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.

677 lines
21 KiB

7 years ago
7 years ago
7 years ago
  1. /*
  2. SENSOR MODULE
  3. Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if SENSOR_SUPPORT
  6. #include <vector>
  7. #include "filters/MaxFilter.h"
  8. #include "filters/MedianFilter.h"
  9. #include "filters/MovingAverageFilter.h"
  10. #include "sensors/BaseSensor.h"
  11. typedef struct {
  12. BaseSensor * sensor;
  13. unsigned char local; // Local index in its provider
  14. unsigned char type; // Type of measurement
  15. unsigned char global; // Global index in its type
  16. double current; // Current (last) value, unfiltered
  17. double filtered; // Filtered (averaged) value
  18. double reported; // Last reported value
  19. double min_change; // Minimum value change to report
  20. BaseFilter * filter; // Filter object
  21. } sensor_magnitude_t;
  22. std::vector<BaseSensor *> _sensors;
  23. std::vector<sensor_magnitude_t> _magnitudes;
  24. unsigned char _counts[MAGNITUDE_MAX];
  25. bool _sensor_realtime = API_REAL_TIME_VALUES;
  26. unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
  27. unsigned int _sensor_report_every = SENSOR_REPORT_EVERY;
  28. unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
  29. double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION;
  30. // -----------------------------------------------------------------------------
  31. // Private
  32. // -----------------------------------------------------------------------------
  33. String _magnitudeTopic(unsigned char type) {
  34. char buffer[16] = {0};
  35. if (type < MAGNITUDE_MAX) strncpy_P(buffer, magnitude_topics[type], sizeof(buffer));
  36. return String(buffer);
  37. }
  38. unsigned char _magnitudeDecimals(unsigned char type) {
  39. if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type);
  40. return 0;
  41. }
  42. String _magnitudeUnits(unsigned char type) {
  43. char buffer[8] = {0};
  44. if (type < MAGNITUDE_MAX) {
  45. if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) {
  46. strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer));
  47. } else {
  48. strncpy_P(buffer, magnitude_units[type], sizeof(buffer));
  49. }
  50. }
  51. return String(buffer);
  52. }
  53. double _magnitudeProcess(unsigned char type, double value) {
  54. if (type == MAGNITUDE_TEMPERATURE) {
  55. if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32;
  56. value = value + _sensor_temperature_correction;
  57. }
  58. return roundTo(value, _magnitudeDecimals(type));
  59. }
  60. // -----------------------------------------------------------------------------
  61. #if WEB_SUPPORT
  62. void _sensorWebSocketSendData(JsonObject& root) {
  63. char buffer[10];
  64. bool hasTemperature = false;
  65. JsonArray& list = root.createNestedArray("magnitudes");
  66. for (unsigned char i=0; i<_magnitudes.size(); i++) {
  67. sensor_magnitude_t magnitude = _magnitudes[i];
  68. unsigned char decimals = _magnitudeDecimals(magnitude.type);
  69. dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer);
  70. JsonObject& element = list.createNestedObject();
  71. element["index"] = int(magnitude.global);
  72. element["type"] = int(magnitude.type);
  73. element["value"] = String(buffer);
  74. element["units"] = _magnitudeUnits(magnitude.type);
  75. element["description"] = magnitude.sensor->slot(magnitude.local);
  76. element["error"] = magnitude.sensor->error();
  77. if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
  78. }
  79. if (hasTemperature) root["temperatureVisible"] = 1;
  80. }
  81. void _sensorWebSocketStart(JsonObject& root) {
  82. for (unsigned char i=0; i<_sensors.size(); i++) {
  83. BaseSensor * sensor = _sensors[i];
  84. #if EMON_ANALOG_SUPPORT
  85. if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
  86. root["emonVisible"] = 1;
  87. root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage();
  88. }
  89. #endif
  90. #if HLW8012_SUPPORT
  91. if (sensor->getID() == SENSOR_HLW8012_ID) {
  92. root["hlwVisible"] = 1;
  93. }
  94. #endif
  95. }
  96. if (_magnitudes.size() > 0) {
  97. root["sensorsVisible"] = 1;
  98. //root["apiRealTime"] = _sensor_realtime;
  99. root["tmpUnits"] = _sensor_temperature_units;
  100. root["tmpCorrection"] = _sensor_temperature_correction;
  101. root["snsRead"] = _sensor_read_interval / 1000;
  102. root["snsReport"] = _sensor_report_every;
  103. }
  104. /*
  105. // Sensors manifest
  106. JsonArray& manifest = root.createNestedArray("manifest");
  107. #if BMX280_SUPPORT
  108. BMX280Sensor::manifest(manifest);
  109. #endif
  110. // Sensors configuration
  111. JsonArray& sensors = root.createNestedArray("sensors");
  112. for (unsigned char i; i<_sensors.size(); i++) {
  113. JsonObject& sensor = sensors.createNestedObject();
  114. sensor["index"] = i;
  115. sensor["id"] = _sensors[i]->getID();
  116. _sensors[i]->getConfig(sensor);
  117. }
  118. */
  119. }
  120. void _sensorAPISetup() {
  121. for (unsigned char magnitude_id=0; magnitude_id<_magnitudes.size(); magnitude_id++) {
  122. sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
  123. String topic = _magnitudeTopic(magnitude.type);
  124. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) topic = topic + "/" + String(magnitude.global);
  125. apiRegister(topic.c_str(), topic.c_str(), [magnitude_id](char * buffer, size_t len) {
  126. sensor_magnitude_t magnitude = _magnitudes[magnitude_id];
  127. unsigned char decimals = _magnitudeDecimals(magnitude.type);
  128. double value = _sensor_realtime ? magnitude.current : magnitude.filtered;
  129. dtostrf(value, 1-len, decimals, buffer);
  130. });
  131. }
  132. }
  133. #endif
  134. void _sensorTick() {
  135. for (unsigned char i=0; i<_sensors.size(); i++) {
  136. _sensors[i]->tick();
  137. }
  138. }
  139. void _sensorPre() {
  140. for (unsigned char i=0; i<_sensors.size(); i++) {
  141. _sensors[i]->pre();
  142. if (!_sensors[i]->status()) {
  143. DEBUG_MSG_P(PSTR("[SENSOR] Error reading data from %s (error: %d)\n"),
  144. _sensors[i]->description().c_str(),
  145. _sensors[i]->error()
  146. );
  147. }
  148. }
  149. }
  150. void _sensorPost() {
  151. for (unsigned char i=0; i<_sensors.size(); i++) {
  152. _sensors[i]->post();
  153. }
  154. }
  155. // -----------------------------------------------------------------------------
  156. // Sensor initialization
  157. // -----------------------------------------------------------------------------
  158. void _sensorInit() {
  159. #if ANALOG_SUPPORT
  160. {
  161. AnalogSensor * sensor = new AnalogSensor();
  162. _sensors.push_back(sensor);
  163. }
  164. #endif
  165. #if BMX280_SUPPORT
  166. {
  167. BMX280Sensor * sensor = new BMX280Sensor();
  168. sensor->setAddress(BMX280_ADDRESS);
  169. _sensors.push_back(sensor);
  170. }
  171. #endif
  172. #if DALLAS_SUPPORT
  173. {
  174. DallasSensor * sensor = new DallasSensor();
  175. sensor->setGPIO(DALLAS_PIN);
  176. _sensors.push_back(sensor);
  177. }
  178. #endif
  179. #if DHT_SUPPORT
  180. {
  181. DHTSensor * sensor = new DHTSensor();
  182. sensor->setGPIO(DHT_PIN);
  183. sensor->setType(DHT_TYPE);
  184. _sensors.push_back(sensor);
  185. }
  186. #endif
  187. #if DIGITAL_SUPPORT
  188. {
  189. DigitalSensor * sensor = new DigitalSensor();
  190. sensor->setGPIO(DIGITAL_PIN);
  191. sensor->setMode(DIGITAL_PIN_MODE);
  192. sensor->setDefault(DIGITAL_DEFAULT_STATE);
  193. _sensors.push_back(sensor);
  194. }
  195. #endif
  196. #if ECH1560_SUPPORT
  197. {
  198. ECH1560Sensor * sensor = new ECH1560Sensor();
  199. sensor->setCLK(ECH1560_CLK_PIN);
  200. sensor->setMISO(ECH1560_MISO_PIN);
  201. sensor->setInverted(ECH1560_INVERTED);
  202. _sensors.push_back(sensor);
  203. }
  204. #endif
  205. #if EMON_ADC121_SUPPORT
  206. {
  207. EmonADC121Sensor * sensor = new EmonADC121Sensor();
  208. sensor->setAddress(EMON_ADC121_I2C_ADDRESS);
  209. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  210. sensor->setReference(EMON_REFERENCE_VOLTAGE);
  211. sensor->setCurrentRatio(0, EMON_CURRENT_RATIO);
  212. _sensors.push_back(sensor);
  213. }
  214. #endif
  215. #if EMON_ADS1X15_SUPPORT
  216. {
  217. EmonADS1X15Sensor * sensor = new EmonADS1X15Sensor();
  218. sensor->setAddress(EMON_ADS1X15_I2C_ADDRESS);
  219. sensor->setType(EMON_ADS1X15_TYPE);
  220. sensor->setMask(EMON_ADS1X15_MASK);
  221. sensor->setGain(EMON_ADS1X15_GAIN);
  222. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  223. sensor->setCurrentRatio(0, EMON_CURRENT_RATIO);
  224. sensor->setCurrentRatio(1, EMON_CURRENT_RATIO);
  225. sensor->setCurrentRatio(2, EMON_CURRENT_RATIO);
  226. sensor->setCurrentRatio(3, EMON_CURRENT_RATIO);
  227. _sensors.push_back(sensor);
  228. }
  229. #endif
  230. #if EMON_ANALOG_SUPPORT
  231. {
  232. EmonAnalogSensor * sensor = new EmonAnalogSensor();
  233. sensor->setVoltage(EMON_MAINS_VOLTAGE);
  234. sensor->setReference(EMON_REFERENCE_VOLTAGE);
  235. sensor->setCurrentRatio(0, EMON_CURRENT_RATIO);
  236. _sensors.push_back(sensor);
  237. }
  238. #endif
  239. #if EVENTS_SUPPORT
  240. {
  241. EventSensor * sensor = new EventSensor();
  242. sensor->setGPIO(EVENTS_PIN);
  243. sensor->setMode(EVENTS_PIN_MODE);
  244. sensor->setDebounceTime(EVENTS_DEBOUNCE);
  245. sensor->setInterruptMode(EVENTS_INTERRUPT_MODE);
  246. _sensors.push_back(sensor);
  247. }
  248. #endif
  249. #if HLW8012_SUPPORT
  250. {
  251. HLW8012Sensor * sensor = new HLW8012Sensor();
  252. sensor->setSEL(HLW8012_SEL_PIN);
  253. sensor->setCF(HLW8012_CF_PIN);
  254. sensor->setCF1(HLW8012_CF1_PIN);
  255. sensor->setSELCurrent(HLW8012_SEL_CURRENT);
  256. _sensors.push_back(sensor);
  257. }
  258. #endif
  259. #if MHZ19_SUPPORT
  260. {
  261. MHZ19Sensor * sensor = new MHZ19Sensor();
  262. sensor->setRX(MHZ19_RX_PIN);
  263. sensor->setTX(MHZ19_TX_PIN);
  264. _sensors.push_back(sensor);
  265. }
  266. #endif
  267. #if PMSX003_SUPPORT
  268. {
  269. PMSX003Sensor * sensor = new PMSX003Sensor();
  270. sensor->setRX(PMS_RX_PIN);
  271. sensor->setTX(PMS_TX_PIN);
  272. _sensors.push_back(sensor);
  273. }
  274. #endif
  275. #if SHT3X_I2C_SUPPORT
  276. {
  277. SHT3XI2CSensor * sensor = new SHT3XI2CSensor();
  278. sensor->setAddress(SHT3X_I2C_ADDRESS);
  279. _sensors.push_back(sensor);
  280. }
  281. #endif
  282. #if SI7021_SUPPORT
  283. {
  284. SI7021Sensor * sensor = new SI7021Sensor();
  285. sensor->setAddress(SI7021_ADDRESS);
  286. _sensors.push_back(sensor);
  287. }
  288. #endif
  289. #if V9261F_SUPPORT
  290. {
  291. V9261FSensor * sensor = new V9261FSensor();
  292. sensor->setRX(V9261F_PIN);
  293. sensor->setInverted(V9261F_PIN_INVERSE);
  294. _sensors.push_back(sensor);
  295. }
  296. #endif
  297. }
  298. void _sensorConfigure() {
  299. double value;
  300. for (unsigned char i=0; i<_sensors.size(); i++) {
  301. #if EMON_ANALOG_SUPPORT
  302. if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) {
  303. EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
  304. if (value = getSetting("pwrExpectedP", 0).toInt() == 0) {
  305. value = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat();
  306. if (value > 0) sensor->setCurrentRatio(0, value);
  307. } else {
  308. sensor->expectedPower(0, value);
  309. setSetting("pwrRatioC", sensor->getCurrentRatio(0));
  310. }
  311. if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
  312. sensor->setCurrentRatio(0, EMON_CURRENT_RATIO);
  313. delSetting("pwrRatioC");
  314. }
  315. sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
  316. }
  317. #endif // EMON_ANALOG_SUPPORT
  318. // Force sensor to reload config
  319. _sensors[i]->begin();
  320. #if HLW8012_SUPPORT
  321. if (_sensors[i]->getID() == SENSOR_HLW8012_ID) {
  322. HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i];
  323. if (value = getSetting("pwrExpectedC", 0).toFloat()) {
  324. sensor->expectedCurrent(value);
  325. setSetting("pwrRatioC", sensor->getCurrentRatio());
  326. } else {
  327. value = getSetting("pwrRatioC", 0).toFloat();
  328. if (value > 0) sensor->setCurrentRatio(value);
  329. }
  330. if (value = getSetting("pwrExpectedV", 0).toInt()) {
  331. sensor->expectedVoltage(value);
  332. setSetting("pwrRatioV", sensor->getVoltageRatio());
  333. } else {
  334. value = getSetting("pwrRatioV", 0).toFloat();
  335. if (value > 0) sensor->setVoltageRatio(value);
  336. }
  337. if (value = getSetting("pwrExpectedP", 0).toInt()) {
  338. sensor->expectedPower(value);
  339. setSetting("pwrRatioP", sensor->getPowerRatio());
  340. } else {
  341. value = getSetting("pwrRatioP", 0).toFloat();
  342. if (value > 0) sensor->setPowerRatio(value);
  343. }
  344. if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
  345. sensor->resetRatios();
  346. delSetting("pwrRatioC");
  347. delSetting("pwrRatioV");
  348. delSetting("pwrRatioP");
  349. }
  350. }
  351. #endif // HLW8012_SUPPORT
  352. }
  353. // General sensor settings
  354. _sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
  355. _sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
  356. _sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
  357. _sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
  358. _sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
  359. // Save settings
  360. delSetting("pwrExpectedP");
  361. delSetting("pwrExpectedC");
  362. delSetting("pwrExpectedV");
  363. delSetting("pwrResetCalibration");
  364. //saveSettings();
  365. }
  366. void _magnitudesInit() {
  367. for (unsigned char i=0; i<_sensors.size(); i++) {
  368. BaseSensor * sensor = _sensors[i];
  369. DEBUG_MSG_P(PSTR("[SENSOR] %s\n"), sensor->description().c_str());
  370. if (sensor->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), sensor->error());
  371. for (unsigned char k=0; k<sensor->count(); k++) {
  372. unsigned char type = sensor->type(k);
  373. sensor_magnitude_t new_magnitude;
  374. new_magnitude.sensor = sensor;
  375. new_magnitude.local = k;
  376. new_magnitude.type = type;
  377. new_magnitude.global = _counts[type];
  378. new_magnitude.current = 0;
  379. new_magnitude.filtered = 0;
  380. new_magnitude.reported = 0;
  381. new_magnitude.min_change = 0;
  382. if (type == MAGNITUDE_DIGITAL) {
  383. new_magnitude.filter = new MaxFilter();
  384. } else if (type == MAGNITUDE_EVENTS) {
  385. new_magnitude.filter = new MovingAverageFilter();
  386. } else {
  387. new_magnitude.filter = new MedianFilter();
  388. }
  389. _magnitudes.push_back(new_magnitude);
  390. DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), _magnitudeTopic(type).c_str(), _counts[type]);
  391. _counts[type] = _counts[type] + 1;
  392. }
  393. }
  394. }
  395. // -----------------------------------------------------------------------------
  396. // Public
  397. // -----------------------------------------------------------------------------
  398. unsigned char sensorCount() {
  399. return _sensors.size();
  400. }
  401. unsigned char magnitudeCount() {
  402. return _magnitudes.size();
  403. }
  404. String magnitudeName(unsigned char index) {
  405. if (index < _magnitudes.size()) {
  406. sensor_magnitude_t magnitude = _magnitudes[index];
  407. return magnitude.sensor->slot(magnitude.local);
  408. }
  409. return String();
  410. }
  411. unsigned char magnitudeType(unsigned char index) {
  412. if (index < _magnitudes.size()) {
  413. return int(_magnitudes[index].type);
  414. }
  415. return MAGNITUDE_NONE;
  416. }
  417. unsigned char magnitudeIndex(unsigned char index) {
  418. if (index < _magnitudes.size()) {
  419. return int(_magnitudes[index].global);
  420. }
  421. return 0;
  422. }
  423. // -----------------------------------------------------------------------------
  424. void sensorSetup() {
  425. // Load sensors
  426. _sensorInit();
  427. // Configure stored values
  428. _sensorConfigure();
  429. // Load magnitudes
  430. _magnitudesInit();
  431. #if WEB_SUPPORT
  432. // Websockets
  433. wsOnSendRegister(_sensorWebSocketStart);
  434. wsOnSendRegister(_sensorWebSocketSendData);
  435. wsOnAfterParseRegister(_sensorConfigure);
  436. // API
  437. _sensorAPISetup();
  438. #endif
  439. }
  440. void sensorLoop() {
  441. static unsigned long last_update = 0;
  442. static unsigned long report_count = 0;
  443. if (_magnitudes.size() == 0) return;
  444. // Tick hook
  445. _sensorTick();
  446. // Check if we should read new data
  447. if (millis() - last_update > _sensor_read_interval) {
  448. last_update = millis();
  449. report_count = (report_count + 1) % _sensor_report_every;
  450. double current;
  451. double filtered;
  452. char buffer[64];
  453. // Pre-read hook
  454. _sensorPre();
  455. // Get readings
  456. for (unsigned char i=0; i<_magnitudes.size(); i++) {
  457. sensor_magnitude_t magnitude = _magnitudes[i];
  458. if (magnitude.sensor->status()) {
  459. unsigned char decimals = _magnitudeDecimals(magnitude.type);
  460. current = magnitude.sensor->value(magnitude.local);
  461. magnitude.filter->add(current);
  462. // Special case
  463. if (magnitude.type == MAGNITUDE_EVENTS) current = magnitude.filter->result();
  464. current = _magnitudeProcess(magnitude.type, current);
  465. _magnitudes[i].current = current;
  466. // Debug
  467. #if SENSOR_DEBUG
  468. {
  469. dtostrf(current, 1-sizeof(buffer), decimals, buffer);
  470. DEBUG_MSG_P(PSTR("[SENSOR] %s - %s: %s%s\n"),
  471. magnitude.sensor->slot(magnitude.local).c_str(),
  472. _magnitudeTopic(magnitude.type).c_str(),
  473. buffer,
  474. _magnitudeUnits(magnitude.type).c_str()
  475. );
  476. }
  477. #endif // SENSOR_DEBUG
  478. // Time to report (we do it every _sensor_report_every readings)
  479. if (report_count == 0) {
  480. filtered = magnitude.filter->result();
  481. magnitude.filter->reset();
  482. filtered = _magnitudeProcess(magnitude.type, filtered);
  483. _magnitudes[i].filtered = filtered;
  484. // Check if there is a minimum change threshold to report
  485. if (fabs(filtered - magnitude.reported) >= magnitude.min_change) {
  486. _magnitudes[i].reported = filtered;
  487. dtostrf(filtered, 1-sizeof(buffer), decimals, buffer);
  488. #if MQTT_SUPPORT
  489. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
  490. mqttSend(_magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
  491. } else {
  492. mqttSend(_magnitudeTopic(magnitude.type).c_str(), buffer);
  493. }
  494. #endif // MQTT_SUPPORT
  495. #if INFLUXDB_SUPPORT
  496. if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
  497. idbSend(_magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
  498. } else {
  499. idbSend(_magnitudeTopic(magnitude.type).c_str(), buffer);
  500. }
  501. #endif // INFLUXDB_SUPPORT
  502. #if DOMOTICZ_SUPPORT
  503. {
  504. char key[15];
  505. snprintf_P(key, sizeof(key), PSTR("dczMagnitude%d"), i);
  506. if (magnitude.type == MAGNITUDE_HUMIDITY) {
  507. int status;
  508. if (filtered > 70) {
  509. status = HUMIDITY_WET;
  510. } else if (filtered > 45) {
  511. status = HUMIDITY_COMFORTABLE;
  512. } else if (filtered > 30) {
  513. status = HUMIDITY_NORMAL;
  514. } else {
  515. status = HUMIDITY_DRY;
  516. }
  517. char status_buf[5];
  518. itoa(status, status_buf, 10);
  519. domoticzSend(key, buffer, status_buf);
  520. } else {
  521. domoticzSend(key, 0, buffer);
  522. }
  523. }
  524. #endif // DOMOTICZ_SUPPORT
  525. } // if (fabs(filtered - magnitude.reported) >= magnitude.min_change)
  526. } // if (report_count == 0)
  527. } // if (magnitude.sensor->status())
  528. } // for (unsigned char i=0; i<_magnitudes.size(); i++)
  529. // Post-read hook
  530. _sensorPost();
  531. #if WEB_SUPPORT
  532. wsSend(_sensorWebSocketSendData);
  533. #endif
  534. }
  535. }
  536. #endif // SENSOR_SUPPORT