From 2299ce9bfdfb4abc4fb6c11d6f807adf4464940f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Tue, 12 Dec 2017 21:45:28 +0100 Subject: [PATCH] Basic sensor scafolding, DHT 90% migrated --- code/espurna/config/general.h | 22 +- code/espurna/config/prototypes.h | 7 + code/espurna/config/sensors.h | 5 +- code/espurna/dht.ino | 8 +- code/espurna/espurna.ino | 6 +- code/espurna/influxdb.ino | 6 + code/espurna/libs/AggregatorBase.h | 49 +++ .../{MedianFilter.h => AggregatorMedian.h} | 32 +- code/espurna/libs/AggregatorMovingAverage.h | 41 +++ code/espurna/power.ino | 10 +- code/espurna/relay.ino | 4 +- code/espurna/sensor.ino | 290 ++++++++++++++++++ code/espurna/sensors/SensorBase.h | 70 +++++ code/espurna/sensors/SensorDHT.h | 194 ++++++++++++ code/espurna/ws.ino | 4 +- code/html/custom.css | 3 + code/html/custom.js | 36 +++ code/html/index.html | 17 +- 18 files changed, 751 insertions(+), 53 deletions(-) create mode 100644 code/espurna/libs/AggregatorBase.h rename code/espurna/libs/{MedianFilter.h => AggregatorMedian.h} (64%) create mode 100644 code/espurna/libs/AggregatorMovingAverage.h create mode 100644 code/espurna/sensor.ino create mode 100644 code/espurna/sensors/SensorBase.h create mode 100644 code/espurna/sensors/SensorDHT.h diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 672f88bb..3f6dbc55 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -146,8 +146,6 @@ #define CUSTOM_RESET_MAX 10 -#include - PROGMEM const char custom_reset_hardware[] = "Hardware button"; PROGMEM const char custom_reset_web[] = "Reboot from web interface"; PROGMEM const char custom_reset_terminal[] = "Reboot from terminal"; @@ -246,7 +244,6 @@ PROGMEM const char* const custom_reset_string[] = { #define TMP_CELSIUS 0 #define TMP_FAHRENHEIT 1 -#define TMP_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT) //------------------------------------------------------------------------------ // LED @@ -749,6 +746,25 @@ PROGMEM const char* const custom_reset_string[] = { #define RF_SEND_DELAY 250 // Interval between sendings in ms #define RF_RECEIVE_DELAY 500 // Interval between recieving in ms (avoid debouncing) +// ----------------------------------------------------------------------------- +// SENSORS +// ----------------------------------------------------------------------------- + +#define SENSOR_READ_INTERVAL 6000 // Read data from sensors every 6 seconds +#define SENSOR_REPORT_EVERY 10 // Report every this many readings +#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0) + // even if just one sensor (0 for backwards compatibility) + +#define SENSOR_TEMPERATURE_DECIMALS 2 +#define SENSOR_HUMIDITY_DECIMALS 0 + +#define SENSOR_UNKNOWN_TOPIC "unknown" +#define SENSOR_TEMPERATURE_TOPIC "temperature" +#define SENSOR_HUMIDITY_TOPIC "humidity" + +#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT) +#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction + // ----------------------------------------------------------------------------- // IR // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 38d549aa..74aaa43c 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -1,6 +1,7 @@ #include #include #include +#include extern "C" { #include "user_interface.h" @@ -60,6 +61,7 @@ template void domoticzSend(const char * key, T nvalue, const char * // ----------------------------------------------------------------------------- #if INFLUXDB_SUPPORT template bool idbSend(const char * topic, T payload); +template bool idbSend(const char * topic, unsigned char id, T payload); #endif // ----------------------------------------------------------------------------- @@ -69,6 +71,11 @@ template bool idbSend(const char * topic, T payload); #include #endif +// ----------------------------------------------------------------------------- +// Sensors +// ----------------------------------------------------------------------------- +#include "sensors/SensorBase.h" + // ----------------------------------------------------------------------------- // Utils // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index e9389722..bad927b8 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -27,7 +27,6 @@ #define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report #endif -#define TEMPERATURE_CORRECTION 0.0 // This is both for DHT and DS18B20 #define TEMPERATURE_DECIMALS 1 // Decimals for temperature values //-------------------------------------------------------------------------------- @@ -36,11 +35,11 @@ //-------------------------------------------------------------------------------- #ifndef DHT_SUPPORT -#define DHT_SUPPORT 0 +#define DHT_SUPPORT 1 #endif #ifndef DHT_PIN -#define DHT_PIN 14 +#define DHT_PIN 13 #endif #ifndef DHT_TYPE diff --git a/code/espurna/dht.ino b/code/espurna/dht.ino index c4ad71df..572a1e0f 100644 --- a/code/espurna/dht.ino +++ b/code/espurna/dht.ino @@ -139,7 +139,7 @@ void _dhtWebSocketOnSend(JsonObject& root) { root["dhtTmp"] = getDHTTemperature(); root["dhtHum"] = getDHTHumidity(); } - root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt(); + root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); } // ----------------------------------------------------------------------------- @@ -152,12 +152,12 @@ bool getDHTIsConnected() { double getDHTTemperature(bool celsius) { double value = celsius ? _dhtTemperature : _dhtTemperature * 1.8 + 32; - double correction = getSetting("tmpCorrection", TEMPERATURE_CORRECTION).toFloat(); + double correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat(); return roundTo(value + correction, TEMPERATURE_DECIMALS); } double getDHTTemperature() { - bool celsius = getSetting("tmpUnits", TMP_UNITS).toInt() == TMP_CELSIUS; + bool celsius = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt() == TMP_CELSIUS; return getDHTTemperature(celsius); } @@ -206,7 +206,7 @@ void dhtLoop() { _dhtIsConnected = true; // Get values - bool celsius = getSetting("tmpUnits", TMP_UNITS).toInt() == TMP_CELSIUS; + bool celsius = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt() == TMP_CELSIUS; double t = getDHTTemperature(celsius); unsigned int h = getDHTHumidity(); diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index fdcfdd86..20e1d038 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -324,7 +324,8 @@ void setup() { counterSetup(); #endif #if DHT_SUPPORT - dhtSetup(); + //dhtSetup(); + sensorSetup(); #endif #if RF_SUPPORT rfSetup(); @@ -394,7 +395,8 @@ void loop() { counterLoop(); #endif #if DHT_SUPPORT - dhtLoop(); + //dhtLoop(); + sensorLoop(); #endif #if RF_SUPPORT rfLoop(); diff --git a/code/espurna/influxdb.ino b/code/espurna/influxdb.ino index b25c1088..c5a795d1 100644 --- a/code/espurna/influxdb.ino +++ b/code/espurna/influxdb.ino @@ -76,6 +76,12 @@ template bool idbSend(const char * topic, T payload) { } +template bool idbSend(const char * topic, unsigned char id, T payload) { + char measurement[64]; + snprintf_P(measurement, sizeof(measurement), PSTR("%s,id=%d"), topic, id); + return idbSend(topic, payload); +} + bool idbEnabled() { return _idb_enabled; } diff --git a/code/espurna/libs/AggregatorBase.h b/code/espurna/libs/AggregatorBase.h new file mode 100644 index 00000000..2a0c3286 --- /dev/null +++ b/code/espurna/libs/AggregatorBase.h @@ -0,0 +1,49 @@ +// ----------------------------------------------------------------------------- +// Aggregator base class +// ----------------------------------------------------------------------------- + +#pragma once + +#include + +class AggregatorBase { + + public: + + AggregatorBase() { + _data = new std::vector(); + } + + ~AggregatorBase() { + if (_data) delete _data; + } + + virtual void add(double value) { + _data->push_back(value); + } + + virtual unsigned char count() { + return _data->size(); + } + + virtual void reset() { + _data->clear(); + } + + virtual double max() { + double max = 0; + for (unsigned char i = 1; i < _data->size(); i++) { + if (max < _data->at(i)) max = _data->at(i); + } + return max; + } + + virtual double result() { + return 0; + } + + protected: + + std::vector *_data; + +}; diff --git a/code/espurna/libs/MedianFilter.h b/code/espurna/libs/AggregatorMedian.h similarity index 64% rename from code/espurna/libs/MedianFilter.h rename to code/espurna/libs/AggregatorMedian.h index be7239b2..26d48a17 100644 --- a/code/espurna/libs/MedianFilter.h +++ b/code/espurna/libs/AggregatorMedian.h @@ -4,25 +4,11 @@ #pragma once -class MedianFilter { +#include "AggregatorBase.h" - public: - - MedianFilter() { - _data = new std::vector(); - } - - ~MedianFilter() { - if (_data) delete _data; - } - - virtual void add(double value) { - _data->push_back(value); - } +class AggregatorMedian : public AggregatorBase { - virtual unsigned char count() { - return _data->size(); - } + public: virtual void reset() { double last = _data->empty() ? 0 : _data->back(); @@ -30,14 +16,6 @@ class MedianFilter { add(last); } - virtual double max() { - double max = 0; - for (unsigned char i = 1; i < _data->size(); i++) { - if (max < _data->at(i)) max = _data->at(i); - } - return max; - } - virtual double result() { double sum = 0; @@ -70,8 +48,4 @@ class MedianFilter { } - private: - - std::vector *_data; - }; diff --git a/code/espurna/libs/AggregatorMovingAverage.h b/code/espurna/libs/AggregatorMovingAverage.h new file mode 100644 index 00000000..4c7a812c --- /dev/null +++ b/code/espurna/libs/AggregatorMovingAverage.h @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// Aggregator Moving Average +// ----------------------------------------------------------------------------- + +#pragma once + +#include +#include "AggregatorBase.h" + +class AggregatorMovingAverage : public AggregatorBase { + + public: + + AggregatorMovingAverage(unsigned char size) { + _size = size; + for (unsigned char i=0; ipush_back(0); + } + } + + virtual void add(double value) { + _sum = _sum + value - _data->at(_pointer); + _data->at(_pointer) = value; + _pointer = (_pointer + 1) % _size; + } + + virtual void reset() { + // Nothing to do + } + + virtual double result() { + return _sum; + } + + protected: + + unsigned char _size = 0; + unsigned char _pointer = 0; + double _sum = 0; + +}; diff --git a/code/espurna/power.ino b/code/espurna/power.ino index f8ab8725..745304be 100644 --- a/code/espurna/power.ino +++ b/code/espurna/power.ino @@ -12,7 +12,7 @@ Copyright (C) 2016-2017 by Xose Pérez // MODULE GLOBALS AND CACHE // ----------------------------------------------------------------------------- -#include "libs/MedianFilter.h" +#include "libs/AggregatorMedian.h" #include #include @@ -27,15 +27,15 @@ double _power_current = 0; double _power_voltage = 0; double _power_apparent = 0; double _power_energy = 0; -MedianFilter _filter_current = MedianFilter(); +AggregatorMedian _filter_current = AggregatorMedian(); #if POWER_HAS_ACTIVE double _power_active = 0; double _power_reactive = 0; double _power_factor = 0; - MedianFilter _filter_voltage = MedianFilter(); - MedianFilter _filter_active = MedianFilter(); - MedianFilter _filter_apparent = MedianFilter(); + AggregatorMedian _filter_voltage = AggregatorMedian(); + AggregatorMedian _filter_active = AggregatorMedian(); + AggregatorMedian _filter_apparent = AggregatorMedian(); #endif #if POWER_HAS_ENERGY diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index a387db11..be6fd755 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -618,9 +618,7 @@ void relaySetupMQTT() { #if INFLUXDB_SUPPORT void relayInfluxDB(unsigned char id) { if (id >= _relays.size()) return; - char buffer[20]; - snprintf_P(buffer, sizeof(buffer), PSTR("%s,id=%d"), MQTT_TOPIC_RELAY, id); - idbSend(buffer, relayStatus(id) ? "1" : "0"); + idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0"); } #endif diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino new file mode 100644 index 00000000..10e51291 --- /dev/null +++ b/code/espurna/sensor.ino @@ -0,0 +1,290 @@ +/* + +SENSOR MODULE + +Copyright (C) 2016-2017 by Xose Pérez + +*/ + +#include +#include "libs/AggregatorMedian.h" +#include "libs/AggregatorMovingAverage.h" +#include "sensors/SensorBase.h" + +typedef struct { + SensorBase * sensor; + unsigned char local; // Local index in its provider + magnitude_t type; // Type of measurement + unsigned char global; // Global index in its type + double current; // Current (last) value, unfiltered + double filtered; // Filtered (averaged) value + AggregatorBase * filter; // Filter object +} sensor_magnitude_t; + +std::vector _sensors; +std::vector _magnitudes; + +unsigned char _counts[MAGNITUDE_MAX]; +bool _sensor_realtime = API_REAL_TIME_VALUES; +unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS; +double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION; + +#if DHT_SUPPORT + #include "sensors/SensorDHT.h" +#endif + +// ----------------------------------------------------------------------------- +// Private +// ----------------------------------------------------------------------------- + +String _sensorTopic(magnitude_t type) { + if (type == MAGNITUDE_TEMPERATURE) { + return String(SENSOR_TEMPERATURE_TOPIC); + } else if (type == MAGNITUDE_HUMIDITY) { + return String(SENSOR_HUMIDITY_TOPIC); + } + return String(SENSOR_UNKNOWN_TOPIC); +} + +unsigned char _sensorDecimals(magnitude_t type) { + if (type == MAGNITUDE_TEMPERATURE) { + return SENSOR_TEMPERATURE_DECIMALS; + } else if (type == MAGNITUDE_HUMIDITY) { + return SENSOR_HUMIDITY_DECIMALS; + } + return 0; +} + +String _sensorUnits(magnitude_t type) { + if (type == MAGNITUDE_TEMPERATURE) { + if (_sensor_temperature_units == TMP_CELSIUS) { + return String("C"); + } else { + return String("F"); + } + } else if (type == MAGNITUDE_HUMIDITY) { + return String("%"); + } + return String(); +} + +double _sensorProcess(magnitude_t type, double value) { + if (type == MAGNITUDE_TEMPERATURE) { + if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32; + value = value + _sensor_temperature_correction; + } + return roundTo(value, _sensorDecimals(type)); +} + +void _sensorConfigure() { + _sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1; + _sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); + _sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat(); +} + +#if WEB_SUPPORT + +void _sensorWebSocketOnSend(JsonObject& root) { + + bool hasTemperature = false; + + JsonArray& sensors = root.createNestedArray("sensors"); + for (unsigned char i=0; i<_magnitudes.size(); i++) { + + sensor_magnitude_t magnitude = _magnitudes[i]; + JsonObject& sensor = sensors.createNestedObject(); + sensor["type"] = int(magnitude.type); + sensor["value"] = magnitude.current; + sensor["units"] = _sensorUnits(magnitude.type); + sensor["description"] = magnitude.sensor->slot(magnitude.local); + + if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true; + + } + + //root["apiRealTime"] = _sensor_realtime; + root["tmpUnits"] = _sensor_temperature_units; + root["tmpCorrection"] = _sensor_temperature_correction; + if (hasTemperature) root["temperatureVisible"] = 1; + +} + +void _sensorAPISetup() { + + for (unsigned char magnitude_id=0; magnitude_id<_magnitudes.size(); magnitude_id++) { + + sensor_magnitude_t magnitude = _magnitudes[magnitude_id]; + + String topic = _sensorTopic(magnitude.type); + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) topic = topic + "/" + String(magnitude.global); + + apiRegister(topic.c_str(), topic.c_str(), [magnitude_id](char * buffer, size_t len) { + sensor_magnitude_t magnitude = _magnitudes[magnitude_id]; + unsigned char decimals = _sensorDecimals(magnitude.type); + double value = _sensor_realtime ? magnitude.current : magnitude.filtered; + dtostrf(value, 1-len, decimals, buffer); + }); + + } + +} +#endif + +// ----------------------------------------------------------------------------- +// Values +// ----------------------------------------------------------------------------- + +void sensorSetup() { + + // Load sensors + #if DHT_SUPPORT + { + _sensors.push_back(new SensorDHT(DHT_PIN, DHT_TYPE)); + #if DHT_PULLUP + pinMode(DHT_PIN, INPUT_PULLUP); + #endif + } + #endif + + // Read magnitudes + for (unsigned char i=0; i<_sensors.size(); i++) { + + SensorBase * sensor = _sensors[i]; + DEBUG_MSG("[SENSOR] %s\n", sensor->name().c_str()); + + for (unsigned char k=0; kcount(); k++) { + + magnitude_t type = sensor->type(k); + + sensor_magnitude_t new_magnitude; + new_magnitude.sensor = sensor; + new_magnitude.local = k; + new_magnitude.type = type; + new_magnitude.global = _counts[type]; + new_magnitude.current = 0; + new_magnitude.filtered = 0; + if (type == MAGNITUDE_EVENTS) { + new_magnitude.filter = new AggregatorMovingAverage(SENSOR_REPORT_EVERY); + } else { + new_magnitude.filter = new AggregatorMedian(); + } + _magnitudes.push_back(new_magnitude); + + DEBUG_MSG("[SENSOR] -> %s:%d\n", _sensorTopic(type).c_str(), _counts[type]); + + _counts[type] = _counts[type] + 1; + + } + + } + + #if WEB_SUPPORT + + // Websockets + wsOnSendRegister(_sensorWebSocketOnSend); + wsOnAfterParseRegister(_sensorConfigure); + + // API + _sensorAPISetup(); + + #endif + +} + +void sensorLoop() { + + static unsigned long last_update = 0; + static unsigned long report_count = 0; + + // Check if we should read new data + if ((millis() - last_update > SENSOR_READ_INTERVAL) || (last_update == 0)) { + + last_update = millis(); + report_count = (report_count + 1) % SENSOR_REPORT_EVERY; + + double value; + char buffer[64]; + + // Pre-read hook + for (unsigned char i=0; i<_sensors.size(); i++) { + _sensors[i]->pre(); + if (!_sensors[i]->status()) { + DEBUG_MSG("[SENSOR] Error reading data from %s (error: %d)\n", + _sensors[i]->name().c_str(), + _sensors[i]->error() + ); + } + } + + // Get readings + for (unsigned char i=0; i<_magnitudes.size(); i++) { + + sensor_magnitude_t magnitude = _magnitudes[i]; + + if (magnitude.sensor->status()) { + + unsigned char decimals = _sensorDecimals(magnitude.type); + + value = magnitude.sensor->value(magnitude.local); + magnitude.filter->add(value); + value = _sensorProcess(magnitude.type, value); + _magnitudes[i].current = value; + + // Debug + /* + { + dtostrf(value, 1-sizeof(buffer), decimals, buffer); + DEBUG_MSG("[SENSOR] %s - %s: %s%s\n", + magnitude.sensor->name().c_str(), + _sensorTopic(magnitude.type).c_str(), + buffer, + _sensorUnits(magnitude.type).c_str() + ); + } + */ + + if (report_count == 0) { + + double value = magnitude.filter->result(); + value = _sensorProcess(magnitude.type, value); + _magnitudes[i].filtered = value; + magnitude.filter->reset(); + dtostrf(value, 1-sizeof(buffer), decimals, buffer); + + #if MQTT_SUPPORT + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { + mqttSend(_sensorTopic(magnitude.type).c_str(), magnitude.global, buffer); + } else { + mqttSend(_sensorTopic(magnitude.type).c_str(), buffer); + } + #endif + + #if INFLUXDB_SUPPORT + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { + idbSend(_sensorTopic(magnitude.type).c_str(), magnitude.global, buffer); + } else { + idbSend(_sensorTopic(magnitude.type).c_str(), buffer); + } + #endif + + #if DOMOTICZ_SUPPORT + // TODO + #endif + + } + } + } + + // Post-read hook + for (unsigned char i=0; i<_sensors.size(); i++) { + _sensors[i]->post(); + } + + #if WEB_SUPPORT + wsSend(_sensorWebSocketOnSend); + #endif + + } + + +} diff --git a/code/espurna/sensors/SensorBase.h b/code/espurna/sensors/SensorBase.h new file mode 100644 index 00000000..c8792ff7 --- /dev/null +++ b/code/espurna/sensors/SensorBase.h @@ -0,0 +1,70 @@ +// ----------------------------------------------------------------------------- +// Median Filter +// ----------------------------------------------------------------------------- + +#pragma once + +typedef enum magnitude_t { + + MAGNITUDE_NONE = 0, + + MAGNITUDE_TEMPERATURE, + MAGNITUDE_HUMIDITY, + MAGNITUDE_PRESSURE, + + MAGNITUDE_ACTIVE_POWER, + MAGNITUDE_APPARENT_POWER, + MAGNITUDE_REACTIVE_POWER, + MAGNITUDE_VOLTAGE_POWER, + MAGNITUDE_CURRENT_POWER, + MAGNITUDE_ENERGY_POWER, + MAGNITUDE_POWER_FACTOR, + + MAGNITUDE_ANALOG, + MAGNITUDE_EVENTS, + + MAGNITUDE_MAX, + +} magnitude_t; + +class SensorBase { + + public: + + SensorBase() { + } + + ~SensorBase() { + } + + // Pre-read hook (usually to populate registers with up-to-date data) + virtual void pre(); + + // Post-read hook (usually to reset things) + virtual void post(); + + // Return sensor status (true for ready) + virtual bool status(); + + // Return sensor last internal error + virtual int error(); + + // Number of available slots + virtual unsigned char count(); + + // Descriptive name of the sensor + virtual String name(); + + // Descriptive name of the slot # index + virtual String slot(unsigned char index); + + // Type for slot # index + virtual magnitude_t type(unsigned char index); + + // Current value for slot # index + virtual double value(unsigned char index); + + + private: + +}; diff --git a/code/espurna/sensors/SensorDHT.h b/code/espurna/sensors/SensorDHT.h new file mode 100644 index 00000000..6bb92d10 --- /dev/null +++ b/code/espurna/sensors/SensorDHT.h @@ -0,0 +1,194 @@ +// ----------------------------------------------------------------------------- +// DHT Sensor +// ----------------------------------------------------------------------------- + +#pragma once + +#include "Arduino.h" +#include "SensorBase.h" + +#define DHT_MAX_DATA 5 +#define DHT_MAX_ERRORS 5 +#define DHT_MIN_INTERVAL 2000 +#define DHT_OK 0 +#define DHT_CHECKSUM_ERROR -1 +#define DHT_TIMEOUT_ERROR -2 + +#define DHT11 11 +#define DHT22 22 +#define DHT21 21 +#define AM2301 21 + +class SensorDHT : public SensorBase { + + public: + + SensorDHT(unsigned char gpio, unsigned char type): SensorBase() { + _gpio = gpio; + _type = type; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + + if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) { + _error = 0; + return; + } + + unsigned long low = 0; + unsigned long high = 0; + + unsigned char dhtData[DHT_MAX_DATA] = {0}; + unsigned char byteInx = 0; + unsigned char bitInx = 7; + + // Send start signal to DHT sensor + if (++_errors > DHT_MAX_ERRORS) { + _errors = 0; + digitalWrite(_gpio, HIGH); + delay(250); + } + pinMode(_gpio, OUTPUT); + noInterrupts(); + digitalWrite(_gpio, LOW); + delayMicroseconds(500); + digitalWrite(_gpio, HIGH); + delayMicroseconds(40); + pinMode(_gpio, INPUT_PULLUP); + + // No errors, read the 40 data bits + for( int k = 0; k < 41; k++ ) { + + // Starts new data transmission with >50us low signal + low = _signal(56, LOW); + if (low == 0) { + _error = DHT_TIMEOUT_ERROR; + return; + } + + // Check to see if after >70us rx data is a 0 or a 1 + high = _signal(75, HIGH); + if (high == 0) { + _error = DHT_TIMEOUT_ERROR; + return; + } + + // Skip the first bit + if (k == 0) continue; + + // add the current read to the output data + // since all dhtData array where set to 0 at the start, + // only look for "1" (>28us us) + if (high > low) dhtData[byteInx] |= (1 << bitInx); + + // index to next byte + if (bitInx == 0) { + bitInx = 7; + ++byteInx; + } else { + --bitInx; + } + + } + + interrupts(); + + // Verify checksum + if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) { + _error = DHT_CHECKSUM_ERROR; + return; + } + + // Get humidity from Data[0] and Data[1] + if (_type == DHT11) { + _humidity = dhtData[0]; + } else { + _humidity = dhtData[0] * 256 + dhtData[1]; + _humidity /= 10; + } + + // Get temp from Data[2] and Data[3] + if (_type == DHT11) { + _temperature = dhtData[2]; + } else { + _temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3]; + _temperature /= 10; + if (dhtData[2] & 0x80) _temperature *= -1; + } + + _last_ok = millis(); + _errors = 0; + _error = 0; + + } + + // Post-read hook (usually to reset things) + void post() { + + } + + // Return sensor status (true for ready) + bool status() { + return (_last_ok > 0) & (_error == 0); + } + + // Return sensor last internal error + int error() { + return _error; + } + + // Number of available slots + unsigned char count() { + return 2; + } + + // Descriptive name of the sensor + String name() { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", _type, _gpio); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return name(); + } + + // Type for slot # index + magnitude_t type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + return 0; + } + + + private: + + unsigned long _signal(int usTimeOut, bool state) { + unsigned long uSec = 1; + while (digitalRead(_gpio) == state) { + if (++uSec > usTimeOut) return 0; + delayMicroseconds(1); + } + return uSec; + } + + unsigned char _gpio; + unsigned char _type; + int _error; + + unsigned long _last_ok = 0; + unsigned char _errors = 0; + + double _temperature; + unsigned int _humidity; + +}; diff --git a/code/espurna/ws.ino b/code/espurna/ws.ino index 63becbb4..2706db5b 100644 --- a/code/espurna/ws.ino +++ b/code/espurna/ws.ino @@ -339,8 +339,8 @@ void _wsStart(uint32_t client_id) { root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); root["webPort"] = getSetting("webPort", WEB_PORT).toInt(); - root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt(); - root["tmpCorrection"] = getSetting("tmpCorrection", TEMPERATURE_CORRECTION).toFloat(); + root["tmpUnits"] = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); + root["tmpCorrection"] = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat(); // Callbacks for (unsigned char i = 0; i < _ws_on_send_callbacks.size(); i++) { diff --git a/code/html/custom.css b/code/html/custom.css index feecc5f5..ffe18a51 100644 --- a/code/html/custom.css +++ b/code/html/custom.css @@ -93,6 +93,9 @@ div.hint { font-size: 80%; color: #ccc; } +div.hint.inline { + margin-top: 6px; +} .break { margin-top: 5px; } diff --git a/code/html/custom.js b/code/html/custom.js index 31fd5104..fe36c6ac 100644 --- a/code/html/custom.js +++ b/code/html/custom.js @@ -27,6 +27,12 @@ function initMessages() { messages[10] = "Session expired, please reload page..."; } +function sensorType(type) { + if (type == 1) return "Temperature"; + if (type == 2) return "Humidity"; + return null; +} + // ----------------------------------------------------------------------------- // Utils // ----------------------------------------------------------------------------- @@ -422,6 +428,24 @@ function addRelayGroup() { } +function initSensors(data) { + + // check if already initialized + var done = $("#sensors > div").length; + if (done > 0) return; + + // add templates + var template = $("#sensorTemplate").children(); + for (var i=0; i +
+
+
@@ -365,13 +368,13 @@
-
+
Celsius (°C)
Fahrenheit (°F)
-
+
 
@@ -1146,6 +1149,16 @@
+
+
+ +
+ +
+
+
+
+