Browse Source

sensor: streamline correction settings (#2265)

* wip

* template common operation

* more

* ui

* fix naming issues

* remove old constraints

* oops

* dummy sensor

* rearrange bmx280 magnitudes
mcspr-patch-1
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
d9cb35f781
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 20268 additions and 19925 deletions
  1. +19
    -12
      code/espurna/config/sensors.h
  2. +3
    -0
      code/espurna/config/types.h
  3. BIN
      code/espurna/data/index.all.html.gz
  4. BIN
      code/espurna/data/index.curtain.html.gz
  5. BIN
      code/espurna/data/index.light.html.gz
  6. BIN
      code/espurna/data/index.lightfox.html.gz
  7. BIN
      code/espurna/data/index.rfbridge.html.gz
  8. BIN
      code/espurna/data/index.rfm69.html.gz
  9. BIN
      code/espurna/data/index.sensor.html.gz
  10. BIN
      code/espurna/data/index.small.html.gz
  11. BIN
      code/espurna/data/index.thermostat.html.gz
  12. +1
    -0
      code/espurna/relay.cpp
  13. +313
    -60
      code/espurna/sensor.cpp
  14. +10
    -8
      code/espurna/sensor.h
  15. +15
    -15
      code/espurna/sensors/BMX280Sensor.h
  16. +96
    -0
      code/espurna/sensors/DummySensor.h
  17. +3094
    -3109
      code/espurna/static/index.all.html.gz.h
  18. +1690
    -1689
      code/espurna/static/index.curtain.html.gz.h
  19. +2028
    -2027
      code/espurna/static/index.light.html.gz.h
  20. +1670
    -1669
      code/espurna/static/index.lightfox.html.gz.h
  21. +1677
    -1675
      code/espurna/static/index.rfbridge.html.gz.h
  22. +3028
    -3026
      code/espurna/static/index.rfm69.html.gz.h
  23. +2502
    -2518
      code/espurna/static/index.sensor.html.gz.h
  24. +2373
    -2371
      code/espurna/static/index.small.html.gz.h
  25. +1668
    -1666
      code/espurna/static/index.thermostat.html.gz.h
  26. +61
    -59
      code/html/custom.js
  27. +20
    -21
      code/html/index.html

+ 19
- 12
code/espurna/config/sensors.h View File

@ -45,18 +45,10 @@
#define SENSOR_POWER_CHECK_STATUS 1 // If set to 1 the reported power/current/energy will be 0 if the relay[0] is OFF #define SENSOR_POWER_CHECK_STATUS 1 // If set to 1 the reported power/current/energy will be 0 if the relay[0] is OFF
#endif #endif
#ifndef SENSOR_TEMPERATURE_CORRECTION
#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction
#endif
#ifndef TEMPERATURE_MIN_CHANGE #ifndef TEMPERATURE_MIN_CHANGE
#define TEMPERATURE_MIN_CHANGE 0.0 // Minimum temperature change to report #define TEMPERATURE_MIN_CHANGE 0.0 // Minimum temperature change to report
#endif #endif
#ifndef SENSOR_HUMIDITY_CORRECTION
#define SENSOR_HUMIDITY_CORRECTION 0.0 // Offset correction
#endif
#ifndef HUMIDITY_MIN_CHANGE #ifndef HUMIDITY_MIN_CHANGE
#define HUMIDITY_MIN_CHANGE 0.0 // Minimum humidity change to report #define HUMIDITY_MIN_CHANGE 0.0 // Minimum humidity change to report
#endif #endif
@ -89,6 +81,25 @@
#define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS) #define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS)
#endif #endif
// -----------------------------------------------------------------------------
// Magnitude offset correction
// -----------------------------------------------------------------------------
#ifndef SENSOR_TEMPERATURE_CORRECTION
#define SENSOR_TEMPERATURE_CORRECTION 0.0
#endif
#ifndef SENSOR_HUMIDITY_CORRECTION
#define SENSOR_HUMIDITY_CORRECTION 0.0
#endif
#ifndef SENSOR_PRESSURE_CORRECTION
#define SENSOR_PRESSURE_CORRECTION 0.0
#endif
#ifndef SENSOR_LUX_CORRECTION
#define SENSOR_LUX_CORRECTION 0.0
#endif
// ============================================================================= // =============================================================================
// Specific data for each sensor // Specific data for each sensor
@ -771,10 +782,6 @@
// Enable support by passing LDR_SUPPORT=1 build flag // Enable support by passing LDR_SUPPORT=1 build flag
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#ifndef SENSOR_LUX_CORRECTION
#define SENSOR_LUX_CORRECTION 0.0 // Offset correction
#endif
#ifndef LDR_SUPPORT #ifndef LDR_SUPPORT
#define LDR_SUPPORT 0 #define LDR_SUPPORT 0
#endif #endif


+ 3
- 0
code/espurna/config/types.h View File

@ -384,6 +384,9 @@
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use #define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated #define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error #define SENSOR_ERROR_OTHER 99 // Any other error
#define SENSOR_ERROR_MAX 9
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Telnet server // Telnet server
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------


BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.curtain.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.lightfox.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


BIN
code/espurna/data/index.thermostat.html.gz View File


+ 1
- 0
code/espurna/relay.cpp View File

@ -885,6 +885,7 @@ void _relayWebSocketUpdate(JsonObject& root) {
JsonArray& status = state.createNestedArray("status"); JsonArray& status = state.createNestedArray("status");
JsonArray& lock = state.createNestedArray("lock"); JsonArray& lock = state.createNestedArray("lock");
// Note: we use byte instead of bool to ever so slightly compress json output
for (unsigned char i=0; i<relayCount(); i++) { for (unsigned char i=0; i<relayCount(); i++) {
status.add<uint8_t>(_relays[i].target_status); status.add<uint8_t>(_relays[i].target_status);
lock.add(_relays[i].lock); lock.add(_relays[i].lock);


+ 313
- 60
code/espurna/sensor.cpp View File

@ -236,7 +236,7 @@ struct sensor_magnitude_t {
}; };
unsigned char sensor_magnitude_t::_counts[MAGNITUDE_MAX];
unsigned char sensor_magnitude_t::_counts[MAGNITUDE_MAX] = {0};
namespace sensor { namespace sensor {
@ -332,6 +332,30 @@ void Energy::reset() {
} // namespace sensor } // namespace sensor
// -----------------------------------------------------------------------------
// Configuration
// -----------------------------------------------------------------------------
constexpr double _magnitudeCorrection(unsigned char type) {
return (
(MAGNITUDE_TEMPERATURE == type) ? (SENSOR_TEMPERATURE_CORRECTION) :
(MAGNITUDE_HUMIDITY == type) ? (SENSOR_HUMIDITY_CORRECTION) :
(MAGNITUDE_LUX == type) ? (SENSOR_LUX_CORRECTION) :
(MAGNITUDE_PRESSURE == type) ? (SENSOR_PRESSURE_CORRECTION) :
0.0
);
}
constexpr bool _magnitudeCanUseCorrection(unsigned char type) {
return (
(MAGNITUDE_TEMPERATURE == type) ? (true) :
(MAGNITUDE_HUMIDITY == type) ? (true) :
(MAGNITUDE_LUX == type) ? (true) :
(MAGNITUDE_PRESSURE == type) ? (true) :
false
);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Energy persistence // Energy persistence
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -831,16 +855,89 @@ double _magnitudeProcess(const sensor_magnitude_t& magnitude, double value) {
} }
String _magnitudeDescription(const sensor_magnitude_t& magnitude) {
return magnitude.sensor->description(magnitude.slot);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// do `callback(type)` for each present magnitude
template<typename T>
void _magnitudeForEachCounted(T callback) {
for (unsigned char type = MAGNITUDE_NONE + 1; type < MAGNITUDE_MAX; ++type) {
if (sensor_magnitude_t::counts(type)) {
callback(type);
}
}
}
// check if `callback(type)` returns `true` at least once
template<typename T>
bool _magnitudeForEachCountedCheck(T callback) {
for (unsigned char type = MAGNITUDE_NONE + 1; type < MAGNITUDE_MAX; ++type) {
if (sensor_magnitude_t::counts(type) && callback(type)) {
return true;
}
}
return false;
}
// do `callback(type)` for each error type
template<typename T>
void _sensorForEachError(T callback) {
for (unsigned char error = SENSOR_ERROR_OK; error < SENSOR_ERROR_MAX; ++error) {
callback(error);
}
}
const char * const _magnitudeSettingsPrefix(unsigned char type) {
switch (type) {
case MAGNITUDE_TEMPERATURE: return "tmp";
case MAGNITUDE_HUMIDITY: return "hum";
case MAGNITUDE_PRESSURE: return "press";
case MAGNITUDE_CURRENT: return "curr";
case MAGNITUDE_VOLTAGE: return "volt";
case MAGNITUDE_POWER_ACTIVE: return "pwrP";
case MAGNITUDE_POWER_APPARENT: return "pwrQ";
case MAGNITUDE_POWER_REACTIVE: return "pwrModS";
case MAGNITUDE_POWER_FACTOR: return "pwrPF";
case MAGNITUDE_ENERGY: return "ene";
case MAGNITUDE_ENERGY_DELTA: return "eneDelta";
case MAGNITUDE_ANALOG: return "analog";
case MAGNITUDE_DIGITAL: return "digital";
case MAGNITUDE_EVENT: return "event";
case MAGNITUDE_PM1dot0: return "pm1dot0";
case MAGNITUDE_PM2dot5: return "pm1dot5";
case MAGNITUDE_PM10: return "pm10";
case MAGNITUDE_CO2: return "co2";
case MAGNITUDE_LUX: return "lux";
case MAGNITUDE_UVA: return "uva";
case MAGNITUDE_UVB: return "uvb";
case MAGNITUDE_UVI: return "uvi";
case MAGNITUDE_DISTANCE: return "distance";
case MAGNITUDE_HCHO: return "hcho";
case MAGNITUDE_GEIGER_CPM: return "gcpm";
case MAGNITUDE_GEIGER_SIEVERT: return "gsiev";
case MAGNITUDE_COUNT: return "count";
case MAGNITUDE_NO2: return "no2";
case MAGNITUDE_CO: return "co";
case MAGNITUDE_RESISTANCE: return "res";
case MAGNITUDE_PH: return "ph";
default: return nullptr;
}
}
bool _sensorMatchKeyPrefix(const char * key) { bool _sensorMatchKeyPrefix(const char * key) {
if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true; if (strncmp(key, "sns", 3) == 0) return true;
if (strncmp(key, "tmp", 3) == 0) return true;
if (strncmp(key, "hum", 3) == 0) return true;
if (strncmp(key, "ene", 3) == 0) return true;
if (strncmp(key, "lux", 3) == 0) return true;
return false;
if (strncmp(key, "pwr", 3) == 0) return true;
return _magnitudeForEachCountedCheck([key](unsigned char type) {
const char* const prefix { _magnitudeSettingsPrefix(type) };
return (strncmp(prefix, key, strlen(prefix)) == 0);
});
} }
const String _sensorQueryDefault(const String& key) { const String _sensorQueryDefault(const String& key) {
@ -935,22 +1032,186 @@ void sensorWebSocketMagnitudes(JsonObject& root, const String& prefix) {
} }
} }
String sensorError(unsigned char error) {
const __FlashStringHelper* result = nullptr;
switch (error) {
case SENSOR_ERROR_OK:
result = F("OK");
break;
case SENSOR_ERROR_OUT_OF_RANGE:
result = F("Out of Range");
break;
case SENSOR_ERROR_WARM_UP:
result = F("Warming Up");
break;
case SENSOR_ERROR_TIMEOUT:
result = F("Timeout");
break;
case SENSOR_ERROR_UNKNOWN_ID:
result = F("Unknown ID");
break;
case SENSOR_ERROR_CRC:
result = F("CRC / Data Error");
break;
case SENSOR_ERROR_I2C:
result = F("I2C Error");
break;
case SENSOR_ERROR_GPIO_USED:
result = F("GPIO Already Used");
break;
case SENSOR_ERROR_CALIBRATION:
result = F("Calibration Error");
break;
default:
case SENSOR_ERROR_OTHER:
result = F("Other / Unknown Error");
break;
}
return result;
}
String magnitudeName(unsigned char type) {
const __FlashStringHelper* result = nullptr;
switch (type) {
case MAGNITUDE_TEMPERATURE:
result = F("Temperature");
break;
case MAGNITUDE_HUMIDITY:
result = F("Humidity");
break;
case MAGNITUDE_PRESSURE:
result = F("Pressure");
break;
case MAGNITUDE_CURRENT:
result = F("Current");
break;
case MAGNITUDE_VOLTAGE:
result = F("Voltage");
break;
case MAGNITUDE_POWER_ACTIVE:
result = F("Active Power");
break;
case MAGNITUDE_POWER_APPARENT:
result = F("Apparent Power");
break;
case MAGNITUDE_POWER_REACTIVE:
result = F("Reactive Power");
break;
case MAGNITUDE_POWER_FACTOR:
result = F("Power Factor");
break;
case MAGNITUDE_ENERGY:
result = F("Energy");
break;
case MAGNITUDE_ENERGY_DELTA:
result = F("Energy (delta)");
break;
case MAGNITUDE_ANALOG:
result = F("Analog");
break;
case MAGNITUDE_DIGITAL:
result = F("Digital");
break;
case MAGNITUDE_EVENT:
result = F("Event");
break;
case MAGNITUDE_PM1dot0:
result = F("PM1.0");
break;
case MAGNITUDE_PM2dot5:
result = F("PM2.5");
break;
case MAGNITUDE_PM10:
result = F("PM10");
break;
case MAGNITUDE_CO2:
result = F("CO2");
break;
case MAGNITUDE_LUX:
result = F("Lux");
break;
case MAGNITUDE_UVA:
result = F("UVA");
break;
case MAGNITUDE_UVB:
result = F("UVB");
break;
case MAGNITUDE_UVI:
result = F("UVI");
break;
case MAGNITUDE_DISTANCE:
result = F("Distance");
break;
case MAGNITUDE_HCHO:
result = F("HCHO");
break;
case MAGNITUDE_GEIGER_CPM:
case MAGNITUDE_GEIGER_SIEVERT:
result = F("Local Dose Rate");
break;
case MAGNITUDE_COUNT:
result = F("Count");
break;
case MAGNITUDE_NO2:
result = F("NO2");
break;
case MAGNITUDE_CO:
result = F("CO");
break;
case MAGNITUDE_RESISTANCE:
result = F("Resistance");
break;
case MAGNITUDE_PH:
result = F("pH");
break;
case MAGNITUDE_NONE:
default:
break;
}
return String(result);
}
void _sensorWebSocketOnVisible(JsonObject& root) { void _sensorWebSocketOnVisible(JsonObject& root) {
root["snsVisible"] = 1; root["snsVisible"] = 1;
for (auto& magnitude : _magnitudes) {
if (magnitude.type == MAGNITUDE_TEMPERATURE) root["temperatureVisible"] = 1;
if (magnitude.type == MAGNITUDE_HUMIDITY) root["humidityVisible"] = 1;
#if MICS2710_SUPPORT || MICS5525_SUPPORT
if (magnitude.type == MAGNITUDE_CO || magnitude.type == MAGNITUDE_NO2) root["micsVisible"] = 1;
#endif
}
// prepare available magnitude types
JsonArray& magnitudes = root.createNestedArray("snsMagnitudes");
_magnitudeForEachCounted([&magnitudes](unsigned char type) {
JsonArray& tuple = magnitudes.createNestedArray();
tuple.add(type);
tuple.add(_magnitudeSettingsPrefix(type));
tuple.add(magnitudeName(type));
});
// and available error types
JsonArray& errors = root.createNestedArray("snsErrors");
_sensorForEachError([&errors](unsigned char error) {
JsonArray& tuple = errors.createNestedArray();
tuple.add(error);
tuple.add(sensorError(error));
});
} }
void _sensorWebSocketMagnitudesConfig(JsonObject& root) { void _sensorWebSocketMagnitudesConfig(JsonObject& root) {
// retrieve per-type ...Correction settings, when available
_magnitudeForEachCounted([&root](unsigned char type) {
if (_magnitudeCanUseCorrection(type)) {
auto key = String(_magnitudeSettingsPrefix(type)) + F("Correction");
root[key] = getSetting(key, _magnitudeCorrection(type));
}
});
JsonObject& magnitudes = root.createNestedObject("magnitudesConfig"); JsonObject& magnitudes = root.createNestedObject("magnitudesConfig");
uint8_t size = 0; uint8_t size = 0;
@ -959,17 +1220,16 @@ void _sensorWebSocketMagnitudesConfig(JsonObject& root) {
JsonArray& units = magnitudes.createNestedArray("units"); JsonArray& units = magnitudes.createNestedArray("units");
JsonArray& description = magnitudes.createNestedArray("description"); JsonArray& description = magnitudes.createNestedArray("description");
for (unsigned char i=0; i<magnitudeCount(); i++) {
for (auto& magnitude : _magnitudes) {
auto& magnitude = _magnitudes[i];
// TODO: we don't display event for some reason?
if (magnitude.type == MAGNITUDE_EVENT) continue; if (magnitude.type == MAGNITUDE_EVENT) continue;
++size; ++size;
// Note: we use int instead of bool to ever so slightly compress json output
index.add<uint8_t>(magnitude.index_global); index.add<uint8_t>(magnitude.index_global);
type.add<uint8_t>(magnitude.type); type.add<uint8_t>(magnitude.type);
units.add(_magnitudeUnits(magnitude)); units.add(_magnitudeUnits(magnitude));
description.add(magnitude.sensor->description(magnitude.slot));
description.add(_magnitudeDescription(magnitude));
} }
@ -1016,10 +1276,7 @@ void _sensorWebSocketSendData(JsonObject& root) {
void _sensorWebSocketOnConnected(JsonObject& root) { void _sensorWebSocketOnConnected(JsonObject& root) {
for (unsigned char i=0; i<_sensors.size(); i++) {
BaseSensor * sensor = _sensors[i];
UNUSED(sensor);
for (auto* sensor [[gnu::unused]] : _sensors) {
#if EMON_ANALOG_SUPPORT #if EMON_ANALOG_SUPPORT
if (sensor->getID() == SENSOR_EMON_ANALOG_ID) { if (sensor->getID() == SENSOR_EMON_ANALOG_ID) {
@ -1069,18 +1326,17 @@ void _sensorWebSocketOnConnected(JsonObject& root) {
} }
#endif #endif
}
if (sensor_magnitude_t::counts(MAGNITUDE_TEMPERATURE)) {
root["tmpCorrection"] = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION);
}
if (sensor_magnitude_t::counts(MAGNITUDE_HUMIDITY)) {
root["humCorrection"] = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION);
}
#if MICS2710_SUPPORT || MICS5525_SUPPORT
switch (sensor->getID()) {
case SENSOR_MICS2710_ID:
case SENSOR_MICS5525_ID:
root["micsVisible"] = 1;
break;
default:
break;
}
#endif
if (sensor_magnitude_t::counts(MAGNITUDE_LUX)) {
root["luxCorrection"] = getSetting("luxCorrection", SENSOR_LUX_CORRECTION);
} }
if (magnitudeCount()) { if (magnitudeCount()) {
@ -1098,9 +1354,7 @@ void _sensorWebSocketOnConnected(JsonObject& root) {
void _sensorAPISetup() { void _sensorAPISetup() {
for (unsigned char magnitude_id=0; magnitude_id<_magnitudes.size(); magnitude_id++) {
auto& magnitude = _magnitudes.at(magnitude_id);
for (auto& magnitude : _magnitudes) {
String topic = magnitudeTopic(magnitude.type); String topic = magnitudeTopic(magnitude.type);
if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) topic = topic + "/" + String(magnitude.index_global); if (SENSOR_USE_INDEX || (sensor_magnitude_t::counts(magnitude.type) > 1)) topic = topic + "/" + String(magnitude.index_global);
@ -1173,8 +1427,9 @@ void _sensorInitCommands() {
dtostrf(magnitude.reported, 1, magnitude.decimals, reported); dtostrf(magnitude.reported, 1, magnitude.decimals, reported);
DEBUG_MSG_P(PSTR("[SENSOR] %2u * %s/%u @ %s (last:%s, reported:%s)\n"), DEBUG_MSG_P(PSTR("[SENSOR] %2u * %s/%u @ %s (last:%s, reported:%s)\n"),
index, index,
magnitudeTopic(magnitude.type).c_str(), magnitude.index_global,
magnitude.sensor->description(magnitude.slot).c_str(),
magnitudeTopic(magnitude.type).c_str(),
magnitude.index_global,
_magnitudeDescription(magnitude).c_str(),
last, reported last, reported
); );
} }
@ -1803,6 +2058,7 @@ void _sensorLoad() {
_sensors.push_back(sensor); _sensors.push_back(sensor);
} }
#endif #endif
} }
void _sensorReport(unsigned char index, double value) { void _sensorReport(unsigned char index, double value) {
@ -1874,7 +2130,7 @@ void _sensorInit() {
if (!_sensors[i]->ready()) { if (!_sensors[i]->ready()) {
if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error()); if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error());
_sensors_ready = false; _sensors_ready = false;
continue;
break;
} }
// Initialize sensor magnitudes // Initialize sensor magnitudes
@ -2039,11 +2295,6 @@ void _sensorConfigure() {
const auto pwrUnits = getSetting("pwrUnits", SENSOR_POWER_UNITS); const auto pwrUnits = getSetting("pwrUnits", SENSOR_POWER_UNITS);
const auto eneUnits = getSetting("eneUnits", SENSOR_ENERGY_UNITS); const auto eneUnits = getSetting("eneUnits", SENSOR_ENERGY_UNITS);
// TODO: map MAGNITUDE_... type to a specific string? nvm the preprocessor flags, just focus on settings
const auto tmpCorrection = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION);
const auto humCorrection = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION);
const auto luxCorrection = getSetting("luxCorrection", SENSOR_LUX_CORRECTION);
for (unsigned char index = 0; index < _magnitudes.size(); ++index) { for (unsigned char index = 0; index < _magnitudes.size(); ++index) {
auto& magnitude = _magnitudes.at(index); auto& magnitude = _magnitudes.at(index);
@ -2093,10 +2344,8 @@ void _sensorConfigure() {
magnitude, magnitude,
getSetting({"tmpUnits", magnitude.index_global}, tmpUnits) getSetting({"tmpUnits", magnitude.index_global}, tmpUnits)
); );
magnitude.correction = getSetting({"tmpCorrection", magnitude.index_global}, tmpCorrection);
break; break;
case MAGNITUDE_HUMIDITY: case MAGNITUDE_HUMIDITY:
magnitude.correction = getSetting({"humCorrection", magnitude.index_global}, humCorrection);
break; break;
case MAGNITUDE_POWER_ACTIVE: case MAGNITUDE_POWER_ACTIVE:
magnitude.units = _magnitudeUnitFilter( magnitude.units = _magnitudeUnitFilter(
@ -2110,14 +2359,19 @@ void _sensorConfigure() {
getSetting({"eneUnits", magnitude.index_global}, eneUnits) getSetting({"eneUnits", magnitude.index_global}, eneUnits)
); );
break; break;
case MAGNITUDE_LUX:
magnitude.correction = getSetting({"luxCorrection", magnitude.index_global}, luxCorrection);
break;
default: default:
magnitude.units = magnitude.sensor->units(magnitude.slot); magnitude.units = magnitude.sensor->units(magnitude.slot);
break; break;
} }
// some magnitudes allow to be corrected with an offset
{
if (_magnitudeCanUseCorrection(magnitude.type)) {
auto key = String(_magnitudeSettingsPrefix(magnitude.type)) + F("Correction");
magnitude.correction = getSetting({key, magnitude.index_global}, getSetting(key, _magnitudeCorrection(magnitude.type)));
}
}
// some sensors can override decimal values if sensor has more precision than default // some sensors can override decimal values if sensor has more precision than default
{ {
signed char decimals = magnitude.sensor->decimals(magnitude.units); signed char decimals = magnitude.sensor->decimals(magnitude.units);
@ -2174,17 +2428,9 @@ unsigned char magnitudeCount() {
return _magnitudes.size(); return _magnitudes.size();
} }
String magnitudeName(unsigned char index) {
if (index < _magnitudes.size()) {
sensor_magnitude_t magnitude = _magnitudes[index];
return magnitude.sensor->description(magnitude.slot);
}
return String();
}
unsigned char magnitudeType(unsigned char index) { unsigned char magnitudeType(unsigned char index) {
if (index < _magnitudes.size()) { if (index < _magnitudes.size()) {
return int(_magnitudes[index].type);
return _magnitudes[index].type;
} }
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
@ -2198,11 +2444,18 @@ double magnitudeValue(unsigned char index) {
unsigned char magnitudeIndex(unsigned char index) { unsigned char magnitudeIndex(unsigned char index) {
if (index < _magnitudes.size()) { if (index < _magnitudes.size()) {
return int(_magnitudes[index].index_global);
return _magnitudes[index].index_global;
} }
return 0; return 0;
} }
String magnitudeDescription(unsigned char index) {
if (index < _magnitudes.size()) {
return _magnitudeDescription(_magnitudes[index]);
}
return String();
}
String magnitudeTopicIndex(unsigned char index) { String magnitudeTopicIndex(unsigned char index) {
char topic[32] = {0}; char topic[32] = {0};
if (index < _magnitudes.size()) { if (index < _magnitudes.size()) {
@ -2403,7 +2656,7 @@ void sensorLoop() {
char buffer[64]; char buffer[64];
dtostrf(value_show, 1, magnitude.decimals, buffer); dtostrf(value_show, 1, magnitude.decimals, buffer);
DEBUG_MSG_P(PSTR("[SENSOR] %s - %s: %s%s\n"), DEBUG_MSG_P(PSTR("[SENSOR] %s - %s: %s%s\n"),
magnitude.sensor->description(magnitude.slot).c_str(),
_magnitudeDescription(magnitude).c_str(),
magnitudeTopic(magnitude.type).c_str(), magnitudeTopic(magnitude.type).c_str(),
buffer, buffer,
_magnitudeUnits(magnitude).c_str() _magnitudeUnits(magnitude).c_str()


+ 10
- 8
code/espurna/sensor.h View File

@ -130,24 +130,26 @@ struct Energy {
BrokerDeclare(SensorReadBroker, void(const String&, unsigned char, double, const char*)); BrokerDeclare(SensorReadBroker, void(const String&, unsigned char, double, const char*));
BrokerDeclare(SensorReportBroker, void(const String&, unsigned char, double, const char*)); BrokerDeclare(SensorReportBroker, void(const String&, unsigned char, double, const char*));
String magnitudeName(unsigned char index);
String magnitudeUnits(unsigned char index); String magnitudeUnits(unsigned char index);
String magnitudeDescription(unsigned char index);
unsigned char magnitudeType(unsigned char index); unsigned char magnitudeType(unsigned char index);
// XXX: without param name it is kind of vague what exactly unsigned char is
// consider using index instead of type or adding stronger param type
String magnitudeTopic(unsigned char type);
unsigned char magnitudeIndex(unsigned char index);
String magnitudeTopicIndex(unsigned char index);
unsigned char sensorCount();
unsigned char magnitudeCount(); unsigned char magnitudeCount();
double magnitudeValue(unsigned char index); double magnitudeValue(unsigned char index);
unsigned char magnitudeIndex(unsigned char index);
String magnitudeTopicIndex(unsigned char index);
// XXX: without param name it is kind of vague what exactly unsigned char is
// consider adding stronger param type e.g. enum class
String magnitudeTopic(unsigned char type);
String magnitudeName(unsigned char type);
String sensorError(unsigned char error);
void sensorWebSocketMagnitudes(JsonObject& root, const String& prefix); void sensorWebSocketMagnitudes(JsonObject& root, const String& prefix);
unsigned char sensorCount();
void sensorSetup(); void sensorSetup();
void sensorLoop(); void sensorLoop();

+ 15
- 15
code/espurna/sensors/BMX280Sensor.h View File

@ -87,14 +87,14 @@ class BMX280Sensor : public I2CSensor<> {
#if BMX280_TEMPERATURE > 0 #if BMX280_TEMPERATURE > 0
if (index == i++) return MAGNITUDE_TEMPERATURE; if (index == i++) return MAGNITUDE_TEMPERATURE;
#endif #endif
#if BMX280_PRESSURE > 0
if (index == i++) return MAGNITUDE_PRESSURE;
#endif
#if BMX280_HUMIDITY > 0 #if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) { if (_chip == BMX280_CHIP_BME280) {
if (index == i) return MAGNITUDE_HUMIDITY;
if (index == i++) return MAGNITUDE_HUMIDITY;
} }
#endif #endif
#if BMX280_PRESSURE > 0
if (index == i) return MAGNITUDE_PRESSURE;
#endif
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
@ -145,14 +145,14 @@ class BMX280Sensor : public I2CSensor<> {
#if BMX280_TEMPERATURE > 0 #if BMX280_TEMPERATURE > 0
if (index == i++) return _temperature; if (index == i++) return _temperature;
#endif #endif
#if BMX280_PRESSURE > 0
if (index == i++) return _pressure / 100;
#endif
#if BMX280_HUMIDITY > 0 #if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) { if (_chip == BMX280_CHIP_BME280) {
if (index == i) return _humidity;
if (index == i++) return _humidity;
} }
#endif #endif
#if BMX280_PRESSURE > 0
if (index == i) return _pressure / 100;
#endif
return 0; return 0;
} }
@ -231,12 +231,12 @@ class BMX280Sensor : public I2CSensor<> {
#if BMX280_TEMPERATURE > 0 #if BMX280_TEMPERATURE > 0
++_count; ++_count;
#endif #endif
#if BMX280_PRESSURE > 0
++_count;
#endif
#if BMX280_HUMIDITY > 0 #if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) ++_count; if (_chip == BMX280_CHIP_BME280) ++_count;
#endif #endif
#if BMX280_PRESSURE > 0
++_count;
#endif
_readCoefficients(); _readCoefficients();
@ -297,14 +297,14 @@ class BMX280Sensor : public I2CSensor<> {
#if BMX280_TEMPERATURE > 0 #if BMX280_TEMPERATURE > 0
t += (2.3 * BMX280_TEMPERATURE); t += (2.3 * BMX280_TEMPERATURE);
#endif #endif
#if BMX280_PRESSURE > 0
t += (2.3 * BMX280_PRESSURE + 0.575);
#endif
#if BMX280_HUMIDITY > 0 #if BMX280_HUMIDITY > 0
if (_chip == BMX280_CHIP_BME280) { if (_chip == BMX280_CHIP_BME280) {
t += (2.4 * BMX280_HUMIDITY + 0.575); t += (2.4 * BMX280_HUMIDITY + 0.575);
} }
#endif #endif
#if BMX280_PRESSURE > 0
t += (2.3 * BMX280_PRESSURE + 0.575);
#endif
return round(t + 1); // round up return round(t + 1); // round up
@ -416,8 +416,8 @@ class BMX280Sensor : public I2CSensor<> {
unsigned long _measurement_delay; unsigned long _measurement_delay;
bool _run_init = false; bool _run_init = false;
double _temperature = 0; double _temperature = 0;
double _pressure = 0;
double _humidity = 0; double _humidity = 0;
double _pressure = 0;
typedef struct { typedef struct {


+ 96
- 0
code/espurna/sensors/DummySensor.h View File

@ -0,0 +1,96 @@
/*
Example for SENSOR MODULE
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
// In sensor.cpp:
// - #include "sensors/DummySensor.h"
// - add `_sensors.push_back(new DummySensor());` at the end of _sensorLoad();
#include "BaseSensor.h"
struct DummySensor : public BaseSensor {
DummySensor() :
_temperature(25.0),
_humidity(50.0),
_pressure(1000.0),
_lux(0.0)
{
_count = 4;
}
void begin() override {
_ready = true;
_error = SENSOR_ERROR_OK;
}
String description() override {
static String dummy(F("Dummy"));
return dummy;
}
String description(unsigned char) override {
return description();
}
String address(unsigned char) override {
static String dummy(F("/dev/null"));
return dummy;
}
unsigned char type(unsigned char index) override {
switch (index) {
case 0: return MAGNITUDE_TEMPERATURE;
case 1: return MAGNITUDE_HUMIDITY;
case 2: return MAGNITUDE_PRESSURE;
case 3: return MAGNITUDE_LUX;
}
return MAGNITUDE_NONE;
}
double value(unsigned char index) override {
switch (index) {
case 0: return _temperature;
case 1: return _humidity;
case 2: return _pressure;
case 3: return _lux;
}
return 0.0;
}
void pre() override {
++_temperature;
++_humidity;
++_pressure;
++_lux;
if (_temperature >= 40.0) {
_temperature = 0.0;
}
if (_humidity >= 100.0) {
_humidity = 20.0;
}
if (_pressure >= 1024.0) {
_pressure = 980.0;
}
if (_lux >= 100.0) {
_lux = 0.0;
}
}
private:
double _temperature;
double _humidity;
double _pressure;
double _lux;
};

+ 3094
- 3109
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 1690
- 1689
code/espurna/static/index.curtain.html.gz.h
File diff suppressed because it is too large
View File


+ 2028
- 2027
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 1670
- 1669
code/espurna/static/index.lightfox.html.gz.h
File diff suppressed because it is too large
View File


+ 1677
- 1675
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 3028
- 3026
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 2502
- 2518
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 2373
- 2371
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 1668
- 1666
code/espurna/static/index.thermostat.html.gz.h
File diff suppressed because it is too large
View File


+ 61
- 59
code/html/custom.js View File

@ -26,7 +26,11 @@ var filters = [];
<!-- endRemoveIf(!rfm69)--> <!-- endRemoveIf(!rfm69)-->
<!-- removeIf(!sensor)--> <!-- removeIf(!sensor)-->
var magnitudes = [];
var Magnitudes = [];
var MagnitudeErrors = {};
var MagnitudeNames = {};
var MagnitudeTypePrefixes = {};
var MagnitudePrefixTypes = {};
<!-- endRemoveIf(!sensor)--> <!-- endRemoveIf(!sensor)-->
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -45,52 +49,6 @@ function initMessages() {
messages[10] = "Session expired, please reload page..."; messages[10] = "Session expired, please reload page...";
} }
<!-- removeIf(!sensor)-->
function sensorName(id) {
var names = [
"DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15",
"HLW8012", "V9261F", "ECH1560", "Analog", "Digital",
"Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
"SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
"T6613", "TMP3X", "Sonar", "SenseAir", "GeigerTicks", "GeigerCPM",
"NTC", "SDS011", "MICS2710", "MICS5525", "VL53L1X", "VEML6075",
"EZOPH"
];
if (1 <= id && id <= names.length) {
return names[id - 1];
}
return null;
}
function magnitudeType(type) {
var types = [
"Temperature", "Humidity", "Pressure",
"Current", "Voltage", "Active Power", "Apparent Power",
"Reactive Power", "Power Factor", "Energy", "Energy (delta)",
"Analog", "Digital", "Event",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UVA", "UVB", "UV Index", "Distance" , "HCHO",
"Local Dose Rate", "Local Dose Rate",
"Count",
"NO2", "CO", "Resistance", "pH"
];
if (1 <= type && type <= types.length) {
return types[type - 1];
}
return null;
}
function magnitudeError(error) {
var errors = [
"OK", "Out of Range", "Warming Up", "Timeout", "Wrong ID",
"Data Error", "I2C Error", "GPIO Error", "Calibration error"
];
if (0 <= error && error < errors.length) {
return errors[error];
}
return "Error " + error;
}
<!-- endRemoveIf(!sensor)-->
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Utils // Utils
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -453,6 +411,15 @@ function getJson(str) {
} }
} }
function moduleVisible(module) {
if (module == "sch") {
$("li.module-" + module).css("display", "inherit");
$("div.module-" + module).css("display", "flex");
return;
}
$(".module-" + module).css("display", "inherit");
}
<!-- removeIf(!thermostat)--> <!-- removeIf(!thermostat)-->
function checkTempRangeMin() { function checkTempRangeMin() {
var min = parseInt($("#tempRangeMinInput").val(), 10); var min = parseInt($("#tempRangeMinInput").val(), 10);
@ -943,8 +910,8 @@ function createMagnitudeList(data, container, template_name) {
for (var i=0; i<size; ++i) { for (var i=0; i<size; ++i) {
var line = $(template).clone(); var line = $(template).clone();
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(magnitudes[i].description);
$("label", line).html(MagnitudeNames[data.type[i]] + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(Magnitudes[i].description);
$("input", line).attr("tabindex", 40 + i).val(data.idx[i]); $("input", line).attr("tabindex", 40 + i).val(data.idx[i]);
setOriginalsFromValues($("input", line)); setOriginalsFromValues($("input", line));
line.appendTo("#" + container); line.appendTo("#" + container);
@ -1266,11 +1233,11 @@ function initMagnitudes(data) {
for (var i=0; i<size; ++i) { for (var i=0; i<size; ++i) {
var magnitude = { var magnitude = {
"name": magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10),
"name": MagnitudeNames[data.type[i]] + " #" + parseInt(data.index[i], 10),
"units": data.units[i], "units": data.units[i],
"description": data.description[i] "description": data.description[i]
}; };
magnitudes.push(magnitude);
Magnitudes.push(magnitude);
var line = $(template).clone(); var line = $(template).clone();
$("label", line).html(magnitude.name); $("label", line).html(magnitude.name);
@ -1866,8 +1833,48 @@ function processData(data) {
<!-- removeIf(!sensor)--> <!-- removeIf(!sensor)-->
{
var position = key.indexOf("Correction");
if (position > 0 && position === key.length - 10) {
var template = $("#magnitudeCorrectionTemplate > div")[0];
var elem = $(template).clone();
var prefix = key.slice(0, position);
$("label", elem).html(MagnitudeNames[MagnitudePrefixTypes[prefix]]);
$("input", elem).attr("name", key).val(value);
setOriginalsFromValues($("input", elem));
elem.appendTo("#magnitude-corrections");
moduleVisible("magnitude-corrections");
return;
}
}
if ("snsErrors" === key) {
for (var index in value) {
var type = value[index][0];
var name = value[index][1];
MagnitudeErrors[type] = name;
}
return;
}
if ("snsMagnitudes" === key) {
for (var index in value) {
var type = value[index][0];
var prefix = value[index][1];
var name = value[index][2];
MagnitudeNames[type] = name;
MagnitudeTypePrefixes[type] = prefix;
MagnitudePrefixTypes[prefix] = type;
moduleVisible(prefix);
}
return;
}
if ("magnitudesConfig" === key) { if ("magnitudesConfig" === key) {
initMagnitudes(value); initMagnitudes(value);
return;
} }
if ("magnitudes" === key) { if ("magnitudes" === key) {
@ -1877,8 +1884,8 @@ function processData(data) {
var error = value.error[i] || 0; var error = value.error[i] || 0;
var text = (0 === error) var text = (0 === error)
? value.value[i] + magnitudes[i].units
: magnitudeError(error);
? value.value[i] + Magnitudes[i].units
: MagnitudeErrors[error];
inputElem.val(text); inputElem.val(text);
if (value.info !== undefined) { if (value.info !== undefined) {
@ -2102,12 +2109,7 @@ function processData(data) {
var position = key.indexOf("Visible"); var position = key.indexOf("Visible");
if (position > 0 && position === key.length - 7) { if (position > 0 && position === key.length - 7) {
var module = key.slice(0,-7); var module = key.slice(0,-7);
if (module == "sch") {
$("li.module-" + module).css("display", "inherit");
$("div.module-" + module).css("display", "flex");
return;
}
$(".module-" + module).css("display", "inherit");
moduleVisible(module);
return; return;
} }


+ 20
- 21
code/html/index.html View File

@ -1663,7 +1663,7 @@
</select> </select>
</div> </div>
<div class="pure-g module module-temperature">
<div class="pure-g module module-tmp">
<label class="pure-u-1 pure-u-lg-1-4">Temperature units</label> <label class="pure-u-1 pure-u-lg-1-4">Temperature units</label>
<select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4"> <select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="2">Celsius (&deg;C)</option> <option value="2">Celsius (&deg;C)</option>
@ -1672,26 +1672,6 @@
</select> </select>
</div> </div>
<div class="pure-g module module-temperature">
<label class="pure-u-1 pure-u-lg-1-4">Temperature correction</label>
<input name="tmpCorrection" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="-100" step="0.1" max="100" tabindex="18" />
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Temperature correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative.
</div>
</div>
<div class="pure-g module module-humidity">
<label class="pure-u-1 pure-u-lg-1-4">Humidity correction</label>
<input name="humCorrection" class="pure-u-1 pure-u-lg-1-4" type="number" action="reboot" min="-100" step="0.1" max="100" tabindex="18" />
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Humidity correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative.
</div>
</div>
<div class="pure-g module module-mics"> <div class="pure-g module module-mics">
<label class="pure-u-1 pure-u-lg-1-4">Calibrate gas sensor</label> <label class="pure-u-1 pure-u-lg-1-4">Calibrate gas sensor</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="snsResetCalibration" tabindex="55" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="snsResetCalibration" tabindex="55" /></div>
@ -1753,6 +1733,15 @@
<div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to set energy count to 0.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Move this switch to ON and press "Save" to set energy count to 0.</div>
</div> </div>
<div class="pure-g module module-magnitude-corrections">
<legend>Value corrections</legend>
<div class="pure-u-1 pure-u-lg-3-4 hint more">
Correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative.
</div>
<div class="pure-form pure-form-aligned" id="magnitude-corrections">
</div>
</div>
</fieldset> </fieldset>
</div> </div>
@ -1848,6 +1837,16 @@
<!-- Templates --> <!-- Templates -->
<div id="magnitudeCorrectionTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4"></label>
<input name="correction" class="pure-u-1 pure-u-lg-1-4" type="number" step="0.1" data="0"></input>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"></div>
</div>
</div>
<div id="ledConfigTemplate" class="template"> <div id="ledConfigTemplate" class="template">
<div class="pure-form pure-form-aligned"> <div class="pure-form pure-form-aligned">
<legend class="pure-u-1 pure-u-lg-1-4">LED #<span class="id"></span></legend> <legend class="pure-u-1 pure-u-lg-1-4">LED #<span class="id"></span></legend>


Loading…
Cancel
Save