Fork of the espurna firmware for `mhsw` switches
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.

361 lines
11 KiB

  1. /*
  2. SENSOR MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include <vector>
  6. #include "filters/MedianFilter.h"
  7. #include "filters/MovingAverageFilter.h"
  8. #include "sensors/BaseSensor.h"
  9. typedef struct {
  10. BaseSensor * sensor;
  11. unsigned char local; // Local index in its provider
  12. magnitude_t type; // Type of measurement
  13. unsigned char global; // Global index in its type
  14. double current; // Current (last) value, unfiltered
  15. double filtered; // Filtered (averaged) value
  16. BaseFilter * filter; // Filter object
  17. } sensor_magnitude_t;
  18. std::vector<BaseSensor *> _sensors;
  19. std::vector<sensor_magnitude_t> _magnitudes;
  20. unsigned char _counts[MAGNITUDE_MAX];
  21. bool _sensor_realtime = API_REAL_TIME_VALUES;
  22. unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
  23. double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
  24. unsigned char _sensor_isr = 0xFF;
  25. // -----------------------------------------------------------------------------
  26. // Private
  27. // -----------------------------------------------------------------------------
  28. String _sensorTopic(magnitude_t type) {
  29. if (type == MAGNITUDE_TEMPERATURE) {
  30. return String(SENSOR_TEMPERATURE_TOPIC);
  31. } else if (type == MAGNITUDE_HUMIDITY) {
  32. return String(SENSOR_HUMIDITY_TOPIC);
  33. } else if (type == MAGNITUDE_ANALOG) {
  34. return String(SENSOR_ANALOG_TOPIC);
  35. } else if (type == MAGNITUDE_EVENTS) {
  36. return String(SENSOR_EVENTS_TOPIC);
  37. }
  38. return String(SENSOR_UNKNOWN_TOPIC);
  39. }
  40. unsigned char _sensorDecimals(magnitude_t type) {
  41. if (type == MAGNITUDE_TEMPERATURE) {
  42. return SENSOR_TEMPERATURE_DECIMALS;
  43. } else if (type == MAGNITUDE_HUMIDITY) {
  44. return SENSOR_HUMIDITY_DECIMALS;
  45. } else if (type == MAGNITUDE_ANALOG) {
  46. return SENSOR_ANALOG_DECIMALS;
  47. } else if (type == MAGNITUDE_EVENTS) {
  48. return SENSOR_EVENTS_DECIMALS;
  49. }
  50. return 0;
  51. }
  52. String _sensorUnits(magnitude_t type) {
  53. if (type == MAGNITUDE_TEMPERATURE) {
  54. if (_sensor_temperature_units == TMP_CELSIUS) {
  55. return String("C");
  56. } else {
  57. return String("F");
  58. }
  59. } else if (type == MAGNITUDE_HUMIDITY) {
  60. return String("%");
  61. } else if (type == MAGNITUDE_EVENTS) {
  62. return String("/m");
  63. }
  64. return String();
  65. }
  66. double _sensorProcess(magnitude_t type, double value) {
  67. if (type == MAGNITUDE_TEMPERATURE) {
  68. if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
  69. value = value + _sensor_temperature_correction;
  70. }
  71. return roundTo(value, _sensorDecimals(type));
  72. }
  73. void _sensorConfigure() {
  74. _sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
  75. _sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
  76. _sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
  77. }
  78. #if WEB_SUPPORT
  79. void _sensorWebSocketOnSend(JsonObject& root) {
  80. char buffer[10];
  81. bool hasTemperature = false;
  82. JsonArray& sensors = root.createNestedArray("sensors");
  83. for (unsigned char i=0; i<_magnitudes.size(); i++) {
  84. sensor_magnitude_t magnitude = _magnitudes[i];
  85. unsigned char decimals = _sensorDecimals(magnitude.type);
  86. dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer);
  87. JsonObject& sensor = sensors.createNestedObject();
  88. sensor["type"] = int(magnitude.type);
  89. sensor["value"] = String(buffer);
  90. sensor["units"] = _sensorUnits(magnitude.type);
  91. sensor["description"] = magnitude.sensor->slot(magnitude.local);
  92. if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
  93. }
  94. //root["apiRealTime"] = _sensor_realtime;
  95. root["tmpUnits"] = _sensor_temperature_units;
  96. root["tmpCorrection"] = _sensor_temperature_correction;
  97. if (hasTemperature) root["temperatureVisible"] = 1;
  98. }
  99. void _sensorAPISetup() {
  100. for (unsigned char magnitude_id=0; magnitude_id<_magnitudes.size(); magnitude_id++) {
  101. sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
  102. String topic = _sensorTopic(magnitude.type);
  103. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) topic = topic + "/" + String(magnitude.global);
  104. apiRegister(topic.c_str(), topic.c_str(), [magnitude_id](char * buffer, size_t len) {
  105. sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
  106. unsigned char decimals = _sensorDecimals(magnitude.type);
  107. double value = _sensor_realtime ? magnitude.current : magnitude.filtered;
  108. dtostrf(value, 1-len, decimals, buffer);
  109. });
  110. }
  111. }
  112. #endif
  113. void _sensorTick() {
  114. for (unsigned char i=0; i<_sensors.size(); i++) {
  115. _sensors[i]->tick();
  116. }
  117. }
  118. void _sensorPre() {
  119. for (unsigned char i=0; i<_sensors.size(); i++) {
  120. _sensors[i]->pre();
  121. if (!_sensors[i]->status()) {
  122. DEBUG_MSG("[SENSOR] Error reading data from %s (error: %d)\n",
  123. _sensors[i]->name().c_str(),
  124. _sensors[i]->error()
  125. );
  126. }
  127. }
  128. }
  129. void _sensorPost() {
  130. for (unsigned char i=0; i<_sensors.size(); i++) {
  131. _sensors[i]->post();
  132. }
  133. }
  134. // -----------------------------------------------------------------------------
  135. // Values
  136. // -----------------------------------------------------------------------------
  137. void sensorISR() {
  138. _sensors[_sensor_isr]->InterruptHandler();
  139. }
  140. void sensorRegister(BaseSensor * sensor) {
  141. _sensors.push_back(sensor);
  142. }
  143. unsigned char sensorCount() {
  144. return _sensors.size();
  145. }
  146. void sensorInterrupt(unsigned char sensor_id, unsigned char gpio, int mode) {
  147. _sensor_isr = sensor_id;
  148. attachInterrupt(gpio, sensorISR, mode);
  149. }
  150. void sensorInit() {
  151. #if DHT_SUPPORT
  152. #include "sensors/DHTSensor.h"
  153. sensorRegister(new DHTSensor(DHT_PIN, DHT_TYPE, DHT_PULLUP));
  154. #endif
  155. #if DS18B20_SUPPORT
  156. #include "sensors/DallasSensor.h"
  157. sensorRegister(new DallasSensor(DS18B20_PIN, SENSOR_READ_INTERVAL, DS18B20_PULLUP));
  158. #endif
  159. #if ANALOG_SUPPORT
  160. #include "sensors/AnalogSensor.h"
  161. sensorRegister(new AnalogSensor(ANALOG_PIN));
  162. #endif
  163. #if COUNTER_SUPPORT
  164. if (_sensor_isr == 0xFF) {
  165. #include "sensors/EventSensor.h"
  166. sensorRegister(new EventSensor(COUNTER_PIN, COUNTER_PIN_MODE, COUNTER_DEBOUNCE));
  167. sensorInterrupt(sensorCount()-1, COUNTER_PIN, COUNTER_INTERRUPT_MODE);
  168. }
  169. #endif
  170. }
  171. void sensorSetup() {
  172. // Load sensors
  173. sensorInit();
  174. // Load magnitudes
  175. for (unsigned char i=0; i<_sensors.size(); i++) {
  176. BaseSensor * sensor = _sensors[i];
  177. DEBUG_MSG("[SENSOR] %s\n", sensor->name().c_str());
  178. for (unsigned char k=0; k<sensor->count(); k++) {
  179. magnitude_t type = sensor->type(k);
  180. sensor_magnitude_t new_magnitude;
  181. new_magnitude.sensor = sensor;
  182. new_magnitude.local = k;
  183. new_magnitude.type = type;
  184. new_magnitude.global = _counts[type];
  185. new_magnitude.current = 0;
  186. new_magnitude.filtered = 0;
  187. if (type == MAGNITUDE_EVENTS) {
  188. new_magnitude.filter = new MovingAverageFilter(SENSOR_REPORT_EVERY);
  189. } else {
  190. new_magnitude.filter = new MedianFilter();
  191. }
  192. _magnitudes.push_back(new_magnitude);
  193. DEBUG_MSG("[SENSOR] -> %s:%d\n", _sensorTopic(type).c_str(), _counts[type]);
  194. _counts[type] = _counts[type] + 1;
  195. }
  196. }
  197. #if WEB_SUPPORT
  198. // Websockets
  199. wsOnSendRegister(_sensorWebSocketOnSend);
  200. wsOnAfterParseRegister(_sensorConfigure);
  201. // API
  202. _sensorAPISetup();
  203. #endif
  204. }
  205. void sensorLoop() {
  206. static unsigned long last_update = 0;
  207. static unsigned long report_count = 0;
  208. // Tick hook
  209. _sensorTick();
  210. // Check if we should read new data
  211. if ((millis() - last_update > SENSOR_READ_INTERVAL) || (last_update == 0)) {
  212. last_update = millis();
  213. report_count = (report_count + 1) % SENSOR_REPORT_EVERY;
  214. double value;
  215. char buffer[64];
  216. // Pre-read hook
  217. _sensorPre();
  218. // Get readings
  219. for (unsigned char i=0; i<_magnitudes.size(); i++) {
  220. sensor_magnitude_t magnitude = _magnitudes[i];
  221. if (magnitude.sensor->status()) {
  222. unsigned char decimals = _sensorDecimals(magnitude.type);
  223. value = magnitude.sensor->value(magnitude.local);
  224. magnitude.filter->add(value);
  225. // Special case
  226. if (magnitude.type == MAGNITUDE_EVENTS) value = magnitude.filter->result();
  227. value = _sensorProcess(magnitude.type, value);
  228. _magnitudes[i].current = value;
  229. // Debug
  230. #if TRUE
  231. {
  232. dtostrf(value, 1-sizeof(buffer), decimals, buffer);
  233. DEBUG_MSG("[SENSOR] %s - %s: %s%s\n",
  234. magnitude.sensor->name().c_str(),
  235. _sensorTopic(magnitude.type).c_str(),
  236. buffer,
  237. _sensorUnits(magnitude.type).c_str()
  238. );
  239. }
  240. #endif
  241. if (report_count == 0) {
  242. // TODO: option to report only if it has change (configurable amount)
  243. value = magnitude.filter->result();
  244. value = _sensorProcess(magnitude.type, value);
  245. _magnitudes[i].filtered = value;
  246. magnitude.filter->reset();
  247. dtostrf(value, 1-sizeof(buffer), decimals, buffer);
  248. #if MQTT_SUPPORT
  249. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
  250. mqttSend(_sensorTopic(magnitude.type).c_str(), magnitude.global, buffer);
  251. } else {
  252. mqttSend(_sensorTopic(magnitude.type).c_str(), buffer);
  253. }
  254. #endif
  255. #if INFLUXDB_SUPPORT
  256. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
  257. idbSend(_sensorTopic(magnitude.type).c_str(), magnitude.global, buffer);
  258. } else {
  259. idbSend(_sensorTopic(magnitude.type).c_str(), buffer);
  260. }
  261. #endif
  262. #if DOMOTICZ_SUPPORT
  263. // TODO
  264. #endif
  265. }
  266. }
  267. }
  268. // Post-read hook
  269. _sensorPost();
  270. #if WEB_SUPPORT
  271. wsSend(_sensorWebSocketOnSend);
  272. #endif
  273. }
  274. }