Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4216 lines
113 KiB

api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
3 years ago
6 years ago
6 years ago
5 years ago
5 years ago
6 years ago
  1. /*
  2. SENSOR MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. Copyright (C) 2020-2022 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  5. */
  6. #include "espurna.h"
  7. #if SENSOR_SUPPORT
  8. #include "sensor.h"
  9. #include "api.h"
  10. #include "domoticz.h"
  11. #include "i2c.h"
  12. #include "mqtt.h"
  13. #include "ntp.h"
  14. #include "relay.h"
  15. #include "terminal.h"
  16. #include "thingspeak.h"
  17. #include "rtcmem.h"
  18. #include "ws.h"
  19. #include <cfloat>
  20. #include <cmath>
  21. #include <cstring>
  22. #include <limits>
  23. #include <vector>
  24. //--------------------------------------------------------------------------------
  25. #include "sensors/BaseSensor.h"
  26. #include "sensors/BaseEmonSensor.h"
  27. #include "sensors/BaseAnalogEmonSensor.h"
  28. #include "sensors/BaseAnalogSensor.h"
  29. #if DUMMY_SENSOR_SUPPORT
  30. #include "sensors/DummySensor.h"
  31. #endif
  32. #if AM2320_SUPPORT
  33. #include "sensors/AM2320Sensor.h"
  34. #endif
  35. #if ANALOG_SUPPORT
  36. #include "sensors/AnalogSensor.h"
  37. #endif
  38. #if BH1750_SUPPORT
  39. #include "sensors/BH1750Sensor.h"
  40. #endif
  41. #if BMP180_SUPPORT
  42. #include "sensors/BMP180Sensor.h"
  43. #endif
  44. #if BMX280_SUPPORT
  45. #include "sensors/BMX280Sensor.h"
  46. #endif
  47. #if BME680_SUPPORT
  48. #include "sensors/BME680Sensor.h"
  49. #endif
  50. #if CSE7766_SUPPORT
  51. #include "sensors/CSE7766Sensor.h"
  52. #endif
  53. #if DALLAS_SUPPORT
  54. #include "sensors/DallasSensor.h"
  55. #endif
  56. #if DHT_SUPPORT
  57. #include "sensors/DHTSensor.h"
  58. #endif
  59. #if DIGITAL_SUPPORT
  60. #include "sensors/DigitalSensor.h"
  61. #endif
  62. #if ECH1560_SUPPORT
  63. #include "sensors/ECH1560Sensor.h"
  64. #endif
  65. #if EMON_ADC121_SUPPORT
  66. #include "sensors/EmonADC121Sensor.h"
  67. #endif
  68. #if EMON_ADS1X15_SUPPORT
  69. #include "sensors/EmonADS1X15Sensor.h"
  70. #endif
  71. #if EMON_ANALOG_SUPPORT
  72. #include "sensors/EmonAnalogSensor.h"
  73. #endif
  74. #if EVENTS_SUPPORT
  75. #include "sensors/EventSensor.h"
  76. #endif
  77. #if EZOPH_SUPPORT
  78. #include "sensors/EZOPHSensor.h"
  79. #endif
  80. #if GEIGER_SUPPORT
  81. #include "sensors/GeigerSensor.h"
  82. #endif
  83. #if GUVAS12SD_SUPPORT
  84. #include "sensors/GUVAS12SDSensor.h"
  85. #endif
  86. #if HLW8012_SUPPORT
  87. #include "sensors/HLW8012Sensor.h"
  88. #endif
  89. #if INA219_SUPPORT
  90. #include "sensors/INA219Sensor.h"
  91. #endif
  92. #if LDR_SUPPORT
  93. #include "sensors/LDRSensor.h"
  94. #endif
  95. #if MAX6675_SUPPORT
  96. #include "sensors/MAX6675Sensor.h"
  97. #endif
  98. #if MICS2710_SUPPORT
  99. #include "sensors/MICS2710Sensor.h"
  100. #endif
  101. #if MICS5525_SUPPORT
  102. #include "sensors/MICS5525Sensor.h"
  103. #endif
  104. #if MHZ19_SUPPORT
  105. #include "sensors/MHZ19Sensor.h"
  106. #endif
  107. #if NTC_SUPPORT
  108. #include "sensors/NTCSensor.h"
  109. #endif
  110. #if SDS011_SUPPORT
  111. #include "sensors/SDS011Sensor.h"
  112. #endif
  113. #if SENSEAIR_SUPPORT
  114. #include "sensors/SenseAirSensor.h"
  115. #endif
  116. #if PM1006_SUPPORT
  117. #include "sensors/PM1006Sensor.h"
  118. #endif
  119. #if PMSX003_SUPPORT
  120. #include "sensors/PMSX003Sensor.h"
  121. #endif
  122. #if PULSEMETER_SUPPORT
  123. #include "sensors/PulseMeterSensor.h"
  124. #endif
  125. #if PZEM004T_SUPPORT
  126. #include "sensors/PZEM004TSensor.h"
  127. #endif
  128. #if SHT3X_I2C_SUPPORT
  129. #include "sensors/SHT3XI2CSensor.h"
  130. #endif
  131. #if SI7021_SUPPORT
  132. #include "sensors/SI7021Sensor.h"
  133. #endif
  134. #if SM300D2_SUPPORT
  135. #include "sensors/SM300D2Sensor.h"
  136. #endif
  137. #if SONAR_SUPPORT
  138. #include "sensors/SonarSensor.h"
  139. #endif
  140. #if T6613_SUPPORT
  141. #include "sensors/T6613Sensor.h"
  142. #endif
  143. #if TMP3X_SUPPORT
  144. #include "sensors/TMP3XSensor.h"
  145. #endif
  146. #if V9261F_SUPPORT
  147. #include "sensors/V9261FSensor.h"
  148. #endif
  149. #if VEML6075_SUPPORT
  150. #include "sensors/VEML6075Sensor.h"
  151. #endif
  152. #if VL53L1X_SUPPORT
  153. #include "sensors/VL53L1XSensor.h"
  154. #endif
  155. #if ADE7953_SUPPORT
  156. #include "sensors/ADE7953Sensor.h"
  157. #endif
  158. #if SI1145_SUPPORT
  159. #include "sensors/SI1145Sensor.h"
  160. #endif
  161. #if HDC1080_SUPPORT
  162. #include "sensors/HDC1080Sensor.h"
  163. #endif
  164. #if PZEM004TV30_SUPPORT
  165. #include "sensors/PZEM004TV30Sensor.h"
  166. #endif
  167. #include "filters/LastFilter.h"
  168. #include "filters/MaxFilter.h"
  169. #include "filters/MedianFilter.h"
  170. #include "filters/MovingAverageFilter.h"
  171. #include "filters/SumFilter.h"
  172. //--------------------------------------------------------------------------------
  173. namespace espurna {
  174. namespace sensor {
  175. Value::operator bool() const {
  176. return !std::isinf(value) && !std::isnan(value);
  177. }
  178. String error(unsigned char error) {
  179. const char* result { nullptr };
  180. switch (error) {
  181. case SENSOR_ERROR_OK:
  182. result = PSTR("OK");
  183. break;
  184. case SENSOR_ERROR_OUT_OF_RANGE:
  185. result = PSTR("Out of Range");
  186. break;
  187. case SENSOR_ERROR_WARM_UP:
  188. result = PSTR("Warming Up");
  189. break;
  190. case SENSOR_ERROR_TIMEOUT:
  191. result = PSTR("Timeout");
  192. break;
  193. case SENSOR_ERROR_UNKNOWN_ID:
  194. result = PSTR("Unknown ID");
  195. break;
  196. case SENSOR_ERROR_CRC:
  197. result = PSTR("CRC / Data Error");
  198. break;
  199. case SENSOR_ERROR_I2C:
  200. result = PSTR("I2C Error");
  201. break;
  202. case SENSOR_ERROR_GPIO_USED:
  203. result = PSTR("GPIO Already Used");
  204. break;
  205. case SENSOR_ERROR_CALIBRATION:
  206. result = PSTR("Calibration Error");
  207. break;
  208. case SENSOR_ERROR_OVERFLOW:
  209. result = PSTR("Value Overflow");
  210. break;
  211. case SENSOR_ERROR_NOT_READY:
  212. result = PSTR("Not Ready");
  213. break;
  214. case SENSOR_ERROR_CONFIG:
  215. result = PSTR("Invalid Configuration");
  216. break;
  217. case SENSOR_ERROR_SUPPORT:
  218. result = PSTR("Not Supported");
  219. break;
  220. case SENSOR_ERROR_OTHER:
  221. default:
  222. result = PSTR("Other / Unknown Error");
  223. break;
  224. }
  225. return result;
  226. }
  227. template <typename T>
  228. void forEachError(T&& callback) {
  229. for (unsigned char error = SENSOR_ERROR_OK; error < SENSOR_ERROR_MAX; ++error) {
  230. callback(error);
  231. }
  232. }
  233. struct ReadValue {
  234. double raw;
  235. double processed;
  236. double filtered;
  237. };
  238. enum class Filter : int {
  239. Last,
  240. Max,
  241. Median,
  242. MovingAverage,
  243. Sum,
  244. };
  245. // Generic storage. Most of the time we init this on boot with both members or start at 0 and increment with watt-second
  246. Energy::Energy(Energy::Pair pair) :
  247. _kwh(pair.kwh),
  248. _ws(pair.ws)
  249. {}
  250. Energy::Energy(WattSeconds ws) {
  251. _ws.value = ws.value;
  252. while (_ws.value >= WattSecondsMax) {
  253. _ws.value -= WattSecondsMax;
  254. ++_kwh.value;
  255. }
  256. }
  257. Energy::Energy(WattHours other) :
  258. Energy(static_cast<double>(other.value) / 1000.0)
  259. {}
  260. Energy::Energy(double kwh) {
  261. double lhs;
  262. double rhs = fs_modf(kwh, &lhs);
  263. _kwh.value = lhs;
  264. _ws.value = rhs * static_cast<double>(KilowattHours::Ratio::num);
  265. }
  266. Energy& Energy::operator+=(WattSeconds other) {
  267. return *this += Energy(other);
  268. }
  269. Energy Energy::operator+(WattSeconds other) {
  270. Energy result(*this);
  271. result += other;
  272. return result;
  273. }
  274. Energy& Energy::operator+=(const Energy& other) {
  275. _kwh.value += other._kwh.value;
  276. const auto left = WattSecondsMax - _ws.value;
  277. if (other._ws.value >= left) {
  278. _kwh.value += 1;
  279. _ws.value += (other._ws.value - left);
  280. } else {
  281. _ws.value += other._ws.value;
  282. }
  283. return *this;
  284. }
  285. Energy::operator bool() const {
  286. return (_kwh.value > 0) && (_ws.value > 0);
  287. }
  288. WattSeconds Energy::asWattSeconds() const {
  289. using Type = WattSeconds::Type;
  290. static constexpr auto TypeMax = std::numeric_limits<Type>::max();
  291. static constexpr Type KwhMax { TypeMax / WattSecondsMax };
  292. auto kwh = _kwh.value;
  293. while (kwh >= KwhMax) {
  294. kwh -= KwhMax;
  295. }
  296. WattSeconds out;
  297. out.value += _ws.value;
  298. out.value += kwh * WattSecondsMax;
  299. return out;
  300. }
  301. double Energy::asDouble() const {
  302. return static_cast<double>(_kwh.value)
  303. + static_cast<double>(_ws.value)
  304. / static_cast<double>(WattSecondsMax);
  305. }
  306. String Energy::asString() const {
  307. String out;
  308. // Value without `+` is treated as just `<kWh>`
  309. out += String(_kwh.value, 10);
  310. if (_ws.value) {
  311. out += '+';
  312. out += String(_ws.value, 10);
  313. }
  314. return out;
  315. }
  316. void Energy::reset() {
  317. *this = Energy{};
  318. }
  319. namespace {
  320. class BaseSensorPtr {
  321. public:
  322. BaseSensorPtr() = delete;
  323. constexpr BaseSensorPtr(const BaseSensorPtr&) = default;
  324. constexpr BaseSensorPtr(BaseSensorPtr&&) noexcept = default;
  325. #if __cplusplus > 201103L
  326. constexpr BaseSensorPtr& operator=(const BaseSensorPtr&) = default;
  327. constexpr BaseSensorPtr& operator=(BaseSensorPtr&&) noexcept = default;
  328. #else
  329. BaseSensorPtr& operator=(const BaseSensorPtr&) = default;
  330. BaseSensorPtr& operator=(BaseSensorPtr&&) noexcept = default;
  331. #endif
  332. constexpr BaseSensorPtr(std::nullptr_t) = delete;
  333. constexpr BaseSensorPtr& operator=(std::nullptr_t) = delete;
  334. constexpr BaseSensorPtr(BaseSensor* ptr) :
  335. _ptr(ptr)
  336. {}
  337. constexpr BaseSensor* get() const {
  338. return _ptr;
  339. }
  340. constexpr BaseSensor* operator->() const {
  341. return _ptr;
  342. }
  343. private:
  344. BaseSensor* _ptr;
  345. };
  346. using BaseFilterPtr = std::unique_ptr<BaseFilter>;
  347. class Magnitude {
  348. private:
  349. static unsigned char _counts[MAGNITUDE_MAX];
  350. public:
  351. static size_t counts(unsigned char type) {
  352. return _counts[type];
  353. }
  354. Magnitude() = delete;
  355. Magnitude(const Magnitude&) = delete;
  356. Magnitude& operator=(const Magnitude&) = delete;
  357. Magnitude(Magnitude&& other) noexcept = default;
  358. Magnitude& operator=(Magnitude&&) noexcept = default;
  359. Magnitude(BaseSensorPtr, unsigned char slot, unsigned char type);
  360. BaseSensorPtr sensor; // Sensor object, *cannot be empty*
  361. unsigned char slot; // Sensor slot # taken by the magnitude, used to access the measurement
  362. unsigned char type; // Type of measurement, returned by the BaseSensor::type(slot)
  363. unsigned char index_global; // N'th magnitude of it's type, across all of the active sensors
  364. Unit units { Unit::None }; // Units of measurement
  365. unsigned char decimals { 0u }; // Number of decimals in textual representation
  366. Filter filter_type { Filter::Median }; // Instead of using raw value, filter it through a filter object
  367. BaseFilterPtr filter; // *cannot be empty*
  368. double last { Value::Unknown }; // Last raw value from sensor (unfiltered)
  369. double reported { Value::Unknown }; // Last reported value
  370. double min_delta { 0.0 }; // Minimum value change to report
  371. double max_delta { 0.0 }; // Maximum value change to report
  372. double correction { 0.0 }; // Value correction (applied when processing)
  373. double zero_threshold { Value::Unknown }; // Reset value to zero when below threshold (applied when reading)
  374. };
  375. static_assert(
  376. std::is_nothrow_move_constructible<Magnitude>::value,
  377. "std::vector<Magnitude> should be able to work with resize()"
  378. );
  379. static_assert(
  380. !std::is_copy_constructible<Magnitude>::value,
  381. "std::vector<Magnitude> should only use move ctor"
  382. );
  383. Magnitude::Magnitude(BaseSensorPtr sensor, unsigned char slot, unsigned char type) :
  384. sensor(std::move(sensor)),
  385. slot(slot),
  386. type(type),
  387. index_global(_counts[type])
  388. {
  389. ++_counts[type];
  390. }
  391. unsigned char Magnitude::_counts[MAGNITUDE_MAX] = {0};
  392. bool isEmon(BaseSensorPtr sensor) {
  393. return (sensor->kind() == BaseEmonSensor::Kind)
  394. || (sensor->kind() == BaseAnalogEmonSensor::Kind);
  395. }
  396. bool isAnalogEmon(BaseSensorPtr sensor) {
  397. return sensor->kind() == BaseAnalogEmonSensor::Kind;
  398. }
  399. bool isAnalog(BaseSensorPtr sensor) {
  400. return sensor->kind() == BaseAnalogSensor::Kind;
  401. }
  402. } // namespace
  403. namespace convert {
  404. namespace temperature {
  405. namespace {
  406. struct Base {
  407. constexpr Base() = default;
  408. constexpr explicit Base(double value) :
  409. _value(value)
  410. {}
  411. constexpr double value() const {
  412. return _value;
  413. }
  414. constexpr operator double() const {
  415. return _value;
  416. }
  417. private:
  418. double _value { 0.0 };
  419. };
  420. struct Kelvin : public Base {
  421. using Base::Base;
  422. };
  423. struct Farenheit : public Base {
  424. using Base::Base;
  425. };
  426. struct Celcius : public Base {
  427. using Base::Base;
  428. };
  429. static constexpr Celcius AbsoluteZero { -273.15 };
  430. namespace internal {
  431. template <typename To, typename From, typename Same = void>
  432. struct Converter {
  433. };
  434. template <typename To, typename From>
  435. struct Converter<To, From, typename std::enable_if<std::is_same<To, From>::value>::type> {
  436. static constexpr To convert(To value) {
  437. return value;
  438. }
  439. };
  440. static constexpr double celcius_to_kelvin(double celcius) {
  441. return celcius - AbsoluteZero;
  442. }
  443. static constexpr double celcius_to_farenheit(double celcius) {
  444. return (celcius * (9.0 / 5.0)) + 32.0;
  445. }
  446. static constexpr double farenheit_to_celcius(double farenheit) {
  447. return (farenheit - 32.0) * (5.0 / 9.0);
  448. }
  449. static constexpr double farenheit_to_kelvin(double farenheit) {
  450. return celcius_to_kelvin(farenheit_to_celcius(farenheit));
  451. }
  452. static constexpr double kelvin_to_celcius(double kelvin) {
  453. return kelvin + AbsoluteZero;
  454. }
  455. static constexpr double kelvin_to_farenheit(double kelvin) {
  456. return celcius_to_farenheit(kelvin_to_celcius(kelvin));
  457. }
  458. static_assert(celcius_to_kelvin(kelvin_to_celcius(0.0)) == 0.0, "");
  459. static_assert(celcius_to_farenheit(farenheit_to_celcius(0.0)) == 0.0, "");
  460. static_assert(farenheit_to_kelvin(kelvin_to_farenheit(0.0)) == 0.0, "");
  461. static_assert(farenheit_to_celcius(celcius_to_farenheit(0.0)) == 0.0, "");
  462. static_assert(kelvin_to_celcius(celcius_to_kelvin(0.0)) == 0.0, "");
  463. // ref. https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
  464. static constexpr bool almost_equal(double lhs, double rhs, int ulp) {
  465. // the machine epsilon has to be scaled to the magnitude of the values used
  466. // and multiplied by the desired precision in ULPs (units in the last place)
  467. return __builtin_fabs(lhs - rhs) <= std::numeric_limits<double>::epsilon() * __builtin_fabs(lhs + rhs) * ulp
  468. // unless the result is subnormal
  469. || __builtin_fabs(lhs - rhs) < std::numeric_limits<double>::min();
  470. }
  471. static_assert(almost_equal(10.0, kelvin_to_farenheit(farenheit_to_kelvin(10.0)), 3), "");
  472. template <>
  473. struct Converter<Celcius, Kelvin> {
  474. static constexpr Celcius convert(Kelvin kelvin) {
  475. return Celcius{ kelvin_to_celcius(kelvin.value()) };
  476. }
  477. };
  478. template <>
  479. struct Converter<Farenheit, Kelvin> {
  480. static constexpr Farenheit convert(Kelvin kelvin) {
  481. return Farenheit{ kelvin_to_farenheit(kelvin.value()) };
  482. }
  483. };
  484. template <>
  485. struct Converter<Kelvin, Celcius> {
  486. static constexpr Kelvin convert(Celcius celcius) {
  487. return Kelvin{ celcius_to_kelvin(celcius.value()) };
  488. }
  489. };
  490. template <>
  491. struct Converter<Farenheit, Celcius> {
  492. static constexpr Farenheit convert(Celcius celcius) {
  493. return Farenheit{ celcius_to_farenheit(celcius.value()) };
  494. }
  495. };
  496. template <>
  497. struct Converter<Kelvin, Farenheit> {
  498. static constexpr Kelvin convert(Farenheit farenheit) {
  499. return Kelvin{ farenheit_to_kelvin(farenheit.value()) };
  500. }
  501. };
  502. template <>
  503. struct Converter<Celcius, Farenheit> {
  504. static constexpr Celcius convert(Farenheit farenheit) {
  505. return Celcius{ farenheit_to_celcius(farenheit.value()) };
  506. }
  507. };
  508. // just some sanity checks. note that floating point will not always produce exact results
  509. // (and it might not be a good idea to actually have anything compare with the Farenheit one)
  510. static_assert(Converter<Kelvin, Kelvin>::convert(Kelvin{0.0}) == Kelvin{0.0}, "");
  511. static_assert(Converter<Kelvin, Celcius>::convert(AbsoluteZero) == Kelvin{0.0}, "");
  512. static_assert(Converter<Celcius, Celcius>::convert(AbsoluteZero) == AbsoluteZero, "");
  513. static_assert(Converter<Celcius, Kelvin>::convert(Kelvin{0.0}) == AbsoluteZero, "");
  514. } // namespace internal
  515. template <typename To, typename From>
  516. constexpr To unit_cast(From value) {
  517. return internal::Converter<To, From>::convert(value);
  518. }
  519. static_assert(unit_cast<Kelvin>(AbsoluteZero).value() == 0.0, "");
  520. static_assert(unit_cast<Celcius>(AbsoluteZero).value() == AbsoluteZero.value(), "");
  521. constexpr bool supported(Unit unit) {
  522. return (unit == Unit::Celcius)
  523. || (unit == Unit::Kelvin)
  524. || (unit == Unit::Farenheit);
  525. }
  526. // since the outside api only works with the enumeration, make sure to cast it to our types for conversion
  527. // a table like this could've also worked
  528. // > {Unit(from), Unit(to), Converter(double(*)(double))}
  529. // but, it is ~0.6KiB vs. ~0.1KiB for this one. plus, some obstacles with c++11 implementation
  530. // although, there may be a way to make this cheaper in both compile-time and runtime
  531. // attempt to convert the input value from one unit to the other
  532. // will return the input value when units match or there's no known conversion
  533. constexpr double convert(double value, Unit from, Unit to) {
  534. #define UNIT_CAST(LHS, RHS) \
  535. ((from == Unit::LHS) && (to == Unit::RHS)) \
  536. ? (unit_cast<RHS, LHS>(LHS{value})) : \
  537. ((from == Unit::RHS) && (to == Unit::LHS)) \
  538. ? (unit_cast<LHS, RHS>(RHS{value}))
  539. return UNIT_CAST(Kelvin, Celcius) :
  540. UNIT_CAST(Kelvin, Farenheit) :
  541. UNIT_CAST(Celcius, Farenheit) : value;
  542. #undef UNIT_CAST
  543. }
  544. } // namespace
  545. } // namespace temperature
  546. // right now, limited to plain and kilo values
  547. // (since we mostly care about a fairly small values)
  548. // type conversion should only work for related types
  549. namespace metric {
  550. namespace {
  551. template <typename __Ratio>
  552. struct Base {
  553. using Type = double;
  554. using Ratio = __Ratio;
  555. constexpr Base() = default;
  556. constexpr explicit Base(Type value) :
  557. _value(value)
  558. {}
  559. constexpr Type value() const {
  560. return _value;
  561. }
  562. constexpr operator Type() const {
  563. return _value;
  564. }
  565. private:
  566. Type _value { 0.0 };
  567. };
  568. template <typename To, typename From>
  569. struct convertible_base : std::false_type {
  570. };
  571. template <typename To, typename From>
  572. constexpr bool is_convertible_base() {
  573. return std::is_same<To, From>::value
  574. || std::is_base_of<std::true_type, convertible_base<To, From>>::value
  575. || std::is_base_of<std::true_type, convertible_base<From, To>>::value;
  576. }
  577. template <typename To, typename From>
  578. using is_convertible = std::enable_if<is_convertible_base<To, From>()>;
  579. template <typename To, typename From,
  580. typename Divide = std::ratio_divide<typename From::Ratio, typename To::Ratio>,
  581. typename = typename is_convertible<To, From>::type>
  582. constexpr To unit_cast(From value) {
  583. return To(value.value()
  584. * static_cast<typename To::Type>(Divide::num)
  585. / static_cast<typename To::Type>(Divide::den));
  586. }
  587. struct Watt : public Base<std::ratio<1, 1>> {
  588. using Base::Base;
  589. };
  590. struct Kilowatt : public Base<std::ratio<1000, 1>> {
  591. using Base::Base;
  592. };
  593. template <>
  594. struct convertible_base<Watt, Kilowatt> : std::true_type {
  595. };
  596. struct Voltampere : public Base<std::ratio<1, 1>> {
  597. using Base::Base;
  598. };
  599. struct Kilovoltampere : public Base<std::ratio<1000, 1>> {
  600. using Base::Base;
  601. };
  602. template <>
  603. struct convertible_base<Voltampere, Kilovoltampere> : std::true_type {
  604. };
  605. struct VoltampereReactive : public Base<std::ratio<1, 1>> {
  606. using Base::Base;
  607. };
  608. struct KilovoltampereReactive : public Base<std::ratio<1000, 1>> {
  609. using Base::Base;
  610. };
  611. template <>
  612. struct convertible_base<VoltampereReactive, KilovoltampereReactive> : std::true_type {
  613. };
  614. struct WattSecond : public Base<std::ratio<1, 1>> {
  615. using Base::Base;
  616. };
  617. using Joule = WattSecond;
  618. struct KilowattHour : public Base<std::ratio<3600000, 1>> {
  619. using Base::Base;
  620. };
  621. template <>
  622. struct convertible_base<WattSecond, KilowattHour> : std::true_type {
  623. };
  624. static_assert(is_convertible_base<Voltampere, Kilovoltampere>(), "");
  625. static_assert(is_convertible_base<Kilovoltampere, Voltampere>(), "");
  626. static_assert(!is_convertible_base<KilovoltampereReactive, Voltampere>(), "");
  627. static_assert(is_convertible_base<Joule, WattSecond>(), "");
  628. static_assert(unit_cast<Joule>(KilowattHour{0.02}) == 72000.0, "");
  629. static_assert(unit_cast<VoltampereReactive>(KilovoltampereReactive{1234.0}) == 1234000.0, "");
  630. constexpr bool supported(Unit unit) {
  631. return (unit == Unit::Voltampere)
  632. || (unit == Unit::Kilovoltampere)
  633. || (unit == Unit::VoltampereReactive)
  634. || (unit == Unit::KilovoltampereReactive)
  635. || (unit == Unit::Watt)
  636. || (unit == Unit::Kilowatt)
  637. || (unit == Unit::Joule)
  638. || (unit == Unit::WattSecond)
  639. || (unit == Unit::KilowattHour);
  640. }
  641. // Here we only care about the direct counterparts
  642. // Plus, we still don't enforce supported() at compile time,
  643. // only safeguard is unit_cast<> failing for 'incompatible' base types
  644. constexpr double convert(double value, Unit from, Unit to) {
  645. #define UNIT_CAST(LHS, RHS) \
  646. ((from == Unit::LHS) && (to == Unit::RHS)) \
  647. ? (unit_cast<RHS, LHS>(LHS{value})) : \
  648. ((from == Unit::RHS) && (to == Unit::LHS)) \
  649. ? (unit_cast<LHS, RHS>(RHS{value}))
  650. return UNIT_CAST(Watt, Kilowatt) :
  651. UNIT_CAST(Voltampere, Kilovoltampere) :
  652. UNIT_CAST(VoltampereReactive, KilovoltampereReactive) :
  653. UNIT_CAST(Joule, KilowattHour) :
  654. UNIT_CAST(WattSecond, KilowattHour) : value;
  655. #undef UNIT_CAST
  656. }
  657. } // namespace
  658. } // namespace metric
  659. } // namespace convert
  660. namespace build {
  661. namespace {
  662. constexpr double DefaultMinDelta { 0.0 };
  663. constexpr double DefaultMaxDelta { 0.0 };
  664. constexpr espurna::duration::Seconds initInterval() {
  665. return espurna::duration::Seconds(SENSOR_INIT_INTERVAL);
  666. }
  667. constexpr espurna::duration::Seconds ReadIntervalMin { SENSOR_READ_MIN_INTERVAL };
  668. constexpr espurna::duration::Seconds ReadIntervalMax { SENSOR_READ_MAX_INTERVAL };
  669. constexpr espurna::duration::Seconds readInterval() {
  670. return espurna::duration::Seconds(SENSOR_READ_INTERVAL);
  671. }
  672. constexpr size_t ReportEveryMin PROGMEM { SENSOR_REPORT_MIN_EVERY };
  673. constexpr size_t ReportEveryMax PROGMEM { SENSOR_REPORT_MAX_EVERY };
  674. constexpr size_t reportEvery() {
  675. return SENSOR_REPORT_EVERY;
  676. }
  677. constexpr size_t saveEvery() {
  678. return SENSOR_SAVE_EVERY;
  679. }
  680. constexpr bool realTimeValues() {
  681. return SENSOR_REAL_TIME_VALUES == 1;
  682. }
  683. constexpr bool useIndex() {
  684. return SENSOR_USE_INDEX == 1;
  685. }
  686. } // namespace
  687. } // namespace build
  688. namespace settings {
  689. namespace filters {
  690. namespace {
  691. alignas(4) static constexpr char Last[] PROGMEM = "last";
  692. alignas(4) static constexpr char Max[] PROGMEM = "max";
  693. alignas(4) static constexpr char Median[] PROGMEM = "median";
  694. alignas(4) static constexpr char MovingAverage[] PROGMEM = "moving-average";
  695. alignas(4) static constexpr char Sum[] PROGMEM = "sum";
  696. static constexpr espurna::settings::options::Enumeration<Filter> Options[] PROGMEM {
  697. {Filter::Last, Last},
  698. {Filter::Max, Max},
  699. {Filter::Median, Median},
  700. {Filter::MovingAverage, MovingAverage},
  701. {Filter::Sum, Sum},
  702. };
  703. } // namespace
  704. } // namespace filters
  705. namespace units {
  706. namespace {
  707. alignas(4) static constexpr char Farenheit[] PROGMEM = "°F";
  708. alignas(4) static constexpr char Celcius[] PROGMEM = "°C";
  709. alignas(4) static constexpr char Kelvin[] PROGMEM = "K";
  710. alignas(4) static constexpr char Percentage[] PROGMEM = "%";
  711. alignas(4) static constexpr char Hectopascal[] PROGMEM = "hPa";
  712. alignas(4) static constexpr char Ampere[] PROGMEM = "A";
  713. alignas(4) static constexpr char Volt[] PROGMEM = "V";
  714. alignas(4) static constexpr char Watt[] PROGMEM = "W";
  715. alignas(4) static constexpr char Kilowatt[] PROGMEM = "kW";
  716. alignas(4) static constexpr char Voltampere[] PROGMEM = "VA";
  717. alignas(4) static constexpr char Kilovoltampere[] PROGMEM = "kVA";
  718. alignas(4) static constexpr char VoltampereReactive[] PROGMEM = "VAR";
  719. alignas(4) static constexpr char KilovoltampereReactive[] PROGMEM = "kVAR";
  720. alignas(4) static constexpr char Joule[] PROGMEM = "J";
  721. alignas(4) static constexpr char KilowattHour[] PROGMEM = "kWh";
  722. alignas(4) static constexpr char MicrogrammPerCubicMeter[] PROGMEM = "µg/m³";
  723. alignas(4) static constexpr char PartsPerMillion[] PROGMEM = "ppm";
  724. alignas(4) static constexpr char Lux[] PROGMEM = "lux";
  725. alignas(4) static constexpr char UltravioletIndex[] PROGMEM = "UVindex";
  726. alignas(4) static constexpr char Ohm[] PROGMEM = "ohm";
  727. alignas(4) static constexpr char MilligrammPerCubicMeter[] PROGMEM = "mg/m³";
  728. alignas(4) static constexpr char CountsPerMinute[] PROGMEM = "cpm";
  729. alignas(4) static constexpr char MicrosievertPerHour[] PROGMEM = "µSv/h";
  730. alignas(4) static constexpr char Meter[] PROGMEM = "m";
  731. alignas(4) static constexpr char Hertz[] PROGMEM = "Hz";
  732. alignas(4) static constexpr char Ph[] PROGMEM = "pH";
  733. alignas(4) static constexpr char None[] PROGMEM = "none";
  734. static constexpr espurna::settings::options::Enumeration<Unit> Options[] PROGMEM {
  735. {Unit::Farenheit, Farenheit},
  736. {Unit::Celcius, Celcius},
  737. {Unit::Kelvin, Kelvin},
  738. {Unit::Percentage, Percentage},
  739. {Unit::Hectopascal, Hectopascal},
  740. {Unit::Ampere, Ampere},
  741. {Unit::Volt, Volt},
  742. {Unit::Watt, Watt},
  743. {Unit::Kilowatt, Kilowatt},
  744. {Unit::Voltampere, Voltampere},
  745. {Unit::Kilovoltampere, Kilovoltampere},
  746. {Unit::VoltampereReactive, VoltampereReactive},
  747. {Unit::KilovoltampereReactive, KilovoltampereReactive},
  748. {Unit::Joule, Joule},
  749. {Unit::WattSecond, Joule},
  750. {Unit::KilowattHour, KilowattHour},
  751. {Unit::MicrogrammPerCubicMeter, MicrogrammPerCubicMeter},
  752. {Unit::PartsPerMillion, PartsPerMillion},
  753. {Unit::Lux, Lux},
  754. {Unit::UltravioletIndex, UltravioletIndex},
  755. {Unit::Ohm, Ohm},
  756. {Unit::MilligrammPerCubicMeter, MilligrammPerCubicMeter},
  757. {Unit::CountsPerMinute, CountsPerMinute},
  758. {Unit::MicrosievertPerHour, MicrosievertPerHour},
  759. {Unit::Meter, Meter},
  760. {Unit::Hertz, Hertz},
  761. {Unit::Ph, Ph},
  762. {Unit::None, None},
  763. };
  764. } // namespace
  765. } // namespace units
  766. namespace prefix {
  767. namespace {
  768. alignas(4) static constexpr char Sensor[] PROGMEM = "sns";
  769. alignas(4) static constexpr char Power[] PROGMEM = "pwr";
  770. alignas(4) static constexpr char Temperature[] PROGMEM = "tmp";
  771. alignas(4) static constexpr char Humidity[] PROGMEM = "hum";
  772. alignas(4) static constexpr char Pressure[] PROGMEM = "press";
  773. alignas(4) static constexpr char Current[] PROGMEM = "curr";
  774. alignas(4) static constexpr char Voltage[] PROGMEM = "volt";
  775. alignas(4) static constexpr char PowerActive[] PROGMEM = "pwrP";
  776. alignas(4) static constexpr char PowerApparent[] PROGMEM = "pwrQ";
  777. alignas(4) static constexpr char PowerReactive[] PROGMEM = "pwrModS";
  778. alignas(4) static constexpr char PowerFactor[] PROGMEM = "pwrPF";
  779. alignas(4) static constexpr char Energy[] PROGMEM = "ene";
  780. alignas(4) static constexpr char EnergyDelta[] PROGMEM = "eneDelta";
  781. alignas(4) static constexpr char Analog[] PROGMEM = "analog";
  782. alignas(4) static constexpr char Digital[] PROGMEM = "digital";
  783. alignas(4) static constexpr char Event[] PROGMEM = "event";
  784. alignas(4) static constexpr char Pm1Dot0[] PROGMEM = "pm1dot0";
  785. alignas(4) static constexpr char Pm2Dot5[] PROGMEM = "pm2dot5";
  786. alignas(4) static constexpr char Pm10[] PROGMEM = "pm10";
  787. alignas(4) static constexpr char Co2[] PROGMEM = "co2";
  788. alignas(4) static constexpr char Voc[] PROGMEM = "voc";
  789. alignas(4) static constexpr char Iaq[] PROGMEM = "iaq";
  790. alignas(4) static constexpr char IaqAccuracy[] PROGMEM = "iaqAccuracy";
  791. alignas(4) static constexpr char IaqStatic[] PROGMEM = "iaqStatic";
  792. alignas(4) static constexpr char Lux[] PROGMEM = "lux";
  793. alignas(4) static constexpr char Uva[] PROGMEM = "uva";
  794. alignas(4) static constexpr char Uvb[] PROGMEM = "uvb";
  795. alignas(4) static constexpr char Uvi[] PROGMEM = "uvi";
  796. alignas(4) static constexpr char Distance[] PROGMEM = "distance";
  797. alignas(4) static constexpr char Hcho[] PROGMEM = "hcho";
  798. alignas(4) static constexpr char GeigerCpm[] PROGMEM = "gcpm";
  799. alignas(4) static constexpr char GeigerSievert[] PROGMEM = "gsiev";
  800. alignas(4) static constexpr char Count[] PROGMEM = "count";
  801. alignas(4) static constexpr char No2[] PROGMEM = "no2";
  802. alignas(4) static constexpr char Co[] PROGMEM = "co";
  803. alignas(4) static constexpr char Resistance[] PROGMEM = "res";
  804. alignas(4) static constexpr char Ph[] PROGMEM = "ph";
  805. alignas(4) static constexpr char Frequency[] PROGMEM = "freq";
  806. alignas(4) static constexpr char Tvoc[] PROGMEM = "tvoc";
  807. alignas(4) static constexpr char Ch2o[] PROGMEM = "ch2o";
  808. alignas(4) static constexpr char Unknown[] PROGMEM = "unknown";
  809. constexpr StringView get(unsigned char type) {
  810. return (type == MAGNITUDE_TEMPERATURE) ? Temperature :
  811. (type == MAGNITUDE_HUMIDITY) ? Humidity :
  812. (type == MAGNITUDE_PRESSURE) ? Pressure :
  813. (type == MAGNITUDE_CURRENT) ? Current :
  814. (type == MAGNITUDE_VOLTAGE) ? Voltage :
  815. (type == MAGNITUDE_POWER_ACTIVE) ? PowerActive :
  816. (type == MAGNITUDE_POWER_APPARENT) ? PowerApparent :
  817. (type == MAGNITUDE_POWER_REACTIVE) ? PowerReactive :
  818. (type == MAGNITUDE_POWER_FACTOR) ? PowerFactor :
  819. (type == MAGNITUDE_ENERGY) ? Energy :
  820. (type == MAGNITUDE_ENERGY_DELTA) ? EnergyDelta :
  821. (type == MAGNITUDE_ANALOG) ? Analog :
  822. (type == MAGNITUDE_DIGITAL) ? Digital :
  823. (type == MAGNITUDE_EVENT) ? Event :
  824. (type == MAGNITUDE_PM1DOT0) ? Pm1Dot0 :
  825. (type == MAGNITUDE_PM2DOT5) ? Pm2Dot5 :
  826. (type == MAGNITUDE_PM10) ? Pm10 :
  827. (type == MAGNITUDE_CO2) ? Co2 :
  828. (type == MAGNITUDE_VOC) ? Voc :
  829. (type == MAGNITUDE_IAQ) ? Iaq :
  830. (type == MAGNITUDE_IAQ_ACCURACY) ? IaqAccuracy :
  831. (type == MAGNITUDE_IAQ_STATIC) ? IaqStatic :
  832. (type == MAGNITUDE_LUX) ? Lux :
  833. (type == MAGNITUDE_UVA) ? Uva :
  834. (type == MAGNITUDE_UVB) ? Uvb :
  835. (type == MAGNITUDE_UVI) ? Uvi :
  836. (type == MAGNITUDE_DISTANCE) ? Distance :
  837. (type == MAGNITUDE_HCHO) ? Hcho :
  838. (type == MAGNITUDE_GEIGER_CPM) ? GeigerCpm :
  839. (type == MAGNITUDE_GEIGER_SIEVERT) ? GeigerSievert :
  840. (type == MAGNITUDE_COUNT) ? Count :
  841. (type == MAGNITUDE_NO2) ? No2 :
  842. (type == MAGNITUDE_CO) ? Co :
  843. (type == MAGNITUDE_RESISTANCE) ? Resistance :
  844. (type == MAGNITUDE_PH) ? Ph :
  845. (type == MAGNITUDE_FREQUENCY) ? Frequency :
  846. (type == MAGNITUDE_TVOC) ? Tvoc :
  847. (type == MAGNITUDE_CH2O) ? Ch2o :
  848. Unknown;
  849. }
  850. } // namespace
  851. } // namespace prefix
  852. namespace suffix {
  853. namespace {
  854. alignas(4) static constexpr char Units[] PROGMEM = "Units";
  855. alignas(4) static constexpr char Ratio[] PROGMEM = "Ratio";
  856. alignas(4) static constexpr char Correction[] PROGMEM = "Correction";
  857. alignas(4) static constexpr char ZeroThreshold[] PROGMEM = "ZeroThreshold";
  858. alignas(4) static constexpr char MinDelta[] PROGMEM = "MinDelta";
  859. alignas(4) static constexpr char MaxDelta[] PROGMEM = "MaxDelta";
  860. alignas(4) static constexpr char Mains[] PROGMEM = "Mains";
  861. alignas(4) static constexpr char Reference[] PROGMEM = "Reference";
  862. alignas(4) static constexpr char Total[] PROGMEM = "Total";
  863. alignas(4) static constexpr char Filter[] PROGMEM = "Filter";
  864. } // namespace
  865. } // namespace suffix
  866. namespace keys {
  867. namespace {
  868. alignas(4) static constexpr char ReadInterval[] PROGMEM = "snsRead";
  869. alignas(4) static constexpr char InitInterval[] PROGMEM = "snsInit";
  870. alignas(4) static constexpr char ReportEvery[] PROGMEM = "snsReport";
  871. alignas(4) static constexpr char SaveEvery[] PROGMEM = "snsSave";
  872. alignas(4) static constexpr char RealTimeValues[] PROGMEM = "snsRealTime";
  873. espurna::settings::Key get(espurna::StringView prefix, espurna::StringView suffix, size_t index) {
  874. String key;
  875. key.reserve(prefix.length() + suffix.length() + 4);
  876. key.concat(prefix.c_str(), prefix.length());
  877. key.concat(suffix.c_str(), suffix.length());
  878. return espurna::settings::Key(std::move(key), index);
  879. }
  880. espurna::settings::Key get(const Magnitude& magnitude, espurna::StringView suffix) {
  881. return get(prefix::get(magnitude.type), suffix, magnitude.index_global);
  882. }
  883. } // namespace
  884. } // namespace keys
  885. namespace {
  886. espurna::duration::Seconds readInterval() {
  887. return std::clamp(getSetting(FPSTR(keys::ReadInterval), build::readInterval()),
  888. build::ReadIntervalMin, build::ReadIntervalMax);
  889. }
  890. espurna::duration::Seconds initInterval() {
  891. return std::clamp(getSetting(FPSTR(keys::InitInterval), build::initInterval()),
  892. build::ReadIntervalMin, build::ReadIntervalMax);
  893. }
  894. size_t reportEvery() {
  895. return std::clamp(getSetting(FPSTR(keys::ReportEvery), build::reportEvery()),
  896. build::ReportEveryMin, build::ReportEveryMax);
  897. }
  898. int saveEvery() {
  899. return getSetting(FPSTR(keys::SaveEvery), build::saveEvery());
  900. }
  901. bool realTimeValues() {
  902. return getSetting(FPSTR(keys::RealTimeValues), build::realTimeValues());
  903. }
  904. } // namespace
  905. } // namespace settings
  906. alignas(4) static constexpr char List[] PROGMEM =
  907. #if ADE7953_SUPPORT
  908. "ADE7953 "
  909. #endif
  910. #if AM2320_SUPPORT
  911. "AM2320_I2C "
  912. #endif
  913. #if ANALOG_SUPPORT
  914. "ANALOG "
  915. #endif
  916. #if BH1750_SUPPORT
  917. "BH1750 "
  918. #endif
  919. #if BMP180_SUPPORT
  920. "BMP180 "
  921. #endif
  922. #if BMX280_SUPPORT
  923. "BMX280 "
  924. #endif
  925. #if BME680_SUPPORT
  926. "BME680 "
  927. #endif
  928. #if CSE7766_SUPPORT
  929. "CSE7766 "
  930. #endif
  931. #if DALLAS_SUPPORT
  932. "DALLAS "
  933. #endif
  934. #if DHT_SUPPORT
  935. "DHTXX "
  936. #endif
  937. #if DIGITAL_SUPPORT
  938. "DIGITAL "
  939. #endif
  940. #if ECH1560_SUPPORT
  941. "ECH1560 "
  942. #endif
  943. #if EMON_ADC121_SUPPORT
  944. "EMON_ADC121 "
  945. #endif
  946. #if EMON_ADS1X15_SUPPORT
  947. "EMON_ADX1X15 "
  948. #endif
  949. #if EMON_ANALOG_SUPPORT
  950. "EMON_ANALOG "
  951. #endif
  952. #if EVENTS_SUPPORT
  953. "EVENTS "
  954. #endif
  955. #if GEIGER_SUPPORT
  956. "GEIGER "
  957. #endif
  958. #if GUVAS12SD_SUPPORT
  959. "GUVAS12SD "
  960. #endif
  961. #if HDC1080_SUPPORT
  962. "HDC1080 "
  963. #endif
  964. #if HLW8012_SUPPORT
  965. "HLW8012 "
  966. #endif
  967. #if INA219_SUPPORT
  968. "INA219 "
  969. #endif
  970. #if LDR_SUPPORT
  971. "LDR "
  972. #endif
  973. #if MAX6675_SUPPORT
  974. "MAX6675 "
  975. #endif
  976. #if MHZ19_SUPPORT
  977. "MHZ19 "
  978. #endif
  979. #if MICS2710_SUPPORT
  980. "MICS2710 "
  981. #endif
  982. #if MICS5525_SUPPORT
  983. "MICS5525 "
  984. #endif
  985. #if NTC_SUPPORT
  986. "NTC "
  987. #endif
  988. #if PM1006_SUPPORT
  989. "PM1006 "
  990. #endif
  991. #if PMSX003_SUPPORT
  992. "PMSX003 "
  993. #endif
  994. #if PULSEMETER_SUPPORT
  995. "PULSEMETER "
  996. #endif
  997. #if PZEM004T_SUPPORT
  998. "PZEM004T "
  999. #endif
  1000. #if PZEM004TV30_SUPPORT
  1001. "PZEM004TV30 "
  1002. #endif
  1003. #if SDS011_SUPPORT
  1004. "SDS011 "
  1005. #endif
  1006. #if SENSEAIR_SUPPORT
  1007. "SENSEAIR "
  1008. #endif
  1009. #if SHT3X_I2C_SUPPORT
  1010. "SHT3X_I2C "
  1011. #endif
  1012. #if SI7021_SUPPORT
  1013. "SI7021 "
  1014. #endif
  1015. #if SM300D2_SUPPORT
  1016. "SM300D2 "
  1017. #endif
  1018. #if SONAR_SUPPORT
  1019. "SONAR "
  1020. #endif
  1021. #if T6613_SUPPORT
  1022. "T6613 "
  1023. #endif
  1024. #if TMP3X_SUPPORT
  1025. "TMP3X "
  1026. #endif
  1027. #if V9261F_SUPPORT
  1028. "V9261F "
  1029. #endif
  1030. #if VEML6075_SUPPORT
  1031. "VEML6075 "
  1032. #endif
  1033. #if VL53L1X_SUPPORT
  1034. "VL53L1X "
  1035. #endif
  1036. #if EZOPH_SUPPORT
  1037. "EZOPH "
  1038. #endif
  1039. #if DUMMY_SENSOR_SUPPORT
  1040. "DUMMY "
  1041. #endif
  1042. #if SI1145_SUPPORT
  1043. "SI1145 "
  1044. #endif
  1045. "";
  1046. } // namespace sensor
  1047. namespace settings {
  1048. namespace internal {
  1049. template <>
  1050. espurna::sensor::Unit convert(const String& value) {
  1051. return convert(espurna::sensor::settings::units::Options, value,
  1052. espurna::sensor::Unit::None);
  1053. }
  1054. String serialize(espurna::sensor::Unit unit) {
  1055. return serialize(espurna::sensor::settings::units::Options, unit);
  1056. }
  1057. template <>
  1058. espurna::sensor::Filter convert(const String& value) {
  1059. return convert(espurna::sensor::settings::filters::Options, value,
  1060. espurna::sensor::Filter::Median);
  1061. }
  1062. String serialize(espurna::sensor::Filter filter) {
  1063. return serialize(espurna::sensor::settings::filters::Options, filter);
  1064. }
  1065. } // namespace internal
  1066. } // namespace settings
  1067. namespace sensor {
  1068. namespace magnitude {
  1069. namespace traits {
  1070. constexpr bool correction_supported(unsigned char type) {
  1071. return (type == MAGNITUDE_TEMPERATURE)
  1072. || (type == MAGNITUDE_HUMIDITY)
  1073. || (type == MAGNITUDE_PRESSURE)
  1074. || (type == MAGNITUDE_LUX);
  1075. }
  1076. static constexpr unsigned char ratio_types[] {
  1077. MAGNITUDE_CURRENT,
  1078. MAGNITUDE_VOLTAGE,
  1079. MAGNITUDE_POWER_ACTIVE,
  1080. MAGNITUDE_ENERGY,
  1081. };
  1082. constexpr bool ratio_supported(unsigned char type) {
  1083. return (type == MAGNITUDE_CURRENT)
  1084. || (type == MAGNITUDE_VOLTAGE)
  1085. || (type == MAGNITUDE_POWER_ACTIVE)
  1086. || (type == MAGNITUDE_ENERGY);
  1087. }
  1088. } // namespace traits
  1089. namespace build {
  1090. static constexpr double correction(unsigned char type) {
  1091. return (
  1092. (type == MAGNITUDE_TEMPERATURE) ? (SENSOR_TEMPERATURE_CORRECTION) :
  1093. (type == MAGNITUDE_HUMIDITY) ? (SENSOR_HUMIDITY_CORRECTION) :
  1094. (type == MAGNITUDE_LUX) ? (SENSOR_LUX_CORRECTION) :
  1095. (type == MAGNITUDE_PRESSURE) ? (SENSOR_PRESSURE_CORRECTION) :
  1096. 0.0
  1097. );
  1098. }
  1099. } // namespace build
  1100. namespace {
  1101. String format(const Magnitude& magnitude, double value) {
  1102. // XXX: dtostrf only handles basic floating point values and will never produce scientific notation
  1103. // ensure decimals is within some sane limit and the actual value never goes above this buffer size
  1104. char buffer[64];
  1105. dtostrf(value, 1, magnitude.decimals, buffer);
  1106. return buffer;
  1107. }
  1108. String name(unsigned char type) {
  1109. const char* result = nullptr;
  1110. switch (type) {
  1111. case MAGNITUDE_TEMPERATURE:
  1112. result = PSTR("Temperature");
  1113. break;
  1114. case MAGNITUDE_HUMIDITY:
  1115. result = PSTR("Humidity");
  1116. break;
  1117. case MAGNITUDE_PRESSURE:
  1118. result = PSTR("Pressure");
  1119. break;
  1120. case MAGNITUDE_CURRENT:
  1121. result = PSTR("Current");
  1122. break;
  1123. case MAGNITUDE_VOLTAGE:
  1124. result = PSTR("Voltage");
  1125. break;
  1126. case MAGNITUDE_POWER_ACTIVE:
  1127. result = PSTR("Active Power");
  1128. break;
  1129. case MAGNITUDE_POWER_APPARENT:
  1130. result = PSTR("Apparent Power");
  1131. break;
  1132. case MAGNITUDE_POWER_REACTIVE:
  1133. result = PSTR("Reactive Power");
  1134. break;
  1135. case MAGNITUDE_POWER_FACTOR:
  1136. result = PSTR("Power Factor");
  1137. break;
  1138. case MAGNITUDE_ENERGY:
  1139. result = PSTR("Energy");
  1140. break;
  1141. case MAGNITUDE_ENERGY_DELTA:
  1142. result = PSTR("Energy (delta)");
  1143. break;
  1144. case MAGNITUDE_ANALOG:
  1145. result = PSTR("Analog");
  1146. break;
  1147. case MAGNITUDE_DIGITAL:
  1148. result = PSTR("Digital");
  1149. break;
  1150. case MAGNITUDE_EVENT:
  1151. result = PSTR("Event");
  1152. break;
  1153. case MAGNITUDE_PM1DOT0:
  1154. result = PSTR("PM1.0");
  1155. break;
  1156. case MAGNITUDE_PM2DOT5:
  1157. result = PSTR("PM2.5");
  1158. break;
  1159. case MAGNITUDE_PM10:
  1160. result = PSTR("PM10");
  1161. break;
  1162. case MAGNITUDE_CO2:
  1163. result = PSTR("CO2");
  1164. break;
  1165. case MAGNITUDE_VOC:
  1166. result = PSTR("VOC");
  1167. break;
  1168. case MAGNITUDE_IAQ_STATIC:
  1169. result = PSTR("IAQ (Static)");
  1170. break;
  1171. case MAGNITUDE_IAQ:
  1172. result = PSTR("IAQ");
  1173. break;
  1174. case MAGNITUDE_IAQ_ACCURACY:
  1175. result = PSTR("IAQ Accuracy");
  1176. break;
  1177. case MAGNITUDE_LUX:
  1178. result = PSTR("Lux");
  1179. break;
  1180. case MAGNITUDE_UVA:
  1181. result = PSTR("UVA");
  1182. break;
  1183. case MAGNITUDE_UVB:
  1184. result = PSTR("UVB");
  1185. break;
  1186. case MAGNITUDE_UVI:
  1187. result = PSTR("UVI");
  1188. break;
  1189. case MAGNITUDE_DISTANCE:
  1190. result = PSTR("Distance");
  1191. break;
  1192. case MAGNITUDE_HCHO:
  1193. result = PSTR("HCHO");
  1194. break;
  1195. case MAGNITUDE_GEIGER_CPM:
  1196. case MAGNITUDE_GEIGER_SIEVERT:
  1197. result = PSTR("Local Dose Rate");
  1198. break;
  1199. case MAGNITUDE_COUNT:
  1200. result = PSTR("Count");
  1201. break;
  1202. case MAGNITUDE_NO2:
  1203. result = PSTR("NO2");
  1204. break;
  1205. case MAGNITUDE_CO:
  1206. result = PSTR("CO");
  1207. break;
  1208. case MAGNITUDE_RESISTANCE:
  1209. result = PSTR("Resistance");
  1210. break;
  1211. case MAGNITUDE_PH:
  1212. result = PSTR("pH");
  1213. break;
  1214. case MAGNITUDE_FREQUENCY:
  1215. result = PSTR("Frequency");
  1216. break;
  1217. case MAGNITUDE_TVOC:
  1218. result = PSTR("TVOC");
  1219. break;
  1220. case MAGNITUDE_CH2O:
  1221. result = PSTR("CH2O");
  1222. break;
  1223. case MAGNITUDE_NONE:
  1224. default:
  1225. break;
  1226. }
  1227. return String(result);
  1228. }
  1229. String topic(unsigned char type) {
  1230. const char* result = PSTR("unknown");
  1231. switch (type) {
  1232. case MAGNITUDE_TEMPERATURE:
  1233. result = PSTR("temperature");
  1234. break;
  1235. case MAGNITUDE_HUMIDITY:
  1236. result = PSTR("humidity");
  1237. break;
  1238. case MAGNITUDE_PRESSURE:
  1239. result = PSTR("pressure");
  1240. break;
  1241. case MAGNITUDE_CURRENT:
  1242. result = PSTR("current");
  1243. break;
  1244. case MAGNITUDE_VOLTAGE:
  1245. result = PSTR("voltage");
  1246. break;
  1247. case MAGNITUDE_POWER_ACTIVE:
  1248. result = PSTR("power");
  1249. break;
  1250. case MAGNITUDE_POWER_APPARENT:
  1251. result = PSTR("apparent");
  1252. break;
  1253. case MAGNITUDE_POWER_REACTIVE:
  1254. result = PSTR("reactive");
  1255. break;
  1256. case MAGNITUDE_POWER_FACTOR:
  1257. result = PSTR("factor");
  1258. break;
  1259. case MAGNITUDE_ENERGY:
  1260. result = PSTR("energy");
  1261. break;
  1262. case MAGNITUDE_ENERGY_DELTA:
  1263. result = PSTR("energy_delta");
  1264. break;
  1265. case MAGNITUDE_ANALOG:
  1266. result = PSTR("analog");
  1267. break;
  1268. case MAGNITUDE_DIGITAL:
  1269. result = PSTR("digital");
  1270. break;
  1271. case MAGNITUDE_EVENT:
  1272. result = PSTR("event");
  1273. break;
  1274. case MAGNITUDE_PM1DOT0:
  1275. result = PSTR("pm1dot0");
  1276. break;
  1277. case MAGNITUDE_PM2DOT5:
  1278. result = PSTR("pm2dot5");
  1279. break;
  1280. case MAGNITUDE_PM10:
  1281. result = PSTR("pm10");
  1282. break;
  1283. case MAGNITUDE_CO2:
  1284. result = PSTR("co2");
  1285. break;
  1286. case MAGNITUDE_VOC:
  1287. result = PSTR("voc");
  1288. break;
  1289. case MAGNITUDE_IAQ:
  1290. result = PSTR("iaq");
  1291. break;
  1292. case MAGNITUDE_IAQ_ACCURACY:
  1293. result = PSTR("iaq_accuracy");
  1294. break;
  1295. case MAGNITUDE_IAQ_STATIC:
  1296. result = PSTR("iaq_static");
  1297. break;
  1298. case MAGNITUDE_LUX:
  1299. result = PSTR("lux");
  1300. break;
  1301. case MAGNITUDE_UVA:
  1302. result = PSTR("uva");
  1303. break;
  1304. case MAGNITUDE_UVB:
  1305. result = PSTR("uvb");
  1306. break;
  1307. case MAGNITUDE_UVI:
  1308. result = PSTR("uvi");
  1309. break;
  1310. case MAGNITUDE_DISTANCE:
  1311. result = PSTR("distance");
  1312. break;
  1313. case MAGNITUDE_HCHO:
  1314. result = PSTR("hcho");
  1315. break;
  1316. case MAGNITUDE_GEIGER_CPM:
  1317. result = PSTR("ldr_cpm"); // local dose rate [Counts per minute]
  1318. break;
  1319. case MAGNITUDE_GEIGER_SIEVERT:
  1320. result = PSTR("ldr_uSvh"); // local dose rate [µSievert per hour]
  1321. break;
  1322. case MAGNITUDE_COUNT:
  1323. result = PSTR("count");
  1324. break;
  1325. case MAGNITUDE_NO2:
  1326. result = PSTR("no2");
  1327. break;
  1328. case MAGNITUDE_CO:
  1329. result = PSTR("co");
  1330. break;
  1331. case MAGNITUDE_RESISTANCE:
  1332. result = PSTR("resistance");
  1333. break;
  1334. case MAGNITUDE_PH:
  1335. result = PSTR("ph");
  1336. break;
  1337. case MAGNITUDE_FREQUENCY:
  1338. result = PSTR("frequency");
  1339. break;
  1340. case MAGNITUDE_TVOC:
  1341. result = PSTR("tvoc");
  1342. break;
  1343. case MAGNITUDE_CH2O:
  1344. result = PSTR("ch2o");
  1345. break;
  1346. case MAGNITUDE_NONE:
  1347. default:
  1348. break;
  1349. }
  1350. return String(result);
  1351. }
  1352. String topic(const Magnitude& magnitude) {
  1353. return topic(magnitude.type);
  1354. }
  1355. String topicWithIndex(const Magnitude& magnitude) {
  1356. auto out = topic(magnitude);
  1357. if (sensor::build::useIndex() || (Magnitude::counts(magnitude.type) > 1)) {
  1358. out += '/' + String(magnitude.index_global, 10);
  1359. }
  1360. return out;
  1361. }
  1362. String description(const Magnitude& magnitude) {
  1363. return magnitude.sensor->description(magnitude.slot);
  1364. }
  1365. sensor::Filter defaultFilter(unsigned char type) {
  1366. switch (type) {
  1367. case MAGNITUDE_IAQ:
  1368. case MAGNITUDE_IAQ_STATIC:
  1369. case MAGNITUDE_ENERGY:
  1370. return Filter::Last;
  1371. case MAGNITUDE_EVENT:
  1372. case MAGNITUDE_DIGITAL:
  1373. return Filter::Max;
  1374. case MAGNITUDE_COUNT:
  1375. case MAGNITUDE_ENERGY_DELTA:
  1376. return Filter::Sum;
  1377. case MAGNITUDE_GEIGER_CPM:
  1378. case MAGNITUDE_GEIGER_SIEVERT:
  1379. return Filter::MovingAverage;
  1380. }
  1381. return Filter::Median;
  1382. }
  1383. Filter defaultFilter(const Magnitude& magnitude) {
  1384. return defaultFilter(magnitude.type);
  1385. }
  1386. BaseFilterPtr makeFilter(Filter filter) {
  1387. BaseFilterPtr out;
  1388. switch (filter) {
  1389. case Filter::Last:
  1390. out = std::make_unique<LastFilter>();
  1391. break;
  1392. case Filter::Max:
  1393. out = std::make_unique<MaxFilter>();
  1394. break;
  1395. case Filter::Sum:
  1396. out = std::make_unique<SumFilter>();
  1397. break;
  1398. case Filter::MovingAverage:
  1399. out = std::make_unique<MovingAverageFilter>();
  1400. break;
  1401. case Filter::Median:
  1402. out = std::make_unique<MedianFilter>();
  1403. break;
  1404. }
  1405. return out;
  1406. }
  1407. String units(Unit unit) {
  1408. return espurna::settings::internal::serialize(unit);
  1409. }
  1410. String units(const Magnitude& magnitude) {
  1411. return units(magnitude.units);
  1412. }
  1413. // Hardcoded decimals for each magnitude
  1414. unsigned char decimals(Unit unit) {
  1415. switch (unit) {
  1416. case Unit::Celcius:
  1417. case Unit::Farenheit:
  1418. return 1;
  1419. case Unit::Percentage:
  1420. return 0;
  1421. case Unit::Hectopascal:
  1422. return 2;
  1423. case Unit::Ampere:
  1424. return 3;
  1425. case Unit::Volt:
  1426. return 0;
  1427. case Unit::Watt:
  1428. case Unit::Voltampere:
  1429. case Unit::VoltampereReactive:
  1430. return 0;
  1431. case Unit::Kilowatt:
  1432. case Unit::Kilovoltampere:
  1433. case Unit::KilovoltampereReactive:
  1434. return 3;
  1435. case Unit::KilowattHour:
  1436. return 3;
  1437. case Unit::WattSecond:
  1438. return 0;
  1439. case Unit::CountsPerMinute:
  1440. case Unit::MicrosievertPerHour:
  1441. return 4;
  1442. case Unit::Meter:
  1443. return 3;
  1444. case Unit::Hertz:
  1445. return 1;
  1446. case Unit::UltravioletIndex:
  1447. return 3;
  1448. case Unit::Ph:
  1449. return 3;
  1450. case Unit::None:
  1451. default:
  1452. break;
  1453. }
  1454. return 0;
  1455. }
  1456. double process(const Magnitude& magnitude, double value) {
  1457. // Process input (sensor) units and convert to the ones that magnitude specifies as output
  1458. const auto sensor_units = magnitude.sensor->units(magnitude.slot);
  1459. if (sensor_units != magnitude.units) {
  1460. using namespace sensor::convert;
  1461. if (temperature::supported(sensor_units) && temperature::supported(magnitude.units)) {
  1462. value = temperature::convert(value, sensor_units, magnitude.units);
  1463. } else if (metric::supported(sensor_units) && metric::supported(magnitude.units)) {
  1464. value = metric::convert(value, sensor_units, magnitude.units);
  1465. }
  1466. }
  1467. // Right now, correction is a simple offset.
  1468. // TODO: math expression?
  1469. value = value + magnitude.correction;
  1470. // RAW value might have more decimal points than necessary.
  1471. return roundTo(value, magnitude.decimals);
  1472. }
  1473. } // namespace
  1474. namespace internal {
  1475. namespace {
  1476. std::vector<Magnitude> magnitudes;
  1477. using ReadHandlers = std::forward_list<MagnitudeReadHandler>;
  1478. ReadHandlers read_handlers;
  1479. ReadHandlers report_handlers;
  1480. } // namespace
  1481. } // namespace internal
  1482. size_t count(unsigned char type) {
  1483. return Magnitude::counts(type);
  1484. }
  1485. size_t count() {
  1486. return internal::magnitudes.size();
  1487. }
  1488. void add(BaseSensorPtr sensor, unsigned char slot, unsigned char type) {
  1489. internal::magnitudes.emplace_back(sensor, slot, type);
  1490. }
  1491. const Magnitude* find(unsigned char type, unsigned char index) {
  1492. const Magnitude* out { nullptr };
  1493. const auto result = std::find_if(
  1494. std::cbegin(internal::magnitudes),
  1495. std::cend(internal::magnitudes),
  1496. [&](const Magnitude& magnitude) {
  1497. return (magnitude.type == type) && (magnitude.index_global == index);
  1498. });
  1499. if (result != internal::magnitudes.end()) {
  1500. out = std::addressof(*result);
  1501. }
  1502. return out;
  1503. }
  1504. Magnitude& get(size_t index) {
  1505. return internal::magnitudes[index];
  1506. }
  1507. template <typename T>
  1508. void forEachInstance(T&& callback) {
  1509. for (auto& magnitude : internal::magnitudes) {
  1510. callback(magnitude);
  1511. }
  1512. }
  1513. template <typename T>
  1514. void forEachCounted(T&& callback) {
  1515. for (unsigned char type = MAGNITUDE_NONE + 1; type < MAGNITUDE_MAX; ++type) {
  1516. if (count(type)) {
  1517. callback(type);
  1518. }
  1519. }
  1520. }
  1521. // check if `callback(type)` returns `true` at least once
  1522. template <typename T>
  1523. bool forEachCountedCheck(T&& callback) {
  1524. for (unsigned char type = MAGNITUDE_NONE + 1; type < MAGNITUDE_MAX; ++type) {
  1525. if (count(type) && callback(type)) {
  1526. return true;
  1527. }
  1528. }
  1529. return false;
  1530. }
  1531. void onRead(MagnitudeReadHandler handler) {
  1532. internal::read_handlers.push_front(handler);
  1533. }
  1534. void read(const Value& value) {
  1535. for (auto& handler : internal::read_handlers) {
  1536. handler(value);
  1537. }
  1538. }
  1539. void onReport(MagnitudeReadHandler handler) {
  1540. internal::report_handlers.push_front(handler);
  1541. }
  1542. void report(const Value& report) {
  1543. for (auto& handler : internal::report_handlers) {
  1544. handler(report);
  1545. }
  1546. #if MQTT_SUPPORT
  1547. {
  1548. mqttSend(report.topic.c_str(), report.repr.c_str());
  1549. #if SENSOR_PUBLISH_ADDRESSES
  1550. {
  1551. static constexpr auto AddressTopic = STRING_VIEW(SENSOR_ADDRESS_TOPIC);
  1552. String address_topic;
  1553. address_topic.reserve(report.topic.length() + AddressTopic.length());
  1554. address_topic.concat(AddressTopic.c_str(), AddressTopic.length());
  1555. address_topic += '/';
  1556. address_topic += report.topic;
  1557. mqttSend(address_topic.c_str(), magnitude.sensor->address(magnitude.slot).c_str());
  1558. }
  1559. #endif // SENSOR_PUBLISH_ADDRESSES
  1560. }
  1561. #endif // MQTT_SUPPORT
  1562. }
  1563. Value value(const Magnitude& magnitude, double value) {
  1564. return Value {
  1565. .type = magnitude.type,
  1566. .index = magnitude.index_global,
  1567. .units = magnitude.units,
  1568. .decimals = magnitude.decimals,
  1569. .value = value,
  1570. .topic = topicWithIndex(magnitude),
  1571. .repr = format(magnitude, value),
  1572. };
  1573. }
  1574. Info info(const Magnitude& magnitude) {
  1575. return Info {
  1576. .type = magnitude.type,
  1577. .index = magnitude.index_global,
  1578. .units = magnitude.units,
  1579. .decimals = magnitude.decimals,
  1580. .topic = topicWithIndex(magnitude),
  1581. };
  1582. }
  1583. } // namespace magnitude
  1584. namespace internal {
  1585. std::vector<BaseSensorPtr> sensors;
  1586. bool ready { false };
  1587. bool real_time { build::realTimeValues() };
  1588. size_t report_every { build::reportEvery() };
  1589. duration::Seconds read_interval { build::readInterval() };
  1590. duration::Seconds init_interval { build::initInterval() };
  1591. } // namespace internal
  1592. bool realTimeValues() {
  1593. return internal::real_time;
  1594. }
  1595. void realTimeValues(bool value) {
  1596. internal::real_time = value;
  1597. }
  1598. size_t reportEvery() {
  1599. return internal::report_every;
  1600. }
  1601. void reportEvery(size_t value) {
  1602. internal::report_every = value;
  1603. }
  1604. duration::Seconds readInterval() {
  1605. return internal::read_interval;
  1606. }
  1607. void readInterval(duration::Seconds value) {
  1608. internal::read_interval = value;
  1609. }
  1610. duration::Seconds initInterval() {
  1611. return internal::init_interval;
  1612. }
  1613. void initInterval(duration::Seconds value) {
  1614. internal::init_interval = value;
  1615. }
  1616. template <typename T>
  1617. void forEachInstance(T&& callback) {
  1618. for (auto sensor : internal::sensors) {
  1619. callback(sensor);
  1620. }
  1621. }
  1622. void add(BaseSensor* sensor) {
  1623. internal::sensors.push_back(sensor);
  1624. }
  1625. size_t count() {
  1626. return internal::sensors.size();
  1627. }
  1628. void tick() {
  1629. for (auto sensor : internal::sensors) {
  1630. sensor->tick();
  1631. }
  1632. }
  1633. void pre() {
  1634. for (auto sensor : internal::sensors) {
  1635. sensor->pre();
  1636. if (!sensor->status()) {
  1637. DEBUG_MSG_P(PSTR("[SENSOR] Could not read from %s (%s)\n"),
  1638. sensor->description().c_str(), error(sensor->error()).c_str());
  1639. }
  1640. }
  1641. }
  1642. void post() {
  1643. for (auto sensor : internal::sensors) {
  1644. sensor->post();
  1645. }
  1646. }
  1647. // Registers available sensor classes.
  1648. //
  1649. // Notice that *every* available sensor (*_SUPPORT set to 1) is queued for initialization.
  1650. // For the time being, failure to `begin()` any sensor will stall all subsequent sensors.
  1651. //
  1652. // Future updates *should* work out whether we need to:
  1653. // - allow to 'enable' specific sensor in settings
  1654. // (...would we have too much key prefixes?)
  1655. // - 'probe' sensor (bus scan, attempt to read) separate from actual loading
  1656. // - allow to soft-fail begin()
  1657. // (although, removing stable magnitude IDs)
  1658. //
  1659. // If you want to add another sensor instance of the same type, just duplicate
  1660. // the initialization block and change the respective method arguments.
  1661. // For example, to add a second DHT sensor:
  1662. //
  1663. // #if DHT_SUPPORT
  1664. // {
  1665. // auto* sensor = new DHTSensor();
  1666. // sensor->setGPIO(DHT2_PIN);
  1667. // sensor->setType(DHT2_TYPE);
  1668. // add(sensor);
  1669. // }
  1670. // #endif
  1671. //
  1672. // Obviously, both DHT2_PIN and DHT2_TYPE should be accessible
  1673. // - use `build_src_flags = -DDHT2_PIN=... -DDHT2_TYPE=...`
  1674. // - update config/custom.h or config/sensor.h, adding `#define DHT2_PIN ...` and `#define DHT2_TYPE ...`
  1675. void load() {
  1676. #if AM2320_SUPPORT
  1677. {
  1678. auto* sensor = new AM2320Sensor();
  1679. sensor->setAddress(AM2320_ADDRESS);
  1680. add(sensor);
  1681. }
  1682. #endif
  1683. #if ANALOG_SUPPORT
  1684. {
  1685. auto* sensor = new AnalogSensor();
  1686. sensor->setSamples(ANALOG_SAMPLES);
  1687. sensor->setDelay(ANALOG_DELAY);
  1688. sensor->setFactor(ANALOG_FACTOR);
  1689. sensor->setOffset(ANALOG_OFFSET);
  1690. add(sensor);
  1691. }
  1692. #endif
  1693. #if BH1750_SUPPORT
  1694. {
  1695. auto* sensor = new BH1750Sensor();
  1696. sensor->setAddress(BH1750_ADDRESS);
  1697. sensor->setMode(BH1750_MODE);
  1698. add(sensor);
  1699. }
  1700. #endif
  1701. #if BMP180_SUPPORT
  1702. {
  1703. auto* sensor = new BMP180Sensor();
  1704. sensor->setAddress(BMP180_ADDRESS);
  1705. add(sensor);
  1706. }
  1707. #endif
  1708. #if BMX280_SUPPORT
  1709. {
  1710. // TODO: bmx280AddressN, do some migrate code based on number?
  1711. // Support up to two sensors with full auto-discovery.
  1712. const auto number = std::clamp(getSetting("bmx280Number", BMX280_NUMBER), 1, 2);
  1713. // For second sensor, if BMX280_ADDRESS is 0x00 then auto-discover
  1714. // otherwise choose the other unnamed sensor address
  1715. static constexpr uint8_t Address { BMX280_ADDRESS };
  1716. const decltype(Address) first = getSetting("bmx280Address", Address);
  1717. const decltype(Address) second = (first == 0x00) ? 0x00 : (0x76 + 0x77 - first);
  1718. const decltype(Address) address_map[2] { first, second };
  1719. for (unsigned char n=0; n < number; ++n) {
  1720. auto* sensor = new BMX280Sensor();
  1721. sensor->setAddress(address_map[n]);
  1722. add(sensor);
  1723. }
  1724. }
  1725. #endif
  1726. #if BME680_SUPPORT
  1727. {
  1728. auto* sensor = new BME680Sensor();
  1729. sensor->setAddress(BME680_I2C_ADDRESS);
  1730. add(sensor);
  1731. }
  1732. #endif
  1733. #if CSE7766_SUPPORT
  1734. {
  1735. auto* sensor = new CSE7766Sensor();
  1736. sensor->setRX(CSE7766_RX_PIN);
  1737. add(sensor);
  1738. }
  1739. #endif
  1740. #if DALLAS_SUPPORT
  1741. {
  1742. auto* sensor = new DallasSensor();
  1743. sensor->setGPIO(DALLAS_PIN);
  1744. add(sensor);
  1745. }
  1746. #endif
  1747. #if DHT_SUPPORT
  1748. {
  1749. auto* sensor = new DHTSensor();
  1750. sensor->setGPIO(DHT_PIN);
  1751. sensor->setType(DHT_TYPE);
  1752. add(sensor);
  1753. }
  1754. #endif
  1755. #if DIGITAL_SUPPORT
  1756. {
  1757. const auto pins = gpioPins();
  1758. for (size_t index = 0; index < pins; ++index) {
  1759. const auto pin = DigitalSensor::defaultPin(index);
  1760. if (pin == GPIO_NONE) {
  1761. break;
  1762. }
  1763. auto* sensor = new DigitalSensor();
  1764. sensor->setPin(pin);
  1765. sensor->setPinMode(DigitalSensor::defaultPinMode(index));
  1766. sensor->setDefault(DigitalSensor::defaultState(index));
  1767. add(sensor);
  1768. }
  1769. }
  1770. #endif
  1771. #if DUMMY_SENSOR_SUPPORT
  1772. {
  1773. add(new DummySensor());
  1774. }
  1775. #endif
  1776. #if ECH1560_SUPPORT
  1777. {
  1778. auto* sensor = new ECH1560Sensor();
  1779. sensor->setCLK(ECH1560_CLK_PIN);
  1780. sensor->setMISO(ECH1560_MISO_PIN);
  1781. sensor->setInverted(ECH1560_INVERTED);
  1782. add(sensor);
  1783. }
  1784. #endif
  1785. #if EMON_ADC121_SUPPORT
  1786. {
  1787. auto* sensor = new EmonADC121Sensor();
  1788. sensor->setAddress(EMON_ADC121_I2C_ADDRESS);
  1789. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  1790. sensor->setReferenceVoltage(EMON_REFERENCE_VOLTAGE);
  1791. add(sensor);
  1792. }
  1793. #endif
  1794. #if EMON_ADS1X15_SUPPORT
  1795. {
  1796. auto port = std::make_shared<EmonADS1X15Sensor::I2CPort>(
  1797. EMON_ADS1X15_I2C_ADDRESS, EMON_ADS1X15_TYPE, EMON_ADS1X15_GAIN, EMON_ADS1X15_DATARATE);
  1798. constexpr unsigned char FirstBit { 1 };
  1799. unsigned char mask { EMON_ADS1X15_MASK };
  1800. unsigned char channel { 0 };
  1801. while (mask) {
  1802. if (mask & FirstBit) {
  1803. auto* sensor = new EmonADS1X15Sensor(port);
  1804. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  1805. sensor->setChannel(channel);
  1806. add(sensor);
  1807. }
  1808. ++channel;
  1809. mask >>= 1;
  1810. }
  1811. }
  1812. #endif
  1813. #if EMON_ANALOG_SUPPORT
  1814. {
  1815. auto* sensor = new EmonAnalogSensor();
  1816. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  1817. sensor->setReferenceVoltage(EMON_REFERENCE_VOLTAGE);
  1818. sensor->setResolution(EMON_ANALOG_RESOLUTION);
  1819. add(sensor);
  1820. }
  1821. #endif
  1822. #if EVENTS_SUPPORT
  1823. {
  1824. for (size_t index = 0; index < EventSensor::SensorsMax; ++index) {
  1825. const auto pin = EventSensor::defaultPin(index);
  1826. if (pin == GPIO_NONE) {
  1827. break;
  1828. }
  1829. auto* sensor = new EventSensor();
  1830. sensor->setPin(pin);
  1831. sensor->setPinMode(
  1832. EventSensor::defaultPinMode(index));
  1833. sensor->setDebounceTime(
  1834. EventSensor::defaultDebounceTime(index));
  1835. sensor->setInterruptMode(
  1836. EventSensor::defaultInterruptMode(index));
  1837. add(sensor);
  1838. }
  1839. }
  1840. #endif
  1841. #if GEIGER_SUPPORT
  1842. {
  1843. auto* sensor = new GeigerSensor();
  1844. sensor->setGPIO(GEIGER_PIN);
  1845. sensor->setMode(GEIGER_PIN_MODE);
  1846. sensor->setDebounceTime(
  1847. GeigerSensor::TimeSource::duration { GEIGER_DEBOUNCE });
  1848. sensor->setInterruptMode(GEIGER_INTERRUPT_MODE);
  1849. sensor->setCPM2SievertFactor(GEIGER_CPM2SIEVERT);
  1850. add(sensor);
  1851. }
  1852. #endif
  1853. #if GUVAS12SD_SUPPORT
  1854. {
  1855. auto* sensor = new GUVAS12SDSensor();
  1856. sensor->setGPIO(GUVAS12SD_PIN);
  1857. add(sensor);
  1858. }
  1859. #endif
  1860. #if SONAR_SUPPORT
  1861. {
  1862. auto* sensor = new SonarSensor();
  1863. sensor->setEcho(SONAR_ECHO);
  1864. sensor->setIterations(SONAR_ITERATIONS);
  1865. sensor->setMaxDistance(SONAR_MAX_DISTANCE);
  1866. sensor->setTrigger(SONAR_TRIGGER);
  1867. add(sensor);
  1868. }
  1869. #endif
  1870. #if HLW8012_SUPPORT
  1871. {
  1872. auto* sensor = new HLW8012Sensor();
  1873. sensor->setSEL(getSetting(F("hlw8012SEL"), HLW8012_SEL_PIN));
  1874. sensor->setCF(getSetting(F("hlw8012CF"), HLW8012_CF_PIN));
  1875. sensor->setCF1(getSetting(F("hlw8012CF1"), HLW8012_CF1_PIN));
  1876. sensor->setSELCurrent(HLW8012_SEL_CURRENT);
  1877. add(sensor);
  1878. }
  1879. #endif
  1880. #if INA219_SUPPORT
  1881. {
  1882. auto* sensor = new INA219Sensor();
  1883. sensor->setAddress(INA219_ADDRESS);
  1884. sensor->setOperatingMode(INA219Sensor::INA219_OPERATING_MODE);
  1885. sensor->setShuntMode(INA219Sensor::INA219_SHUNT_MODE);
  1886. sensor->setBusMode(INA219Sensor::INA219_BUS_MODE);
  1887. sensor->setBusRange(INA219Sensor::INA219_BUS_RANGE);
  1888. sensor->setGain(INA219Sensor::INA219_GAIN);
  1889. sensor->setShuntResistance(INA219_SHUNT_RESISTANCE);
  1890. sensor->setMaxExpectedCurrent(INA219_MAX_EXPECTED_CURRENT);
  1891. add(sensor);
  1892. }
  1893. #endif
  1894. #if LDR_SUPPORT
  1895. {
  1896. auto* sensor = new LDRSensor();
  1897. sensor->setSamples(LDR_SAMPLES);
  1898. sensor->setDelay(LDR_DELAY);
  1899. sensor->setType(LDR_TYPE);
  1900. sensor->setPhotocellPositionOnGround(LDR_ON_GROUND);
  1901. sensor->setResistor(LDR_RESISTOR);
  1902. sensor->setPhotocellParameters(LDR_MULTIPLICATION, LDR_POWER);
  1903. add(sensor);
  1904. }
  1905. #endif
  1906. #if MHZ19_SUPPORT
  1907. {
  1908. auto* sensor = new MHZ19Sensor();
  1909. sensor->setRX(MHZ19_RX_PIN);
  1910. sensor->setTX(MHZ19_TX_PIN);
  1911. sensor->setCalibrateAuto(getSetting("mhz19CalibrateAuto", false));
  1912. add(sensor);
  1913. }
  1914. #endif
  1915. #if MICS2710_SUPPORT
  1916. {
  1917. auto* sensor = new MICS2710Sensor();
  1918. sensor->setPreHeatGPIO(MICS2710_PRE_PIN);
  1919. sensor->setR0(MICS2710_R0);
  1920. sensor->setRL(MICS2710_RL);
  1921. sensor->setRS(0);
  1922. add(sensor);
  1923. }
  1924. #endif
  1925. #if MICS5525_SUPPORT
  1926. {
  1927. auto* sensor = new MICS5525Sensor();
  1928. sensor->setR0(MICS5525_R0);
  1929. sensor->setRL(MICS5525_RL);
  1930. sensor->setRS(0);
  1931. add(sensor);
  1932. }
  1933. #endif
  1934. #if NTC_SUPPORT
  1935. {
  1936. auto* sensor = new NTCSensor();
  1937. sensor->setSamples(NTC_SAMPLES);
  1938. sensor->setDelay(NTC_DELAY);
  1939. sensor->setUpstreamResistor(NTC_R_UP);
  1940. sensor->setDownstreamResistor(NTC_R_DOWN);
  1941. sensor->setInputVoltage(NTC_INPUT_VOLTAGE);
  1942. sensor->setBeta(NTC_BETA);
  1943. sensor->setR0(NTC_R0);
  1944. sensor->setT0(NTC_T0);
  1945. add(sensor);
  1946. }
  1947. #endif
  1948. #if PM1006_SUPPORT
  1949. {
  1950. auto* sensor = new PM1006Sensor();
  1951. sensor->setRX(PM1006_RX_PIN);
  1952. add(sensor);
  1953. }
  1954. #endif
  1955. #if PMSX003_SUPPORT
  1956. {
  1957. auto* sensor = new PMSX003Sensor();
  1958. #if PMS_USE_SOFT
  1959. sensor->setRX(PMS_RX_PIN);
  1960. sensor->setTX(PMS_TX_PIN);
  1961. #else
  1962. sensor->setSerial(& PMS_HW_PORT);
  1963. #endif
  1964. sensor->setType(PMS_TYPE);
  1965. add(sensor);
  1966. }
  1967. #endif
  1968. #if PULSEMETER_SUPPORT
  1969. {
  1970. auto* sensor = new PulseMeterSensor();
  1971. sensor->setPin(PULSEMETER_PIN);
  1972. sensor->setInterruptMode(PULSEMETER_INTERRUPT_ON);
  1973. sensor->setDebounceTime(
  1974. PulseMeterSensor::TimeSource::duration{PULSEMETER_DEBOUNCE});
  1975. add(sensor);
  1976. }
  1977. #endif
  1978. #if PZEM004T_SUPPORT
  1979. {
  1980. PZEM004TSensor::PortPtr port;
  1981. auto rx = getSetting("pzemRX", PZEM004TSensor::RxPin);
  1982. auto tx = getSetting("pzemTX", PZEM004TSensor::TxPin);
  1983. if (getSetting("pzemSoft", PZEM004TSensor::useSoftwareSerial())) {
  1984. port = PZEM004TSensor::makeSoftwarePort(rx, tx);
  1985. } else {
  1986. port = PZEM004TSensor::makeHardwarePort(
  1987. PZEM004TSensor::defaultHardwarePort(), rx, tx);
  1988. }
  1989. if (!port) {
  1990. return;
  1991. }
  1992. bool initialized { false };
  1993. #if !defined(PZEM004T_ADDRESSES)
  1994. for (size_t index = 0; index < PZEM004TSensor::DevicesMax; ++index) {
  1995. auto address = getSetting({"pzemAddr", index}, PZEM004TSensor::defaultAddress(index));
  1996. if (!address.isSet()) {
  1997. break;
  1998. }
  1999. auto* ptr = PZEM004TSensor::make(port, address);
  2000. if (ptr) {
  2001. add(ptr);
  2002. initialized = true;
  2003. }
  2004. }
  2005. #else
  2006. String addrs = getSetting("pzemAddr", F(PZEM004T_ADDRESSES));
  2007. constexpr size_t BufferSize{64};
  2008. char buffer[BufferSize]{0};
  2009. if (addrs.length() < BufferSize) {
  2010. std::copy(addrs.c_str(), addrs.c_str() + addrs.length(), buffer);
  2011. buffer[addrs.length()] = '\0';
  2012. size_t device{0};
  2013. char* address{strtok(buffer, " ")};
  2014. while ((device < PZEM004TSensor::DevicesMax) && (address != nullptr)) {
  2015. auto* ptr = PZEM004TSensor::make(port, address);
  2016. if (ptr) {
  2017. add(ptr);
  2018. initialized = true;
  2019. }
  2020. }
  2021. }
  2022. #endif
  2023. if (initialized) {
  2024. PZEM004TSensor::registerTerminalCommands();
  2025. }
  2026. }
  2027. #endif
  2028. #if SENSEAIR_SUPPORT
  2029. {
  2030. auto* sensor = new SenseAirSensor();
  2031. sensor->setRX(SENSEAIR_RX_PIN);
  2032. sensor->setTX(SENSEAIR_TX_PIN);
  2033. add(sensor);
  2034. }
  2035. #endif
  2036. #if SDS011_SUPPORT
  2037. {
  2038. auto* sensor = new SDS011Sensor();
  2039. sensor->setRX(SDS011_RX_PIN);
  2040. sensor->setTX(SDS011_TX_PIN);
  2041. add(sensor);
  2042. }
  2043. #endif
  2044. #if SHT3X_I2C_SUPPORT
  2045. {
  2046. auto* sensor = new SHT3XI2CSensor();
  2047. sensor->setAddress(SHT3X_I2C_ADDRESS);
  2048. add(sensor);
  2049. }
  2050. #endif
  2051. #if SI7021_SUPPORT
  2052. {
  2053. auto* sensor = new SI7021Sensor();
  2054. sensor->setAddress(SI7021_ADDRESS);
  2055. add(sensor);
  2056. }
  2057. #endif
  2058. #if SM300D2_SUPPORT
  2059. {
  2060. auto* sensor = new SM300D2Sensor();
  2061. sensor->setRX(SM300D2_RX_PIN);
  2062. add(sensor);
  2063. }
  2064. #endif
  2065. #if T6613_SUPPORT
  2066. {
  2067. auto* sensor = new T6613Sensor();
  2068. sensor->setRX(T6613_RX_PIN);
  2069. sensor->setTX(T6613_TX_PIN);
  2070. add(sensor);
  2071. }
  2072. #endif
  2073. #if TMP3X_SUPPORT
  2074. {
  2075. auto* sensor = new TMP3XSensor();
  2076. sensor->setType(TMP3X_TYPE);
  2077. add(sensor);
  2078. }
  2079. #endif
  2080. #if V9261F_SUPPORT
  2081. {
  2082. auto* sensor = new V9261FSensor();
  2083. sensor->setRX(V9261F_PIN);
  2084. sensor->setInverted(V9261F_PIN_INVERSE);
  2085. add(sensor);
  2086. }
  2087. #endif
  2088. #if MAX6675_SUPPORT
  2089. {
  2090. auto* sensor = new MAX6675Sensor();
  2091. sensor->setCS(MAX6675_CS_PIN);
  2092. sensor->setSO(MAX6675_SO_PIN);
  2093. sensor->setSCK(MAX6675_SCK_PIN);
  2094. add(sensor);
  2095. }
  2096. #endif
  2097. #if VEML6075_SUPPORT
  2098. {
  2099. auto* sensor = new VEML6075Sensor();
  2100. sensor->setIntegrationTime(VEML6075_INTEGRATION_TIME);
  2101. sensor->setDynamicMode(VEML6075_DYNAMIC_MODE);
  2102. add(sensor);
  2103. }
  2104. #endif
  2105. #if VL53L1X_SUPPORT
  2106. {
  2107. auto* sensor = new VL53L1XSensor();
  2108. sensor->setInterMeasurementPeriod(
  2109. VL53L1XSensor::InterMeasurementPeriod{VL53L1X_INTER_MEASUREMENT_PERIOD});
  2110. sensor->setMeasurementTimingBudget(
  2111. VL53L1XSensor::MeasurementTimingBudget{VL53L1X_MEASUREMENT_TIMING_BUDGET});
  2112. sensor->setDistanceMode(VL53L1X_DISTANCE_MODE);
  2113. add(sensor);
  2114. }
  2115. #endif
  2116. #if EZOPH_SUPPORT
  2117. {
  2118. auto* sensor = new EZOPHSensor();
  2119. sensor->setRX(EZOPH_RX_PIN);
  2120. sensor->setTX(EZOPH_TX_PIN);
  2121. add(sensor);
  2122. }
  2123. #endif
  2124. #if ADE7953_SUPPORT
  2125. {
  2126. auto* sensor = new ADE7953Sensor();
  2127. sensor->setAddress(ADE7953_ADDRESS);
  2128. add(sensor);
  2129. }
  2130. #endif
  2131. #if SI1145_SUPPORT
  2132. {
  2133. auto* sensor = new SI1145Sensor();
  2134. sensor->setAddress(SI1145_ADDRESS);
  2135. add(sensor);
  2136. }
  2137. #endif
  2138. #if HDC1080_SUPPORT
  2139. {
  2140. auto* sensor = new HDC1080Sensor();
  2141. sensor->setAddress(HDC1080_ADDRESS);
  2142. add(sensor);
  2143. }
  2144. #endif
  2145. #if PZEM004TV30_SUPPORT
  2146. {
  2147. auto rx = getSetting("pzemv30RX", PZEM004TV30Sensor::RxPin);
  2148. auto tx = getSetting("pzemv30TX", PZEM004TV30Sensor::TxPin);
  2149. //TODO: getSetting("pzemv30*Cfg", (SW)SERIAL_8N1); ?
  2150. //TODO: getSetting("serial*Cfg", ...); and attach index of the port ?
  2151. //TODO: more than one sensor on port, like the v1
  2152. PZEM004TV30Sensor::PortPtr port;
  2153. if (getSetting("pzemSoft", PZEM004TV30Sensor::useSoftwareSerial())) {
  2154. port = PZEM004TV30Sensor::makeSoftwarePort(rx, tx);
  2155. } else {
  2156. port = PZEM004TV30Sensor::makeHardwarePort(
  2157. PZEM004TV30Sensor::defaultHardwarePort(), rx, tx);
  2158. }
  2159. if (port) {
  2160. port->begin(PZEM004TV30Sensor::Baudrate);
  2161. auto* sensor = PZEM004TV30Sensor::make(std::move(port),
  2162. getSetting("pzemv30Addr", PZEM004TV30Sensor::DefaultAddress),
  2163. getSetting("pzemv30ReadTimeout", PZEM004TV30Sensor::DefaultReadTimeout));
  2164. sensor->setDebug(
  2165. getSetting("pzemv30Debug", PZEM004TV30Sensor::DefaultDebug));
  2166. add(sensor);
  2167. }
  2168. }
  2169. #endif
  2170. }
  2171. namespace units {
  2172. namespace {
  2173. struct Range {
  2174. Range() = default;
  2175. template <size_t Size>
  2176. explicit Range(const Unit (&units)[Size]) :
  2177. _begin(std::begin(units)),
  2178. _end(std::end(units))
  2179. {}
  2180. template <size_t Size>
  2181. Range& operator=(const Unit (&units)[Size]) {
  2182. _begin = std::begin(units);
  2183. _end = std::end(units);
  2184. return *this;
  2185. }
  2186. const Unit* begin() const {
  2187. return _begin;
  2188. }
  2189. const Unit* end() const {
  2190. return _end;
  2191. }
  2192. private:
  2193. const Unit* _begin { nullptr };
  2194. const Unit* _end { nullptr };
  2195. };
  2196. Range range(unsigned char type) {
  2197. #define MAGNITUDE_UNITS_RANGE(...)\
  2198. static const Unit units[] PROGMEM {\
  2199. __VA_ARGS__\
  2200. };\
  2201. \
  2202. out = units
  2203. Range out;
  2204. switch (type) {
  2205. case MAGNITUDE_TEMPERATURE: {
  2206. MAGNITUDE_UNITS_RANGE(
  2207. Unit::Celcius,
  2208. Unit::Farenheit,
  2209. Unit::Kelvin
  2210. );
  2211. break;
  2212. }
  2213. case MAGNITUDE_HUMIDITY:
  2214. case MAGNITUDE_POWER_FACTOR: {
  2215. MAGNITUDE_UNITS_RANGE(
  2216. Unit::Percentage
  2217. );
  2218. break;
  2219. }
  2220. case MAGNITUDE_PRESSURE: {
  2221. MAGNITUDE_UNITS_RANGE(
  2222. Unit::Hectopascal
  2223. );
  2224. break;
  2225. }
  2226. case MAGNITUDE_CURRENT: {
  2227. MAGNITUDE_UNITS_RANGE(
  2228. Unit::Ampere
  2229. );
  2230. break;
  2231. }
  2232. case MAGNITUDE_VOLTAGE: {
  2233. MAGNITUDE_UNITS_RANGE(
  2234. Unit::Volt
  2235. );
  2236. break;
  2237. }
  2238. case MAGNITUDE_POWER_ACTIVE: {
  2239. MAGNITUDE_UNITS_RANGE(
  2240. Unit::Watt,
  2241. Unit::Kilowatt
  2242. );
  2243. break;
  2244. }
  2245. case MAGNITUDE_POWER_APPARENT: {
  2246. MAGNITUDE_UNITS_RANGE(
  2247. Unit::Voltampere,
  2248. Unit::Kilovoltampere
  2249. );
  2250. break;
  2251. }
  2252. case MAGNITUDE_POWER_REACTIVE: {
  2253. MAGNITUDE_UNITS_RANGE(
  2254. Unit::VoltampereReactive,
  2255. Unit::KilovoltampereReactive
  2256. );
  2257. break;
  2258. }
  2259. case MAGNITUDE_ENERGY_DELTA: {
  2260. MAGNITUDE_UNITS_RANGE(
  2261. Unit::Joule
  2262. );
  2263. break;
  2264. }
  2265. case MAGNITUDE_ENERGY: {
  2266. MAGNITUDE_UNITS_RANGE(
  2267. Unit::Joule,
  2268. Unit::KilowattHour
  2269. );
  2270. break;
  2271. }
  2272. case MAGNITUDE_PM1DOT0:
  2273. case MAGNITUDE_PM2DOT5:
  2274. case MAGNITUDE_PM10:
  2275. case MAGNITUDE_TVOC:
  2276. case MAGNITUDE_CH2O: {
  2277. MAGNITUDE_UNITS_RANGE(
  2278. Unit::MicrogrammPerCubicMeter,
  2279. Unit::MilligrammPerCubicMeter
  2280. );
  2281. break;
  2282. }
  2283. case MAGNITUDE_CO:
  2284. case MAGNITUDE_CO2:
  2285. case MAGNITUDE_NO2:
  2286. case MAGNITUDE_VOC: {
  2287. MAGNITUDE_UNITS_RANGE(
  2288. Unit::PartsPerMillion
  2289. );
  2290. break;
  2291. }
  2292. case MAGNITUDE_LUX: {
  2293. MAGNITUDE_UNITS_RANGE(
  2294. Unit::Lux
  2295. );
  2296. break;
  2297. }
  2298. case MAGNITUDE_RESISTANCE: {
  2299. MAGNITUDE_UNITS_RANGE(
  2300. Unit::Ohm
  2301. );
  2302. break;
  2303. }
  2304. case MAGNITUDE_HCHO: {
  2305. MAGNITUDE_UNITS_RANGE(
  2306. Unit::MilligrammPerCubicMeter
  2307. );
  2308. break;
  2309. }
  2310. case MAGNITUDE_GEIGER_CPM: {
  2311. MAGNITUDE_UNITS_RANGE(
  2312. Unit::CountsPerMinute
  2313. );
  2314. break;
  2315. }
  2316. case MAGNITUDE_GEIGER_SIEVERT: {
  2317. MAGNITUDE_UNITS_RANGE(
  2318. Unit::MicrosievertPerHour
  2319. );
  2320. break;
  2321. }
  2322. case MAGNITUDE_DISTANCE: {
  2323. MAGNITUDE_UNITS_RANGE(
  2324. Unit::Meter
  2325. );
  2326. break;
  2327. }
  2328. case MAGNITUDE_FREQUENCY: {
  2329. MAGNITUDE_UNITS_RANGE(
  2330. Unit::Hertz
  2331. );
  2332. break;
  2333. }
  2334. case MAGNITUDE_PH: {
  2335. MAGNITUDE_UNITS_RANGE(
  2336. Unit::Ph
  2337. );
  2338. break;
  2339. }
  2340. }
  2341. #undef MAGNITUDE_UNITS_RANGE
  2342. return out;
  2343. }
  2344. bool supported(const Magnitude& magnitude, Unit unit) {
  2345. const auto range = units::range(magnitude.type);
  2346. return std::any_of(range.begin(), range.end(), [&](sensor::Unit supported) {
  2347. return (unit == supported);
  2348. });
  2349. }
  2350. sensor::Unit filter(const Magnitude& magnitude, Unit unit) {
  2351. return supported(magnitude, unit) ? unit : magnitude.units;
  2352. }
  2353. } // namespace
  2354. } // namespace units
  2355. // -----------------------------------------------------------------------------
  2356. // Energy persistence
  2357. // -----------------------------------------------------------------------------
  2358. namespace energy {
  2359. namespace {
  2360. struct Persist {
  2361. Persist(size_t index, Energy energy) :
  2362. _index(index),
  2363. _energy(energy)
  2364. {}
  2365. void operator()() const {
  2366. setSetting({F("eneTotal"), _index}, _energy.asString());
  2367. #if NTP_SUPPORT
  2368. if (ntpSynced()) {
  2369. setSetting({F("eneTime"), _index}, ntpDateTime());
  2370. }
  2371. #endif
  2372. }
  2373. private:
  2374. size_t _index;
  2375. Energy _energy;
  2376. };
  2377. struct Tracker {
  2378. using Reference = std::reference_wrapper<const Magnitude>;
  2379. struct Counter {
  2380. Reference magnitude;
  2381. int value;
  2382. };
  2383. using Counters = std::vector<Counter>;
  2384. explicit operator bool() const {
  2385. return _every > 0;
  2386. }
  2387. int every() const {
  2388. return _every;
  2389. }
  2390. void add(Reference magnitude) {
  2391. _count.push_back(Counter{
  2392. .magnitude = magnitude,
  2393. .value = 0
  2394. });
  2395. }
  2396. size_t size() const {
  2397. return _count.size();
  2398. }
  2399. int count(size_t index) const {
  2400. return _count[index].value;
  2401. }
  2402. template <typename Callback>
  2403. void tick(unsigned char index, Callback&& callback) {
  2404. _count[index].value = (_count[index].value + 1) % _every;
  2405. if (_count[index].value == 0) {
  2406. callback();
  2407. }
  2408. }
  2409. void every(int every) {
  2410. _every = every;
  2411. for (auto& count : _count) {
  2412. count.value = 0;
  2413. }
  2414. }
  2415. private:
  2416. Counters _count;
  2417. int _every;
  2418. };
  2419. struct ParseResult {
  2420. ParseResult() = default;
  2421. ParseResult& operator=(sensor::Energy value) {
  2422. _value = value;
  2423. _result = true;
  2424. return *this;
  2425. }
  2426. explicit operator bool() const {
  2427. return _result;
  2428. }
  2429. Energy value() const {
  2430. return _value;
  2431. }
  2432. private:
  2433. bool _result { false };
  2434. Energy _value;
  2435. };
  2436. namespace internal {
  2437. Tracker tracker;
  2438. } // namespace internal
  2439. Energy get_rtcmem(unsigned char index) {
  2440. return Energy {
  2441. Energy::Pair {
  2442. .kwh = KilowattHours(Rtcmem->energy[index].kwh),
  2443. .ws = WattSeconds(Rtcmem->energy[index].ws),
  2444. }};
  2445. }
  2446. void set_rtcmem(unsigned char index, const Energy& source) {
  2447. const auto pair = source.pair();
  2448. Rtcmem->energy[index].kwh = pair.kwh.value;
  2449. Rtcmem->energy[index].ws = pair.ws.value;
  2450. }
  2451. ParseResult convert(StringView value) {
  2452. ParseResult out;
  2453. if (!value.length()) {
  2454. return out;
  2455. }
  2456. const auto begin = value.c_str();
  2457. const auto end = value.c_str() + value.length();
  2458. String kwh_number;
  2459. auto it = begin;
  2460. while (it != end) {
  2461. if (*it == '+') {
  2462. break;
  2463. }
  2464. kwh_number += *it;
  2465. ++it;
  2466. }
  2467. KilowattHours::Type kwh { 0 };
  2468. WattSeconds::Type ws { 0 };
  2469. char* endp { nullptr };
  2470. kwh = strtoul(kwh_number.c_str(), &endp, 10);
  2471. if (!endp || (endp == kwh_number.c_str())) {
  2472. return out;
  2473. }
  2474. if ((it != end) && (*it == '+')) {
  2475. ++it;
  2476. if (it == end) {
  2477. return out;
  2478. }
  2479. String ws_number;
  2480. ws_number.concat(it, (end - it));
  2481. ws = strtoul(ws_number.c_str(), &endp, 10);
  2482. if (!endp || (endp == ws_number.c_str())) {
  2483. return out;
  2484. }
  2485. }
  2486. out = Energy {
  2487. Energy::Pair {
  2488. .kwh = KilowattHours(kwh),
  2489. .ws = WattSeconds(ws),
  2490. }};
  2491. return out;
  2492. }
  2493. Energy get_settings(unsigned char index) {
  2494. using namespace settings;
  2495. const auto current = getSetting(
  2496. keys::get(prefix::get(MAGNITUDE_ENERGY), suffix::Total, index));
  2497. return convert(current).value();
  2498. }
  2499. void set(const Magnitude& magnitude, const Energy& energy) {
  2500. if (isEmon(magnitude.sensor)) {
  2501. auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
  2502. sensor->resetEnergy(magnitude.slot, energy);
  2503. }
  2504. }
  2505. void set(const Magnitude& magnitude, const String& payload) {
  2506. if (!payload.length()) {
  2507. return;
  2508. }
  2509. auto energy = convert(payload);
  2510. if (!energy) {
  2511. return;
  2512. }
  2513. set(magnitude, energy.value());
  2514. }
  2515. void set(const Magnitude& magnitude, const char* payload) {
  2516. if (!payload) {
  2517. return;
  2518. }
  2519. set(magnitude, String(payload));
  2520. }
  2521. Energy get(unsigned char index) {
  2522. Energy result;
  2523. if (rtcmemStatus() && (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy)))) {
  2524. result = get_rtcmem(index);
  2525. } else {
  2526. result = get_settings(index);
  2527. }
  2528. return result;
  2529. }
  2530. void reset(unsigned char index) {
  2531. delSetting({F("eneTotal"), index});
  2532. delSetting({F("eneTime"), index});
  2533. if (index < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) {
  2534. Rtcmem->energy[index].kwh = 0;
  2535. Rtcmem->energy[index].ws = 0;
  2536. }
  2537. }
  2538. int every() {
  2539. return internal::tracker.every();
  2540. }
  2541. void every(int value) {
  2542. internal::tracker.every(value);
  2543. }
  2544. void update(const Magnitude& magnitude, bool persistent) {
  2545. if (!isEmon(magnitude.sensor)) {
  2546. return;
  2547. }
  2548. auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
  2549. const auto energy = sensor->totalEnergy(magnitude.slot);
  2550. // Always save to RTCMEM
  2551. if (magnitude.index_global < (sizeof(Rtcmem->energy) / sizeof(*Rtcmem->energy))) {
  2552. set_rtcmem(magnitude.index_global, energy);
  2553. }
  2554. // Save to EEPROM every '_sensor_save_every' readings
  2555. if (persistent && internal::tracker) {
  2556. internal::tracker.tick(magnitude.index_global,
  2557. Persist{magnitude.index_global, energy});
  2558. }
  2559. }
  2560. void reset() {
  2561. for (auto type : magnitude::traits::ratio_types) {
  2562. for (size_t index = 0; index < Magnitude::counts(type); ++index) {
  2563. delSetting(settings::keys::get(settings::prefix::get(type), settings::suffix::Ratio, index));
  2564. }
  2565. }
  2566. for (auto ptr : sensor::internal::sensors) {
  2567. if (isEmon(ptr)) {
  2568. DEBUG_MSG_P(PSTR("[EMON] Resetting %s\n"), ptr->description().c_str());
  2569. static_cast<BaseEmonSensor*>(ptr.get())->resetRatios();
  2570. }
  2571. }
  2572. }
  2573. double ratioFromValue(const Magnitude& magnitude, double expected) {
  2574. if (!isEmon(magnitude.sensor)) {
  2575. return BaseEmonSensor::DefaultRatio;
  2576. }
  2577. auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
  2578. return sensor->ratioFromValue(magnitude.slot, sensor->value(magnitude.slot), expected);
  2579. }
  2580. void setup(const Magnitude& magnitude) {
  2581. if (!isEmon(magnitude.sensor)) {
  2582. return;
  2583. }
  2584. auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
  2585. sensor->initialEnergy(magnitude.slot, get(magnitude.index_global));
  2586. internal::tracker.add(magnitude);
  2587. DEBUG_MSG_P(PSTR("[ENERGY] Tracking %s/%u for %s\n"),
  2588. magnitude::topic(magnitude).c_str(),
  2589. magnitude.index_global,
  2590. magnitude.sensor->description().c_str());
  2591. }
  2592. } // namespace
  2593. } // namespace energy
  2594. namespace settings {
  2595. namespace query {
  2596. namespace {
  2597. bool check(StringView key) {
  2598. if (key.length() < 3) {
  2599. return false;
  2600. }
  2601. using espurna::settings::query::samePrefix;
  2602. if (samePrefix(key, settings::prefix::Sensor)) {
  2603. return true;
  2604. }
  2605. if (samePrefix(key, settings::prefix::Power)) {
  2606. return true;
  2607. }
  2608. return magnitude::forEachCountedCheck([&](unsigned char type) {
  2609. return samePrefix(key, prefix::get(type));
  2610. });
  2611. }
  2612. String get(StringView key) {
  2613. String out;
  2614. for (auto& magnitude : magnitude::internal::magnitudes) {
  2615. if (magnitude::traits::ratio_supported(magnitude.type)) {
  2616. const auto expected = keys::get(magnitude, suffix::Ratio);
  2617. if (key == expected.value()) {
  2618. out = String(reinterpret_cast<BaseEmonSensor*>(magnitude.sensor.get())->defaultRatio(magnitude.slot));
  2619. break;
  2620. }
  2621. }
  2622. if (magnitude::traits::correction_supported(magnitude.type)) {
  2623. const auto expected = keys::get(magnitude, suffix::Correction);
  2624. if (key == expected.value()) {
  2625. out = String(magnitude.correction);
  2626. break;
  2627. }
  2628. }
  2629. {
  2630. const auto expected = keys::get(magnitude, suffix::Filter);
  2631. if (key == expected.value()) {
  2632. out = espurna::settings::internal::serialize(magnitude.filter_type);
  2633. break;
  2634. }
  2635. }
  2636. {
  2637. const auto expected = keys::get(magnitude, suffix::Units);
  2638. if (key == expected.value()) {
  2639. out = espurna::settings::internal::serialize(magnitude.units);
  2640. break;
  2641. }
  2642. }
  2643. }
  2644. return out;
  2645. }
  2646. void setup() {
  2647. settingsRegisterQueryHandler({
  2648. .check = check,
  2649. .get = get,
  2650. });
  2651. }
  2652. } // namespace
  2653. } // namespace query
  2654. void migrate(int version) {
  2655. auto firstKey = [](unsigned char type, StringView suffix) {
  2656. return keys::get(prefix::get(type), suffix, 0).value();
  2657. };
  2658. // Some keys from older versions were longer
  2659. if (version < 3) {
  2660. moveSetting(F("powerUnits"), F("pwrUnits"));
  2661. moveSetting(F("energyUnits"), F("eneUnits"));
  2662. }
  2663. // Energy is now indexed (based on magnitude.index_global)
  2664. // Also update PZEM004T energy total across multiple devices
  2665. if (version < 5) {
  2666. moveSetting(F("eneTotal"), firstKey(MAGNITUDE_ENERGY, suffix::Total));
  2667. moveSettings(F("pzEneTotal"), prefix::get(MAGNITUDE_ENERGY).toString() + FPSTR(suffix::Total));
  2668. }
  2669. // Unit ID is no longer shared, drop when equal to Min_ or None
  2670. if (version < 5) {
  2671. delSetting(F("pwrUnits"));
  2672. delSetting(F("eneUnits"));
  2673. delSetting(F("tmpUnits"));
  2674. }
  2675. // Generic pwr settings now have type-specific prefixes
  2676. // (index 0, assuming there's only one emon sensor)
  2677. if (version < 7) {
  2678. moveSetting(F("pwrVoltage"), firstKey(MAGNITUDE_VOLTAGE, suffix::Mains));
  2679. moveSetting(F("pwrRatioC"), firstKey(MAGNITUDE_CURRENT, suffix::Ratio));
  2680. moveSetting(F("pwrRatioV"), firstKey(MAGNITUDE_VOLTAGE, suffix::Ratio));
  2681. moveSetting(F("pwrRatioP"), firstKey(MAGNITUDE_POWER_ACTIVE, suffix::Ratio));
  2682. moveSetting(F("pwrRatioE"), firstKey(MAGNITUDE_ENERGY, suffix::Ratio));
  2683. }
  2684. #if HLW8012_SUPPORT
  2685. if (version < 9) {
  2686. moveSetting(F("snsHlw8012SelGPIO"), F("hlw8012SEL"));
  2687. moveSetting(F("snsHlw8012CfGPIO"), F("hlw8012CF"));
  2688. moveSetting(F("snsHlw8012Cf1GPIO"), F("hlw8012CF1"));
  2689. }
  2690. #endif
  2691. if (version < 11) {
  2692. moveSetting(F("apiRealTime"), FPSTR(keys::RealTimeValues));
  2693. moveSetting(F("tmpMinDelta"), firstKey(MAGNITUDE_TEMPERATURE, suffix::MinDelta));
  2694. moveSetting(F("humMinDelta"), firstKey(MAGNITUDE_HUMIDITY, suffix::MinDelta));
  2695. moveSetting(F("eneMaxDelta"), firstKey(MAGNITUDE_ENERGY, suffix::MaxDelta));
  2696. }
  2697. }
  2698. } // namespace settings
  2699. // -----------------------------------------------------------------------------
  2700. // WebUI value display and actions
  2701. // -----------------------------------------------------------------------------
  2702. #if WEB_SUPPORT
  2703. namespace web {
  2704. namespace {
  2705. bool onKeyCheck(StringView key, const JsonVariant&) {
  2706. return settings::query::check(key);
  2707. }
  2708. // Entries related to things reported by the module.
  2709. // - types of magnitudes that are available and the string values associated with them
  2710. // - error types and stringified versions of them
  2711. // - units are the value types of the magnitude
  2712. // TODO: magnitude types have some common keys and some specific ones, only implemented for the type
  2713. // e.g. voltMains is specific to the MAGNITUDE_VOLTAGE but *only* in analog mode, or eneRatio specific to MAGNITUDE_ENERGY
  2714. // but, notice that the sensor will probably be used to 'get' certain properties, to generate certain keys list
  2715. void types(JsonObject& root) {
  2716. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("types")};
  2717. payload(STRING_VIEW("values"), {MAGNITUDE_NONE + 1, MAGNITUDE_MAX},
  2718. [](size_t type) {
  2719. return Magnitude::counts(type) > 0;
  2720. },
  2721. {{STRING_VIEW("type"), [](JsonArray& out, size_t index) {
  2722. out.add(index);
  2723. }},
  2724. {STRING_VIEW("prefix"), [](JsonArray& out, size_t index) {
  2725. out.add(FPSTR(settings::prefix::get(index).c_str()));
  2726. }},
  2727. {STRING_VIEW("name"), [](JsonArray& out, size_t index) {
  2728. out.add(sensor::magnitude::name(index));
  2729. }}
  2730. });
  2731. }
  2732. void errors(JsonObject& root) {
  2733. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("errors")};
  2734. payload(STRING_VIEW("values"), SENSOR_ERROR_MAX,
  2735. {{STRING_VIEW("type"), [](JsonArray& out, size_t index) {
  2736. out.add(index);
  2737. }},
  2738. {STRING_VIEW("name"), [](JsonArray& out, size_t index) {
  2739. out.add(error(index));
  2740. }}
  2741. });
  2742. }
  2743. void units(JsonObject& root) {
  2744. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("units")};
  2745. payload(STRING_VIEW("values"), magnitude::internal::magnitudes.size(),
  2746. {{STRING_VIEW("supported"), [](JsonArray& out, size_t index) {
  2747. JsonArray& units = out.createNestedArray();
  2748. const auto range = units::range(magnitude::get(index).type);
  2749. for (auto it = range.begin(); it != range.end(); ++it) {
  2750. JsonArray& unit = units.createNestedArray();
  2751. unit.add(static_cast<int>(*it)); // raw id
  2752. unit.add(magnitude::units(*it)); // as string
  2753. }
  2754. }}
  2755. });
  2756. }
  2757. void initial(JsonObject& root) {
  2758. if (!magnitude::count()) {
  2759. return;
  2760. }
  2761. JsonObject& container = root.createNestedObject(F("magnitudes-init"));
  2762. types(container);
  2763. errors(container);
  2764. units(container);
  2765. }
  2766. void list(JsonObject& root) {
  2767. if (!magnitude::count()) {
  2768. return;
  2769. }
  2770. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("magnitudes-list")};
  2771. payload(STRING_VIEW("values"), magnitude::count(),
  2772. {{STRING_VIEW("index_global"), [](JsonArray& out, size_t index) {
  2773. out.add(magnitude::get(index).index_global);
  2774. }},
  2775. {STRING_VIEW("type"), [](JsonArray& out, size_t index) {
  2776. out.add(magnitude::get(index).type);
  2777. }},
  2778. {STRING_VIEW("description"), [](JsonArray& out, size_t index) {
  2779. out.add(magnitude::description(magnitude::get(index)));
  2780. }},
  2781. {STRING_VIEW("units"), [](JsonArray& out, size_t index) {
  2782. out.add(static_cast<int>(magnitude::get(index).units));
  2783. }}
  2784. });
  2785. }
  2786. void settings(JsonObject& root) {
  2787. if (!magnitude::count()) {
  2788. return;
  2789. }
  2790. // XXX: inject 'null' in the output. need this for optional fields, since the current
  2791. // version of serializer only does this for char ptr and even makes NaN serialized as
  2792. // NaN, instead of more commonly used null (but, expect this to be fixed after switching to v6+)
  2793. static const char* const NullSymbol { nullptr };
  2794. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("magnitudes-settings")};
  2795. payload(STRING_VIEW("values"), magnitude::count(),
  2796. {{settings::suffix::Correction, [](JsonArray& out, size_t index) {
  2797. const auto& magnitude = magnitude::get(index);
  2798. if (magnitude::traits::correction_supported(magnitude.type)) {
  2799. out.add(magnitude.correction);
  2800. } else {
  2801. out.add(NullSymbol);
  2802. }
  2803. }},
  2804. {settings::suffix::Ratio, [](JsonArray& out, size_t index) {
  2805. const auto& magnitude = magnitude::get(index);
  2806. if (magnitude::traits::ratio_supported(magnitude.type)) {
  2807. out.add(static_cast<BaseEmonSensor*>(magnitude.sensor.get())->getRatio(magnitude.slot));
  2808. } else {
  2809. out.add(NullSymbol);
  2810. }
  2811. }},
  2812. {settings::suffix::ZeroThreshold, [](JsonArray& out, size_t index) {
  2813. const auto threshold = magnitude::get(index).zero_threshold;
  2814. if (!std::isnan(threshold)) {
  2815. out.add(threshold);
  2816. } else {
  2817. out.add(NullSymbol);
  2818. }
  2819. }},
  2820. {settings::suffix::MinDelta, [](JsonArray& out, size_t index) {
  2821. out.add(magnitude::get(index).min_delta);
  2822. }},
  2823. {settings::suffix::MaxDelta, [](JsonArray& out, size_t index) {
  2824. out.add(magnitude::get(index).max_delta);
  2825. }}
  2826. });
  2827. root[FPSTR(settings::keys::RealTimeValues)] = realTimeValues();
  2828. root[FPSTR(settings::keys::ReadInterval)] = readInterval().count();
  2829. root[FPSTR(settings::keys::InitInterval)] = initInterval().count();
  2830. root[FPSTR(settings::keys::ReportEvery)] = reportEvery();
  2831. root[FPSTR(settings::keys::SaveEvery)] = energy::internal::tracker.every();
  2832. }
  2833. void energy(JsonObject& root) {
  2834. #if NTP_SUPPORT
  2835. if (!energy::internal::tracker || !energy::internal::tracker.size()) {
  2836. return;
  2837. }
  2838. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("energy")};
  2839. payload(STRING_VIEW("values"), espurna::settings::Iota(magnitude::count()),
  2840. [](size_t index) {
  2841. return magnitude::get(index).type == MAGNITUDE_ENERGY;
  2842. },
  2843. {{STRING_VIEW("id"), [](JsonArray& out, size_t index) {
  2844. out.add(index);
  2845. }},
  2846. {STRING_VIEW("saved"), [](JsonArray& out, size_t index) {
  2847. if (energy::internal::tracker) {
  2848. out.add(getSetting({F("eneTime"), magnitude::get(index).index_global}, F("(unknown)")));
  2849. } else {
  2850. out.add("");
  2851. }
  2852. }}
  2853. });
  2854. #endif
  2855. }
  2856. void magnitudes(JsonObject& root) {
  2857. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("magnitudes")};
  2858. payload(STRING_VIEW("values"), magnitude::count(), {
  2859. {STRING_VIEW("value"), [](JsonArray& out, size_t index) {
  2860. const auto& magnitude = magnitude::get(index);
  2861. out.add(magnitude::format(magnitude,
  2862. magnitude::process(magnitude, magnitude.last)));
  2863. }},
  2864. {STRING_VIEW("units"), [](JsonArray& out, size_t index) {
  2865. out.add(static_cast<int>(magnitude::get(index).units));
  2866. }},
  2867. {STRING_VIEW("error"), [](JsonArray& out, size_t index) {
  2868. out.add(magnitude::get(index).sensor->error());
  2869. }},
  2870. });
  2871. }
  2872. void onData(JsonObject& root) {
  2873. if (magnitude::count()) {
  2874. magnitudes(root);
  2875. energy(root);
  2876. }
  2877. }
  2878. void onAction(uint32_t client_id, const char* action, JsonObject& data) {
  2879. if (STRING_VIEW("emon-expected") == action) {
  2880. auto id = data["id"].as<size_t>();
  2881. if (id < magnitude::count()) {
  2882. auto expected = data["expected"].as<float>();
  2883. wsPost(client_id, [id, expected](JsonObject& root) {
  2884. const auto& magnitude = magnitude::get(id);
  2885. String key { F("result:") };
  2886. key += settings::keys::get(
  2887. magnitude, settings::suffix::Ratio).value();
  2888. root[key] = energy::ratioFromValue(magnitude, expected);
  2889. });
  2890. }
  2891. return;
  2892. }
  2893. if (STRING_VIEW("emon-reset-ratios") == action) {
  2894. energy::reset();
  2895. return;
  2896. }
  2897. }
  2898. void onVisible(JsonObject& root) {
  2899. wsPayloadModule(root, PSTR("sns"));
  2900. for (auto sensor : internal::sensors) {
  2901. if (isEmon(sensor)) {
  2902. wsPayloadModule(root, PSTR("emon"));
  2903. }
  2904. if (isAnalog(sensor)) {
  2905. wsPayloadModule(root, PSTR("analog"));
  2906. }
  2907. switch (sensor->id()) {
  2908. #if HLW8012_SUPPORT
  2909. case SENSOR_HLW8012_ID:
  2910. wsPayloadModule(root, PSTR("hlw"));
  2911. break;
  2912. #endif
  2913. #if CSE7766_SUPPORT
  2914. case SENSOR_CSE7766_ID:
  2915. wsPayloadModule(root, PSTR("cse"));
  2916. break;
  2917. #endif
  2918. #if PZEM004T_SUPPORT || PZEM004TV30_SUPPORT
  2919. case SENSOR_PZEM004T_ID:
  2920. case SENSOR_PZEM004TV30_ID:
  2921. wsPayloadModule(root, PSTR("pzem"));
  2922. break;
  2923. #endif
  2924. #if PULSEMETER_SUPPORT
  2925. case SENSOR_PULSEMETER_ID:
  2926. wsPayloadModule(root, PSTR("pm"));
  2927. break;
  2928. #endif
  2929. }
  2930. }
  2931. }
  2932. void module(JsonObject& root, const char* prefix, SensorWebSocketMagnitudesCallback callback) {
  2933. espurna::web::ws::EnumerablePayload payload{root, STRING_VIEW("magnitudes-module")};
  2934. auto& container = payload.root();
  2935. container[F("prefix")] = FPSTR(prefix);
  2936. payload(STRING_VIEW("values"), magnitude::count(),
  2937. {{STRING_VIEW("type"), [](JsonArray& out, size_t index) {
  2938. out.add(magnitude::get(index).type);
  2939. }},
  2940. {STRING_VIEW("index_global"), [](JsonArray& out, size_t index) {
  2941. out.add(magnitude::get(index).index_global);
  2942. }},
  2943. {STRING_VIEW("index_module"), callback}
  2944. });
  2945. }
  2946. void setup() {
  2947. wsRegister()
  2948. .onConnected(initial)
  2949. .onConnected(list)
  2950. .onConnected(settings)
  2951. .onVisible(onVisible)
  2952. .onData(onData)
  2953. .onAction(onAction)
  2954. .onKeyCheck(onKeyCheck);
  2955. }
  2956. } // namespace
  2957. } // namespace web
  2958. #endif
  2959. bool tryParseIndex(const char* p, unsigned char type, unsigned char& output) {
  2960. char* endp { nullptr };
  2961. const unsigned long result { strtoul(p, &endp, 10) };
  2962. if ((endp == p) || (*endp != '\0') || (result >= magnitude::count(type))) {
  2963. DEBUG_MSG_P(PSTR("[SENSOR] Invalid magnitude ID (%s)\n"), p);
  2964. return false;
  2965. }
  2966. output = result;
  2967. return true;
  2968. }
  2969. #if API_SUPPORT
  2970. namespace api {
  2971. namespace {
  2972. template <typename T>
  2973. bool tryHandle(ApiRequest& request, unsigned char type, T&& callback) {
  2974. unsigned char index { 0u };
  2975. if (request.wildcards()) {
  2976. auto index_param = request.wildcard(0);
  2977. if (!tryParseIndex(index_param.c_str(), type, index)) {
  2978. return false;
  2979. }
  2980. }
  2981. const auto* magnitude = magnitude::find(type, index);
  2982. if (magnitude) {
  2983. callback(*magnitude);
  2984. return true;
  2985. }
  2986. return false;
  2987. }
  2988. void setup() {
  2989. apiRegister(F("magnitudes"),
  2990. [](ApiRequest&, JsonObject& root) {
  2991. JsonArray& magnitudes = root.createNestedArray("magnitudes");
  2992. for (auto& magnitude : magnitude::internal::magnitudes) {
  2993. JsonArray& data = magnitudes.createNestedArray();
  2994. data.add(sensor::magnitude::topicWithIndex(magnitude));
  2995. data.add(magnitude.last);
  2996. data.add(magnitude.reported);
  2997. }
  2998. return true;
  2999. },
  3000. nullptr
  3001. );
  3002. magnitude::forEachCounted([](unsigned char type) {
  3003. auto pattern = magnitude::topic(type);
  3004. if (sensor::build::useIndex() || (magnitude::count(type) > 1)) {
  3005. pattern += F("/+");
  3006. }
  3007. ApiBasicHandler get = [type](ApiRequest& request) {
  3008. return tryHandle(request, type,
  3009. [&](const Magnitude& magnitude) {
  3010. request.send(magnitude::format(magnitude,
  3011. realTimeValues() ? magnitude.last : magnitude.reported));
  3012. return true;
  3013. });
  3014. };
  3015. ApiBasicHandler put;
  3016. if (type == MAGNITUDE_ENERGY) {
  3017. put = [](ApiRequest& request) {
  3018. return tryHandle(request, MAGNITUDE_ENERGY,
  3019. [&](const Magnitude& magnitude) {
  3020. energy::set(magnitude, request.param(F("value")));
  3021. });
  3022. };
  3023. }
  3024. apiRegister(pattern, std::move(get), std::move(put));
  3025. });
  3026. }
  3027. } // namespace
  3028. } // namespace api
  3029. #endif
  3030. #if MQTT_SUPPORT
  3031. namespace mqtt {
  3032. namespace {
  3033. void callback(unsigned int type, const char* topic, char* payload) {
  3034. if (!magnitude::count(MAGNITUDE_ENERGY)) {
  3035. return;
  3036. }
  3037. static const auto base = magnitude::topic(MAGNITUDE_ENERGY);
  3038. switch (type) {
  3039. case MQTT_MESSAGE_EVENT:
  3040. {
  3041. String tail = mqttMagnitude(topic);
  3042. if (!tail.startsWith(base)) {
  3043. break;
  3044. }
  3045. for (auto ptr = tail.c_str(); ptr != tail.end(); ++ptr) {
  3046. if (*ptr != '/') {
  3047. continue;
  3048. }
  3049. ++ptr;
  3050. if (ptr == tail.end()) {
  3051. break;
  3052. }
  3053. unsigned char index;
  3054. if (!tryParseIndex(ptr, MAGNITUDE_ENERGY, index)) {
  3055. break;
  3056. }
  3057. const auto* magnitude = magnitude::find(MAGNITUDE_ENERGY, index);
  3058. if (magnitude) {
  3059. energy::set(*magnitude, static_cast<const char*>(payload));
  3060. }
  3061. }
  3062. break;
  3063. }
  3064. case MQTT_CONNECT_EVENT:
  3065. {
  3066. mqttSubscribe((base + F("/+")).c_str());
  3067. break;
  3068. }
  3069. }
  3070. }
  3071. void setup() {
  3072. ::mqttRegister(callback);
  3073. }
  3074. } // namespace
  3075. } // namespace mqtt
  3076. #endif
  3077. #if TERMINAL_SUPPORT
  3078. namespace terminal {
  3079. namespace {
  3080. namespace commands {
  3081. alignas(4) static constexpr char Magnitudes[] PROGMEM = "MAGNITUDES";
  3082. void magnitudes(::terminal::CommandContext&& ctx) {
  3083. if (!magnitude::count()) {
  3084. terminalError(ctx, F("No magnitudes"));
  3085. return;
  3086. }
  3087. size_t index = 0;
  3088. for (const auto& magnitude : magnitude::internal::magnitudes) {
  3089. ctx.output.printf_P(PSTR("%2zu * %s @ %s (read:%s reported:%s units:%s)\n"),
  3090. index++, magnitude::topicWithIndex(magnitude).c_str(),
  3091. magnitude::description(magnitude).c_str(),
  3092. magnitude::format(magnitude, magnitude.last).c_str(),
  3093. magnitude::format(magnitude, magnitude.reported).c_str(),
  3094. magnitude::units(magnitude).c_str());
  3095. }
  3096. terminalOK(ctx);
  3097. }
  3098. alignas(4) static constexpr char Expected[] PROGMEM = "EXPECTED";
  3099. void expected(::terminal::CommandContext&& ctx) {
  3100. if (ctx.argv.size() == 3) {
  3101. const auto id = espurna::settings::internal::convert<size_t>(ctx.argv[1]);
  3102. if (id < magnitude::count()) {
  3103. const auto& magnitude = magnitude::get(id);
  3104. const auto result = energy::ratioFromValue(
  3105. magnitude, espurna::settings::internal::convert<double>(ctx.argv[2]));
  3106. const auto key = settings::keys::get(
  3107. magnitude, settings::suffix::Ratio);
  3108. ctx.output.printf("%s => %s\n", key.c_str(), String(result).c_str());
  3109. terminalOK(ctx);
  3110. return;
  3111. }
  3112. terminalError(ctx, F("Invalid magnitude ID"));
  3113. return;
  3114. }
  3115. terminalError(ctx, F("EXPECTED <ID> <VALUE>"));
  3116. }
  3117. alignas(4) static constexpr char ResetRatios[] PROGMEM = "RESET.RATIOS";
  3118. void reset_ratios(::terminal::CommandContext&& ctx) {
  3119. energy::reset();
  3120. terminalOK(ctx);
  3121. }
  3122. alignas(4) static constexpr char Energy[] PROGMEM = "ENERGY";
  3123. void energy(::terminal::CommandContext&& ctx) {
  3124. using IndexType = decltype(Magnitude::index_global);
  3125. if (ctx.argv.size() < 2) {
  3126. terminalError(ctx, F("ENERGY <ID> [<VALUE>]"));
  3127. return;
  3128. }
  3129. const auto index = espurna::settings::internal::convert<IndexType>(ctx.argv[1]);
  3130. const auto* magnitude = magnitude::find(MAGNITUDE_ENERGY, index);
  3131. if (!magnitude) {
  3132. terminalError(ctx, F("Invalid magnitude ID"));
  3133. return;
  3134. }
  3135. if (ctx.argv.size() == 2) {
  3136. ctx.output.printf_P(PSTR("%s => %s (%s)\n"),
  3137. magnitude::topicWithIndex(*magnitude).c_str(),
  3138. magnitude::format(*magnitude, magnitude->reported).c_str(),
  3139. magnitude::units(*magnitude).c_str());
  3140. terminalOK(ctx);
  3141. return;
  3142. }
  3143. if (ctx.argv.size() == 3) {
  3144. const auto energy = energy::convert(ctx.argv[2]);
  3145. if (!energy) {
  3146. terminalError(ctx, F("Invalid energy string"));
  3147. return;
  3148. }
  3149. energy::set(*magnitude, energy.value());
  3150. }
  3151. }
  3152. static constexpr ::terminal::Command List[] PROGMEM {
  3153. {Magnitudes, commands::magnitudes},
  3154. {Expected, commands::expected},
  3155. {ResetRatios, commands::reset_ratios},
  3156. {Energy, commands::energy},
  3157. };
  3158. } // namespace commands
  3159. void setup() {
  3160. espurna::terminal::add(commands::List);
  3161. }
  3162. } // namespace
  3163. } // namespace terminal
  3164. #endif
  3165. // -----------------------------------------------------------------------------
  3166. // Sensor initialization
  3167. // -----------------------------------------------------------------------------
  3168. void init() {
  3169. internal::ready = true;
  3170. for (auto sensor : internal::sensors) {
  3171. // Do not process an already initialized sensor
  3172. if (sensor->ready()) {
  3173. continue;
  3174. }
  3175. // Force sensor to reload config
  3176. DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"),
  3177. sensor->description().c_str());
  3178. sensor->begin();
  3179. if (!sensor->ready()) {
  3180. const auto error = sensor->error();
  3181. if (error != SENSOR_ERROR_OK) {
  3182. DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %s (%hhu)\n"),
  3183. sensor::error(error).c_str(), error);
  3184. }
  3185. internal::ready = false;
  3186. break;
  3187. }
  3188. const auto slots = sensor->count();
  3189. for (auto slot = 0; slot < slots; ++slot) {
  3190. magnitude::add(sensor, slot, sensor->type(slot));
  3191. }
  3192. }
  3193. // Energy tracking is implemented by looking at the specific magnitude & it's index at read time
  3194. // TODO: shuffle some functions around so that debug can be in the init func instead and still be inline?
  3195. for (auto& magnitude : magnitude::internal::magnitudes) {
  3196. if (MAGNITUDE_ENERGY == magnitude.type) {
  3197. energy::setup(magnitude);
  3198. }
  3199. }
  3200. if (internal::ready) {
  3201. DEBUG_MSG_P(PSTR("[SENSOR] Finished initialization for %zu sensor(s) and %zu magnitude(s)\n"),
  3202. sensor::count(), magnitude::count());
  3203. }
  3204. }
  3205. void loop() {
  3206. // Continiously repeat initialization if there are still some un-initialized sensors after setup()
  3207. using TimeSource = espurna::time::CoreClock;
  3208. static auto last_init = TimeSource::now();
  3209. auto timestamp = TimeSource::now();
  3210. if (!internal::ready && (timestamp - last_init > initInterval())) {
  3211. last_init = timestamp;
  3212. sensor::init();
  3213. }
  3214. if (!magnitude::internal::magnitudes.size()) {
  3215. return;
  3216. }
  3217. static auto last_update = TimeSource::now();
  3218. static size_t report_count { 0 };
  3219. sensor::tick();
  3220. if (timestamp - last_update > readInterval()) {
  3221. last_update = timestamp;
  3222. report_count = (report_count + 1) % reportEvery();
  3223. sensor::ReadValue value {
  3224. .raw = 0.0, // as the sensor returns it
  3225. .processed = 0.0, // after applying units and decimals
  3226. .filtered = 0.0 // after applying filters, units and decimals
  3227. };
  3228. // Pre-read hook, called every reading
  3229. sensor::pre();
  3230. // XXX: Filter out certain magnitude types when relay is turned OFF
  3231. #if RELAY_SUPPORT && SENSOR_POWER_CHECK_STATUS
  3232. const bool relay_off = (relayCount() == 1) && (relayStatus(0) == 0);
  3233. #endif
  3234. for (size_t index = 0; index < magnitude::count(); ++index) {
  3235. auto& magnitude = magnitude::get(index);
  3236. if (!magnitude.sensor->status()) {
  3237. continue;
  3238. }
  3239. // -------------------------------------------------------------
  3240. // RAW value, returned from the sensor
  3241. // -------------------------------------------------------------
  3242. value.raw = magnitude.sensor->value(magnitude.slot);
  3243. // But, completely remove spurious values if relay is OFF
  3244. #if RELAY_SUPPORT && SENSOR_POWER_CHECK_STATUS
  3245. switch (magnitude.type) {
  3246. case MAGNITUDE_POWER_ACTIVE:
  3247. case MAGNITUDE_POWER_REACTIVE:
  3248. case MAGNITUDE_POWER_APPARENT:
  3249. case MAGNITUDE_POWER_FACTOR:
  3250. case MAGNITUDE_CURRENT:
  3251. case MAGNITUDE_ENERGY_DELTA:
  3252. if (relay_off) {
  3253. value.raw = 0.0;
  3254. }
  3255. break;
  3256. default:
  3257. break;
  3258. }
  3259. #endif
  3260. // We also check that value is above a certain threshold
  3261. if ((!std::isnan(magnitude.zero_threshold)) && ((value.raw < magnitude.zero_threshold))) {
  3262. value.raw = 0.0;
  3263. }
  3264. magnitude.last = value.raw;
  3265. magnitude.filter->update(value.raw);
  3266. // -------------------------------------------------------------
  3267. // Procesing (units and decimals)
  3268. // -------------------------------------------------------------
  3269. value.processed = magnitude::process(magnitude, value.raw);
  3270. magnitude::read(magnitude::value(magnitude, value.processed));
  3271. // -------------------------------------------------------------------
  3272. // Reporting
  3273. // -------------------------------------------------------------------
  3274. // Initial status or after report counter overflows
  3275. bool report { 0 == report_count };
  3276. // In case magnitude was configured with ${name}MaxDelta, override report check
  3277. // when the value change is greater than the delta
  3278. if (!std::isnan(magnitude.reported) && (magnitude.max_delta > build::DefaultMaxDelta)) {
  3279. report = std::abs(value.processed - magnitude.reported) >= magnitude.max_delta;
  3280. }
  3281. // Special case for energy, save readings to RAM and EEPROM
  3282. if (MAGNITUDE_ENERGY == magnitude.type) {
  3283. energy::update(magnitude, report);
  3284. }
  3285. if (report) {
  3286. value.filtered = magnitude::process(magnitude, magnitude.filter->value());
  3287. // Make sure that report value is calculated using every read value before it
  3288. magnitude.filter->reset();
  3289. // Check ${name}MinDelta if there is a minimum change threshold to report
  3290. if (std::isnan(magnitude.reported) || (std::abs(value.filtered - magnitude.reported) >= magnitude.min_delta)) {
  3291. const auto report = magnitude::value(magnitude, value.filtered);
  3292. magnitude::report(report);
  3293. #if THINGSPEAK_SUPPORT
  3294. tspkEnqueueMagnitude(index, report.repr);
  3295. #endif
  3296. #if DOMOTICZ_SUPPORT
  3297. domoticzSendMagnitude(index, report);
  3298. #endif
  3299. magnitude.reported = value.filtered;
  3300. }
  3301. }
  3302. #if SENSOR_DEBUG
  3303. {
  3304. auto withUnits = [&](double value, Unit units) {
  3305. String out;
  3306. out += magnitude::format(magnitude, value);
  3307. if (units != Unit::None) {
  3308. out += magnitude::units(units);
  3309. }
  3310. return out;
  3311. };
  3312. DEBUG_MSG_P(PSTR("[SENSOR] %s -> raw %s processed %s filtered %s\n"),
  3313. magnitude::topic(magnitude).c_str(),
  3314. withUnits(value.raw, magnitude.sensor->units(magnitude.slot)).c_str(),
  3315. withUnits(value.processed, magnitude.units).c_str(),
  3316. withUnits(value.filtered, magnitude.units).c_str());
  3317. }
  3318. #endif
  3319. }
  3320. sensor::post();
  3321. #if WEB_SUPPORT
  3322. wsPost(web::onData);
  3323. #endif
  3324. }
  3325. }
  3326. void configure() {
  3327. // Read interval is shared between every sensor
  3328. // TODO: implement scheduling in the sensor itself.
  3329. // allow reads faster than 1sec, not just internal ones via tick()
  3330. // allow 'manual' sensors that may be triggered programatically
  3331. readInterval(sensor::settings::readInterval());
  3332. initInterval(sensor::settings::initInterval());
  3333. reportEvery(sensor::settings::reportEvery());
  3334. realTimeValues(sensor::settings::realTimeValues());
  3335. // TODO: something more generic? energy is an accumulating value, only allow for similar ones?
  3336. // TODO: move to an external module?
  3337. energy::every(sensor::settings::saveEvery());
  3338. // Update magnitude config, filter sizes and reset energy if needed
  3339. // TODO: namespace and various helpers need some naming tweaks...
  3340. for (auto& magnitude : magnitude::internal::magnitudes) {
  3341. // Only initialized once, notify about reset requirement?
  3342. if (!magnitude.filter) {
  3343. magnitude.filter_type = getSetting(
  3344. settings::keys::get(magnitude, settings::suffix::Filter),
  3345. magnitude::defaultFilter(magnitude));
  3346. magnitude.filter = magnitude::makeFilter(magnitude.filter_type);
  3347. }
  3348. // Some filters must be able store up to a certain amount of readings.
  3349. if (magnitude.filter->capacity() != reportEvery()) {
  3350. magnitude.filter->resize(reportEvery());
  3351. }
  3352. // process emon-specific settings first. ensure that settings use global index and we access sensor with the local one
  3353. if (isEmon(magnitude.sensor) && magnitude::traits::ratio_supported(magnitude.type)) {
  3354. auto* sensor = static_cast<BaseEmonSensor*>(magnitude.sensor.get());
  3355. sensor->setRatio(magnitude.slot, getSetting(
  3356. settings::keys::get(magnitude, settings::suffix::Ratio),
  3357. sensor->defaultRatio(magnitude.slot)));
  3358. }
  3359. // analog variant of emon sensor has some additional settings
  3360. if (isAnalogEmon(magnitude.sensor) && (magnitude.type == MAGNITUDE_VOLTAGE)) {
  3361. auto* sensor = static_cast<BaseAnalogEmonSensor*>(magnitude.sensor.get());
  3362. sensor->setVoltage(getSetting(
  3363. settings::keys::get(magnitude, settings::suffix::Mains),
  3364. sensor->defaultVoltage()));
  3365. sensor->setReferenceVoltage(getSetting(
  3366. settings::keys::get(magnitude, settings::suffix::Reference),
  3367. sensor->defaultReferenceVoltage()));
  3368. }
  3369. // adjust units based on magnitude's type
  3370. magnitude.units = units::filter(magnitude,
  3371. getSetting(
  3372. settings::keys::get(magnitude, settings::suffix::Units),
  3373. magnitude.sensor->units(magnitude.slot)));
  3374. // adjust resulting value (simple plus or minus)
  3375. // TODO: inject math or rpnlib expression?
  3376. if (magnitude::traits::correction_supported(magnitude.type)) {
  3377. magnitude.correction = getSetting(
  3378. settings::keys::get(magnitude, settings::suffix::Correction),
  3379. magnitude::build::correction(magnitude.type));
  3380. }
  3381. // pick decimal precision either from our (sane) defaults of from the sensor itself
  3382. // (specifically, when sensor has more or less precision than we expect)
  3383. {
  3384. const auto decimals = magnitude.sensor->decimals(magnitude.units);
  3385. magnitude.decimals = (decimals >= 0)
  3386. ? static_cast<unsigned char>(decimals)
  3387. : magnitude::decimals(magnitude.units);
  3388. }
  3389. // Per-magnitude min & max delta settings for reporting the value
  3390. // - ${prefix}DeltaMin${index} controls whether we report when report counter overflows
  3391. // (default is set to 0.0 aka value has changed from the last recorded one)
  3392. // - ${prefix}DeltaMax${index} will trigger report as soon as read value is greater than the specified delta
  3393. // (default is 0.0 as well, but this needs to be >0 to actually do something)
  3394. magnitude.min_delta = getSetting(
  3395. settings::keys::get(magnitude, settings::suffix::MinDelta),
  3396. build::DefaultMinDelta);
  3397. magnitude.max_delta = getSetting(
  3398. settings::keys::get(magnitude, settings::suffix::MaxDelta),
  3399. build::DefaultMaxDelta);
  3400. // Sometimes we want to ensure the value is above certain threshold before reporting
  3401. magnitude.zero_threshold = getSetting(
  3402. settings::keys::get(magnitude, settings::suffix::ZeroThreshold),
  3403. Value::Unknown);
  3404. // When we don't save energy, purge existing value in both RAM & settings
  3405. if (isEmon(magnitude.sensor) && (MAGNITUDE_ENERGY == magnitude.type) && (0 == energy::every())) {
  3406. energy::reset(magnitude.index_global);
  3407. }
  3408. }
  3409. }
  3410. void setup() {
  3411. migrateVersion(settings::migrate);
  3412. sensor::load();
  3413. sensor::init();
  3414. // Configure based on settings
  3415. sensor::configure();
  3416. // Allow us to query key default
  3417. sensor::settings::query::setup();
  3418. // Websockets integration, send sensor readings and configuration
  3419. #if WEB_SUPPORT
  3420. web::setup();
  3421. #endif
  3422. // Publishes sensor reports, and {re,}set energy
  3423. #if MQTT_SUPPORT
  3424. mqtt::setup();
  3425. #endif
  3426. #if API_SUPPORT
  3427. api::setup();
  3428. #endif
  3429. #if TERMINAL_SUPPORT
  3430. terminal::setup();
  3431. #endif
  3432. espurnaRegisterLoop(sensor::loop);
  3433. espurnaRegisterReload(sensor::configure);
  3434. }
  3435. } // namespace sensor
  3436. } // namespace espurna
  3437. // -----------------------------------------------------------------------------
  3438. // Public
  3439. // -----------------------------------------------------------------------------
  3440. #if WEB_SUPPORT
  3441. // Used by modules to generate magnitude_id<->module_id mapping for the WebUI
  3442. // Prefix controls the UI templates, supplied callback should retrieve module-specific value Id
  3443. void sensorWebSocketMagnitudes(JsonObject& root, const char* prefix, SensorWebSocketMagnitudesCallback callback) {
  3444. espurna::sensor::web::module(root, prefix, callback);
  3445. }
  3446. #endif // WEB_SUPPORT
  3447. void sensorOnMagnitudeRead(MagnitudeReadHandler handler) {
  3448. espurna::sensor::magnitude::onRead(handler);
  3449. }
  3450. void sensorOnMagnitudeReport(MagnitudeReadHandler handler) {
  3451. espurna::sensor::magnitude::onReport(handler);
  3452. }
  3453. size_t magnitudeCount() {
  3454. return espurna::sensor::magnitude::count();
  3455. }
  3456. unsigned char magnitudeIndex(unsigned char index) {
  3457. using namespace espurna::sensor;
  3458. if (index < magnitude::count()) {
  3459. return magnitude::get(index).index_global;
  3460. }
  3461. return 0;
  3462. }
  3463. unsigned char magnitudeType(unsigned char index) {
  3464. using namespace espurna::sensor;
  3465. if (index < magnitude::count()) {
  3466. return magnitude::get(index).type;
  3467. }
  3468. return MAGNITUDE_NONE;
  3469. }
  3470. String magnitudeUnits(unsigned char index) {
  3471. using namespace espurna::sensor;
  3472. if (index < magnitude::count()) {
  3473. return magnitude::units(magnitude::get(index));
  3474. }
  3475. return String();
  3476. }
  3477. String magnitudeUnits(espurna::sensor::Unit units) {
  3478. return espurna::sensor::magnitude::units(units);
  3479. }
  3480. espurna::sensor::Value magnitudeValue(unsigned char index) {
  3481. using namespace espurna::sensor;
  3482. Value out;
  3483. out.value = Value::Unknown;
  3484. if (index < magnitude::count()) {
  3485. const auto& magnitude = magnitude::get(index);
  3486. out = magnitude::value(magnitude,
  3487. realTimeValues() ? magnitude.last : magnitude.reported);
  3488. }
  3489. return out;
  3490. }
  3491. String magnitudeDescription(unsigned char index) {
  3492. using namespace espurna::sensor;
  3493. if (index < magnitude::count()) {
  3494. return magnitude::description(magnitude::get(index));
  3495. }
  3496. return String();
  3497. }
  3498. String magnitudeTopic(unsigned char index) {
  3499. using namespace espurna::sensor;
  3500. if (index < magnitude::count()) {
  3501. return magnitude::topicWithIndex(magnitude::get(index));
  3502. }
  3503. return String();
  3504. }
  3505. String magnitudeTypeTopic(unsigned char type) {
  3506. return espurna::sensor::magnitude::topic(type);
  3507. }
  3508. espurna::sensor::Info magnitudeInfo(unsigned char index) {
  3509. using namespace espurna::sensor;
  3510. if (index < magnitude::count()) {
  3511. return magnitude::info(magnitude::get(index));
  3512. }
  3513. return Info {
  3514. .type = MAGNITUDE_NONE,
  3515. .index = 0,
  3516. .units = Unit::None,
  3517. .decimals = 0,
  3518. };
  3519. }
  3520. espurna::StringView sensorList() {
  3521. return espurna::sensor::List;
  3522. }
  3523. void sensorSetup() {
  3524. espurna::sensor::setup();
  3525. }
  3526. #endif // SENSOR_SUPPORT