diff --git a/src/ems-esp.ino b/src/ems-esp.ino index aacaf533b..9376d0687 100644 --- a/src/ems-esp.ino +++ b/src/ems-esp.ino @@ -101,6 +101,7 @@ command_t PROGMEM project_cmds[] = { {false, "info", "show data captured on the EMS bus"}, {false, "log ", "set logging mode to none, basic, thermostat only, raw or verbose"}, {false, "publish", "publish all values to MQTT"}, + {false, "refresh", "fetch values from the EMS devices"}, {false, "types", "list supported EMS telegram type IDs"}, {false, "queue", "show current Tx queue"}, {false, "autodetect", "detect EMS devices and attempt to automatically set boiler and thermostat types"}, @@ -132,7 +133,7 @@ char * _float_to_char(char * a, float f, uint8_t precision = 2) { char * ret = a; // check for 0x8000 (sensor missing) - if (f == EMS_VALUE_FLOAT_NOTSET) { + if (f == EMS_VALUE_SHORT_NOTSET) { strlcpy(ret, "?", sizeof(ret)); } else { long whole = (long)f; @@ -158,63 +159,76 @@ char * _bool_to_char(char * s, uint8_t value) { return s; } -// convert int (single byte) to text value -char * _int_to_char(char * s, uint8_t value) { - if (value == EMS_VALUE_INT_NOTSET) { +// convert short (two bytes) to text value +// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) +char * _short_to_char(char * s, int16_t value, uint8_t div = 10) { + // remove errors on invalid values + if (abs(value) >= EMS_VALUE_SHORT_NOTSET) { strlcpy(s, "?", sizeof(s)); + return (s); + } + + if (div != 0) { + char s2[5] = {0}; + // check for negative values + if (value < 0) { + strlcpy(s, "-", 2); + strlcat(s, itoa(abs(value) / div, s2, 10), 5); + } else { + strlcpy(s, itoa(value / div, s2, 10), 5); + } + strlcat(s, ".", sizeof(s)); + strlcat(s, itoa(abs(value) % div, s2, 10), 5); } else { itoa(value, s, 10); } return s; } -// takes a float value at prints it to debug log -void _renderFloatValue(const char * prefix, const char * postfix, float value) { - char buffer[200] = {0}; - char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - strlcat(buffer, _float_to_char(s, value), sizeof(buffer)); - - if (postfix != NULL) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - myDebug(buffer); -} - -// takes an int (single byte) value at prints it to debug log -void _renderIntValue(const char * prefix, const char * postfix, uint8_t value) { - char buffer[200] = {0}; - char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - strlcat(buffer, _int_to_char(s, value), sizeof(buffer)); - - if (postfix != NULL) { - strlcat(buffer, " ", sizeof(buffer)); - strlcat(buffer, postfix, sizeof(buffer)); - } - myDebug(buffer); -} - -// takes an int value, converts to a fraction -void _renderIntfractionalValue(const char * prefix, const char * postfix, uint8_t value, uint8_t decimals) { - char buffer[200] = {0}; - char s[20] = {0}; - strlcpy(buffer, " ", sizeof(buffer)); - strlcat(buffer, prefix, sizeof(buffer)); - strlcat(buffer, ": ", sizeof(buffer)); - +// convert int (single byte) to text value +char * _int_to_char(char * s, uint8_t value, uint8_t div = 0) { if (value == EMS_VALUE_INT_NOTSET) { - strlcat(buffer, "?", sizeof(buffer)); + strlcpy(s, "?", sizeof(s)); } else { - strlcat(buffer, _int_to_char(s, value / (decimals * 10)), sizeof(buffer)); - strlcat(buffer, ".", sizeof(buffer)); - strlcat(buffer, _int_to_char(s, value % (decimals * 10)), sizeof(buffer)); + if (div != 0) { + char s2[5] = {0}; + strlcpy(s, itoa(value / div, s2, 10), 5); + strlcat(s, ".", sizeof(s)); + strlcat(s, itoa(value % div, s2, 10), 5); + } else { + itoa(value, s, 10); + } } + return s; +} + +// takes an int value (1 byte), converts to a fraction +void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 0) { + char buffer[200] = {0}; + char s[20] = {0}; + strlcpy(buffer, " ", sizeof(buffer)); + strlcat(buffer, prefix, sizeof(buffer)); + strlcat(buffer, ": ", sizeof(buffer)); + + strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer)); + + if (postfix != NULL) { + strlcat(buffer, " ", sizeof(buffer)); + strlcat(buffer, postfix, sizeof(buffer)); + } + + myDebug(buffer); +} + +// takes a short value (2 bytes), converts to a fraction +void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t div = 10) { + char buffer[200] = {0}; + char s[20] = {0}; + strlcpy(buffer, " ", sizeof(buffer)); + strlcat(buffer, prefix, sizeof(buffer)); + strlcat(buffer, ": ", sizeof(buffer)); + + strlcat(buffer, _short_to_char(s, value, div), sizeof(buffer)); if (postfix != NULL) { strlcat(buffer, " ", sizeof(buffer)); @@ -308,7 +322,7 @@ void showInfo() { } if (EMS_Boiler.heatingActive != EMS_VALUE_INT_NOTSET) { - myDebug(" Central Heating: %s", EMS_Boiler.heatingActive ? "active" : "off"); + myDebug(" Central heating: %s", EMS_Boiler.heatingActive ? "active" : "off"); } } @@ -327,8 +341,8 @@ void showInfo() { _renderIntValue("Warm Water desired temperature", "C", EMS_Boiler.wWDesiredTemp); // UBAMonitorWWMessage - _renderFloatValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp); - _renderIntfractionalValue("Warm Water current tap water flow", "l/min", EMS_Boiler.wWCurFlow, 1); + _renderShortValue("Warm Water current temperature", "C", EMS_Boiler.wWCurTmp); + _renderIntValue("Warm Water current tap water flow", "l/min", EMS_Boiler.wWCurFlow, 10); _renderLongValue("Warm Water # starts", "times", EMS_Boiler.wWStarts); if (EMS_Boiler.wWWorkM != EMS_VALUE_LONG_NOTSET) { myDebug(" Warm Water active time: %d days %d hours %d minutes", @@ -340,8 +354,8 @@ void showInfo() { // UBAMonitorFast _renderIntValue("Selected flow temperature", "C", EMS_Boiler.selFlowTemp); - _renderFloatValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); - _renderFloatValue("Return temperature", "C", EMS_Boiler.retTemp); + _renderShortValue("Current flow temperature", "C", EMS_Boiler.curFlowTemp); + _renderShortValue("Return temperature", "C", EMS_Boiler.retTemp); _renderBoolValue("Gas", EMS_Boiler.burnGas); _renderBoolValue("Boiler pump", EMS_Boiler.heatPmp); _renderBoolValue("Fan", EMS_Boiler.fanWork); @@ -349,8 +363,8 @@ void showInfo() { _renderBoolValue("Circulation pump", EMS_Boiler.wWCirc); _renderIntValue("Burner selected max power", "%", EMS_Boiler.selBurnPow); _renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow); - _renderFloatValue("Flame current", "uA", EMS_Boiler.flameCurr); - _renderFloatValue("System pressure", "bar", EMS_Boiler.sysPress); + _renderShortValue("Flame current", "uA", EMS_Boiler.flameCurr); + _renderIntValue("System pressure", "bar", EMS_Boiler.sysPress, 10); if (EMS_Boiler.serviceCode == EMS_VALUE_SHORT_NOTSET) { myDebug(" System service code: %s", EMS_Boiler.serviceCodeChar); } else { @@ -359,14 +373,14 @@ void showInfo() { // UBAParametersMessage _renderIntValue("Heating temperature setting on the boiler", "C", EMS_Boiler.heating_temp); - _renderIntValue("Boiler circuit pump modulation max. power", "%", EMS_Boiler.pump_mod_max); - _renderIntValue("Boiler circuit pump modulation min. power", "%", EMS_Boiler.pump_mod_min); + _renderIntValue("Boiler circuit pump modulation max power", "%", EMS_Boiler.pump_mod_max); + _renderIntValue("Boiler circuit pump modulation min power", "%", EMS_Boiler.pump_mod_min); // UBAMonitorSlow - if (EMS_Boiler.extTemp != EMS_VALUE_FLOAT_NOTSET) { - _renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp); + if (EMS_Boiler.extTemp != EMS_VALUE_SHORT_NOTSET) { + _renderShortValue("Outside temperature", "C", EMS_Boiler.extTemp); } - _renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp); + _renderShortValue("Boiler temperature", "C", EMS_Boiler.boilTemp); _renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod); _renderLongValue("Burner # starts", "times", EMS_Boiler.burnStarts); if (EMS_Boiler.burnWorkMin != EMS_VALUE_LONG_NOTSET) { @@ -393,8 +407,8 @@ void showInfo() { // For SM10 Solar Module if (EMS_Other.SM10) { myDebug("%sSolar Module stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); - _renderFloatValue(" Collector temperature", "C", EMS_Other.SM10collectorTemp); - _renderFloatValue(" Bottom temperature", "C", EMS_Other.SM10bottomTemp); + _renderShortValue(" Collector temperature", "C", EMS_Other.SM10collectorTemp); + _renderShortValue(" Bottom temperature", "C", EMS_Other.SM10bottomTemp); _renderIntValue(" Pump modulation", "%", EMS_Other.SM10pumpModulation); _renderBoolValue(" Pump active", EMS_Other.SM10pump); } @@ -405,9 +419,15 @@ void showInfo() { if (ems_getThermostatEnabled()) { myDebug("%sThermostat stats:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug(" Thermostat type: %s", ems_getThermostatDescription(buffer_type)); - _renderFloatValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp); - _renderFloatValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp); - if ((ems_getThermostatModel() != EMS_MODEL_EASY) && (ems_getThermostatModel() != EMS_MODEL_BOSCHEASY)) { + if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) { + // for easy temps are * 100 + // also we don't have the time or mode + _renderShortValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 100); + _renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 100); + } else { + _renderShortValue("Setpoint room temperature", "C", EMS_Thermostat.setpoint_roomTemp, 2); + _renderShortValue("Current room temperature", "C", EMS_Thermostat.curr_roomTemp, 10); + myDebug(" Thermostat time is %02d:%02d:%02d %d/%d/%d", EMS_Thermostat.hour, EMS_Thermostat.minute, @@ -436,7 +456,7 @@ void showInfo() { myDebug("%sExternal temperature sensors:%s", COLOR_BOLD_ON, COLOR_BOLD_OFF); for (uint8_t i = 0; i < EMSESP_Status.dallas_sensors; i++) { snprintf(s, sizeof(s), "Sensor #%d %s", i + 1, ds18.getDeviceString(buffer, i)); - _renderFloatValue(s, "C", ds18.getValue(i)); + _renderShortValue(s, "C", ds18.getRawValue(i), 16); // divide by 16 } myDebug(""); // newline } @@ -492,8 +512,8 @@ void publishValues(bool force) { JsonObject rootBoiler = doc.to(); rootBoiler["wWSelTemp"] = _int_to_char(s, EMS_Boiler.wWSelTemp); - rootBoiler["selFlowTemp"] = _float_to_char(s, EMS_Boiler.selFlowTemp); - rootBoiler["outdoorTemp"] = _float_to_char(s, EMS_Boiler.extTemp); + rootBoiler["selFlowTemp"] = _int_to_char(s, EMS_Boiler.selFlowTemp); + rootBoiler["outdoorTemp"] = _short_to_char(s, EMS_Boiler.extTemp); rootBoiler["wWActivated"] = _bool_to_char(s, EMS_Boiler.wWActivated); if (EMS_Boiler.wWComfort == EMS_VALUE_UBAParameterWW_wwComfort_Hot) { @@ -504,12 +524,12 @@ void publishValues(bool force) { rootBoiler["wWComfort"] = "Intelligent"; } - rootBoiler["wWCurTmp"] = _float_to_char(s, EMS_Boiler.wWCurTmp); + rootBoiler["wWCurTmp"] = _short_to_char(s, EMS_Boiler.wWCurTmp); snprintf(s, sizeof(s), "%i.%i", EMS_Boiler.wWCurFlow / 10, EMS_Boiler.wWCurFlow % 10); rootBoiler["wWCurFlow"] = s; rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); - rootBoiler["curFlowTemp"] = _float_to_char(s, EMS_Boiler.curFlowTemp); - rootBoiler["retTemp"] = _float_to_char(s, EMS_Boiler.retTemp); + rootBoiler["curFlowTemp"] = _short_to_char(s, EMS_Boiler.curFlowTemp); + rootBoiler["retTemp"] = _short_to_char(s, EMS_Boiler.retTemp); rootBoiler["burnGas"] = _bool_to_char(s, EMS_Boiler.burnGas); rootBoiler["heatPmp"] = _bool_to_char(s, EMS_Boiler.heatPmp); rootBoiler["fanWork"] = _bool_to_char(s, EMS_Boiler.fanWork); @@ -517,8 +537,8 @@ void publishValues(bool force) { rootBoiler["wWCirc"] = _bool_to_char(s, EMS_Boiler.wWCirc); rootBoiler["selBurnPow"] = _int_to_char(s, EMS_Boiler.selBurnPow); rootBoiler["curBurnPow"] = _int_to_char(s, EMS_Boiler.curBurnPow); - rootBoiler["sysPress"] = _float_to_char(s, EMS_Boiler.sysPress); - rootBoiler["boilTemp"] = _float_to_char(s, EMS_Boiler.boilTemp); + rootBoiler["sysPress"] = _int_to_char(s, EMS_Boiler.sysPress, 10); + rootBoiler["boilTemp"] = _short_to_char(s, EMS_Boiler.boilTemp); rootBoiler["pumpMod"] = _int_to_char(s, EMS_Boiler.pumpMod); rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; @@ -551,15 +571,20 @@ void publishValues(bool force) { // handle the thermostat values separately if (ems_getThermostatEnabled()) { // only send thermostat values if we actually have them - if (((int)EMS_Thermostat.curr_roomTemp == (int)0) || ((int)EMS_Thermostat.setpoint_roomTemp == (int)0)) + if ((EMS_Thermostat.curr_roomTemp == 0) || (EMS_Thermostat.setpoint_roomTemp == 0)) return; // build new json object doc.clear(); JsonObject rootThermostat = doc.to(); - rootThermostat[THERMOSTAT_CURRTEMP] = _float_to_char(s, EMS_Thermostat.curr_roomTemp); - rootThermostat[THERMOSTAT_SELTEMP] = _float_to_char(s, EMS_Thermostat.setpoint_roomTemp); + if ((ems_getThermostatModel() == EMS_MODEL_EASY) || (ems_getThermostatModel() == EMS_MODEL_BOSCHEASY)) { + rootThermostat[THERMOSTAT_CURRTEMP] = _short_to_char(s, EMS_Thermostat.curr_roomTemp, 100); + rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 100); + } else { + rootThermostat[THERMOSTAT_CURRTEMP] = _short_to_char(s, EMS_Thermostat.curr_roomTemp, 10); + rootThermostat[THERMOSTAT_SELTEMP] = _short_to_char(s, EMS_Thermostat.setpoint_roomTemp, 2); + } // RC20 has different mode settings if (ems_getThermostatModel() == EMS_MODEL_RC20) { @@ -605,8 +630,8 @@ void publishValues(bool force) { doc.clear(); JsonObject rootSM10 = doc.to(); - rootSM10[SM10_COLLECTORTEMP] = _float_to_char(s, EMS_Other.SM10collectorTemp); - rootSM10[SM10_BOTTOMTEMP] = _float_to_char(s, EMS_Other.SM10bottomTemp); + rootSM10[SM10_COLLECTORTEMP] = _short_to_char(s, EMS_Other.SM10collectorTemp); + rootSM10[SM10_BOTTOMTEMP] = _short_to_char(s, EMS_Other.SM10bottomTemp); rootSM10[SM10_PUMPMODULATION] = _int_to_char(s, EMS_Other.SM10pumpModulation); rootSM10[SM10_PUMP] = _bool_to_char(s, EMS_Other.SM10pump); @@ -925,6 +950,12 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) { ok = true; } + if (strcmp(first_cmd, "refresh") == 0) { + myDebug("Fetching data from EMS devices..."); + do_regularUpdates(); + ok = true; + } + if (strcmp(first_cmd, "types") == 0) { ems_printAllTypes(); ok = true; @@ -1118,10 +1149,9 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // boiler wwtemp changes if (strcmp(topic, TOPIC_BOILER_CMD_WWTEMP) == 0) { - float f = strtof((char *)message, 0); - char s[10] = {0}; - myDebug("MQTT topic: boiler warm water temperature value %s", _float_to_char(s, f)); - ems_setWarmWaterTemp(f); + uint8_t t = atoi((char *)message); + myDebug("MQTT topic: boiler warm water temperature value %d", t); + ems_setWarmWaterTemp(t); publishValues(true); // publish back immediately } diff --git a/src/ems.cpp b/src/ems.cpp index 4a2bbc025..31cf80478 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -14,7 +14,12 @@ #include #include // std::list -// myESP +#define _toByte(i) (data[i]) +#define _toShort(i) ((data[i] << 8) + data[i + 1]) +#define _toLong(i) ((data[i] << 16) + (data[i + 1] << 8) + (data[i + 2])) +#define _bitRead(i, bit) (((data[i]) >> (bit)) & 0x01) + +// myESP for logging to telnet and serial #define myDebug(...) myESP.myDebug(__VA_ARGS__) _EMS_Sys_Status EMS_Sys_Status; // EMS Status @@ -128,9 +133,9 @@ uint8_t _Other_Types_max = ArraySize(Other_Types); // number of other uint8_t _Thermostat_Types_max = ArraySize(Thermostat_Types); // number of defined thermostat types // these structs contain the data we store from the Boiler and Thermostat -_EMS_Boiler EMS_Boiler; -_EMS_Thermostat EMS_Thermostat; -_EMS_Other EMS_Other; +_EMS_Boiler EMS_Boiler; // for boiler +_EMS_Thermostat EMS_Thermostat; // for thermostat +_EMS_Other EMS_Other; // for other known EMS devices // CRC lookup table with poly 12 for faster checking const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, @@ -172,8 +177,8 @@ void ems_init() { EMS_Sys_Status.txRetryCount = 0; // thermostat - EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_FLOAT_NOTSET; - EMS_Thermostat.curr_roomTemp = EMS_VALUE_FLOAT_NOTSET; + EMS_Thermostat.setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET; + EMS_Thermostat.curr_roomTemp = EMS_VALUE_SHORT_NOTSET; EMS_Thermostat.hour = 0; EMS_Thermostat.minute = 0; EMS_Thermostat.second = 0; @@ -196,8 +201,8 @@ void ems_init() { // UBAMonitorFast EMS_Boiler.selFlowTemp = EMS_VALUE_INT_NOTSET; // Selected flow temperature - EMS_Boiler.curFlowTemp = EMS_VALUE_FLOAT_NOTSET; // Current flow temperature - EMS_Boiler.retTemp = EMS_VALUE_FLOAT_NOTSET; // Return temperature + EMS_Boiler.curFlowTemp = EMS_VALUE_SHORT_NOTSET; // Current flow temperature + EMS_Boiler.retTemp = EMS_VALUE_SHORT_NOTSET; // Return temperature EMS_Boiler.burnGas = EMS_VALUE_INT_NOTSET; // Gas on/off EMS_Boiler.fanWork = EMS_VALUE_INT_NOTSET; // Fan on/off EMS_Boiler.ignWork = EMS_VALUE_INT_NOTSET; // Ignition on/off @@ -206,21 +211,21 @@ void ems_init() { EMS_Boiler.wWCirc = EMS_VALUE_INT_NOTSET; // Circulation on/off EMS_Boiler.selBurnPow = EMS_VALUE_INT_NOTSET; // Burner max power EMS_Boiler.curBurnPow = EMS_VALUE_INT_NOTSET; // Burner current power - EMS_Boiler.flameCurr = EMS_VALUE_FLOAT_NOTSET; // Flame current in micro amps - EMS_Boiler.sysPress = EMS_VALUE_FLOAT_NOTSET; // System pressure + EMS_Boiler.flameCurr = EMS_VALUE_SHORT_NOTSET; // Flame current in micro amps + EMS_Boiler.sysPress = EMS_VALUE_INT_NOTSET; // System pressure strlcpy(EMS_Boiler.serviceCodeChar, "??", sizeof(EMS_Boiler.serviceCodeChar)); EMS_Boiler.serviceCode = EMS_VALUE_SHORT_NOTSET; // UBAMonitorSlow - EMS_Boiler.extTemp = EMS_VALUE_FLOAT_NOTSET; // Outside temperature - EMS_Boiler.boilTemp = EMS_VALUE_FLOAT_NOTSET; // Boiler temperature + EMS_Boiler.extTemp = EMS_VALUE_SHORT_NOTSET; // Outside temperature + EMS_Boiler.boilTemp = EMS_VALUE_SHORT_NOTSET; // Boiler temperature EMS_Boiler.pumpMod = EMS_VALUE_INT_NOTSET; // Pump modulation EMS_Boiler.burnStarts = EMS_VALUE_LONG_NOTSET; // # burner restarts EMS_Boiler.burnWorkMin = EMS_VALUE_LONG_NOTSET; // Total burner operating time EMS_Boiler.heatWorkMin = EMS_VALUE_LONG_NOTSET; // Total heat operating time // UBAMonitorWWMessage - EMS_Boiler.wWCurTmp = EMS_VALUE_FLOAT_NOTSET; // Warm Water current temperature: + EMS_Boiler.wWCurTmp = EMS_VALUE_SHORT_NOTSET; // Warm Water current temperature: EMS_Boiler.wWStarts = EMS_VALUE_LONG_NOTSET; // Warm Water # starts EMS_Boiler.wWWorkM = EMS_VALUE_LONG_NOTSET; // Warm Water # minutes EMS_Boiler.wWOneTime = EMS_VALUE_INT_NOTSET; // Warm Water one time function on/off @@ -235,8 +240,8 @@ void ems_init() { EMS_Boiler.pump_mod_min = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation min. power // Other EMS devices values - EMS_Other.SM10collectorTemp = EMS_VALUE_FLOAT_NOTSET; // collector temp from SM10 - EMS_Other.SM10bottomTemp = EMS_VALUE_FLOAT_NOTSET; // bottom temp from SM10 + EMS_Other.SM10collectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10 + EMS_Other.SM10bottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10 EMS_Other.SM10pumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10 EMS_Other.SM10pump = EMS_VALUE_INT_NOTSET; // pump active @@ -346,52 +351,7 @@ uint8_t _crcCalculator(uint8_t * data, uint8_t len) { return crc; } -/** - * function to turn a telegram int (2 bytes) to a float. The source is *10 - * negative values are stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c) - */ -float _toFloat(uint8_t i, uint8_t * data) { - // if the MSB is set, it's a negative number or an error - if ((data[i] & 0x80) == 0x80) { - // check if its an invalid number - // 0x8000 is used when sensor is missing - if ((data[i] >= 0x80) && (data[i + 1] == 0)) { - return (float)EMS_VALUE_FLOAT_NOTSET; // return -1 to indicate that is unknown - } - // its definitely a negative number - // assume its 1-compliment, otherwise we need add 1 to the total for 2-compliment - int16_t x = (data[i] << 8) + data[i + 1]; - return ((float)(x)) / 10; - } else { - // ...a positive number - return ((float)(((data[i] << 8) + data[i + 1]))) / 10; - } -} - -// function to turn a telegram long (3 bytes) to a long int -uint32_t _toLong(uint8_t i, uint8_t * data) { - return (((data[i]) << 16) + ((data[i + 1]) << 8) + (data[i + 2])); -} - -/** - * Find the pointer to the EMS_Types array for a given type ID - */ -int _ems_findType(uint8_t type) { - uint8_t i = 0; - bool typeFound = false; - // scan through known ID types - while (i < _EMS_Types_max) { - if (EMS_Types[i].type == type) { - typeFound = true; // we have a match - break; - } - i++; - } - - return (typeFound ? i : -1); -} - -// like itoa but for hex, and quick +// like itoa but for hex, and quicker char * _hextoa(uint8_t value, char * buffer) { char * p = buffer; byte nib1 = (value >> 4) & 0x0F; @@ -421,6 +381,25 @@ char * _smallitoa3(uint16_t value, char * buffer) { return buffer; } +/** + * Find the pointer to the EMS_Types array for a given type ID + * or -1 if not found + */ +int _ems_findType(uint8_t type) { + uint8_t i = 0; + bool typeFound = false; + // scan through known ID types + while (i < _EMS_Types_max) { + if (EMS_Types[i].type == type) { + typeFound = true; // we have a match + break; + } + i++; + } + + return (typeFound ? i : -1); +} + /** * debug print a telegram to telnet/serial including the CRC * len is length in bytes including the CRC @@ -946,11 +925,11 @@ void _checkActive() { * received only after requested (not broadcasted) */ void _process_UBAParameterWW(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.wWActivated = (data[1] == 0xFF); // 0xFF means on - EMS_Boiler.wWSelTemp = data[2]; - EMS_Boiler.wWCircPump = (data[6] == 0xFF); // 0xFF means on - EMS_Boiler.wWDesiredTemp = data[8]; - EMS_Boiler.wWComfort = data[EMS_OFFSET_UBAParameterWW_wwComfort]; + EMS_Boiler.wWActivated = (_toByte(1) == 0xFF); // 0xFF means on + EMS_Boiler.wWSelTemp = _toByte(2); + EMS_Boiler.wWCircPump = (_toByte(6) == 0xFF); // 0xFF means on + EMS_Boiler.wWDesiredTemp = _toByte(8); + EMS_Boiler.wWComfort = _toByte(EMS_OFFSET_UBAParameterWW_wwComfort); EMS_Sys_Status.emsRefreshed = true; // when we receieve this, lets force an MQTT publish } @@ -960,7 +939,7 @@ void _process_UBAParameterWW(uint8_t src, uint8_t * data, uint8_t length) { * received only after requested (not broadcasted) */ void _process_UBATotalUptimeMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.UBAuptime = _toLong(0, data); + EMS_Boiler.UBAuptime = _toLong(0); EMS_Sys_Status.emsRefreshed = true; // when we receieve this, lets force an MQTT publish } @@ -968,9 +947,9 @@ void _process_UBATotalUptimeMessage(uint8_t src, uint8_t * data, uint8_t length) * UBAParametersMessage - type 0x16 */ void _process_UBAParametersMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.heating_temp = data[1]; - EMS_Boiler.pump_mod_max = data[9]; - EMS_Boiler.pump_mod_min = data[10]; + EMS_Boiler.heating_temp = _toByte(1); + EMS_Boiler.pump_mod_max = _toByte(9); + EMS_Boiler.pump_mod_min = _toByte(10); } /** @@ -978,11 +957,11 @@ void _process_UBAParametersMessage(uint8_t src, uint8_t * data, uint8_t length) * received every 10 seconds */ void _process_UBAMonitorWWMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.wWCurTmp = _toFloat(1, data); - EMS_Boiler.wWStarts = _toLong(13, data); - EMS_Boiler.wWWorkM = _toLong(10, data); - EMS_Boiler.wWOneTime = bitRead(data[5], 1); - EMS_Boiler.wWCurFlow = data[9]; + EMS_Boiler.wWCurTmp = _toShort(1); + EMS_Boiler.wWStarts = _toLong(13); + EMS_Boiler.wWWorkM = _toLong(10); + EMS_Boiler.wWOneTime = _bitRead(5, 1); + EMS_Boiler.wWCurFlow = _toByte(9); } /** @@ -990,36 +969,32 @@ void _process_UBAMonitorWWMessage(uint8_t src, uint8_t * data, uint8_t length) { * received every 10 seconds */ void _process_UBAMonitorFast(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.selFlowTemp = data[0]; - EMS_Boiler.curFlowTemp = _toFloat(1, data); - EMS_Boiler.retTemp = _toFloat(13, data); + EMS_Boiler.selFlowTemp = _toByte(0); + EMS_Boiler.curFlowTemp = _toShort(1); + EMS_Boiler.retTemp = _toShort(13); - uint8_t v = data[7]; - EMS_Boiler.burnGas = bitRead(v, 0); - EMS_Boiler.fanWork = bitRead(v, 2); - EMS_Boiler.ignWork = bitRead(v, 3); - EMS_Boiler.heatPmp = bitRead(v, 5); - EMS_Boiler.wWHeat = bitRead(v, 6); - EMS_Boiler.wWCirc = bitRead(v, 7); + EMS_Boiler.burnGas = _bitRead(7, 0); + EMS_Boiler.fanWork = _bitRead(7, 2); + EMS_Boiler.ignWork = _bitRead(7, 3); + EMS_Boiler.heatPmp = _bitRead(7, 5); + EMS_Boiler.wWHeat = _bitRead(7, 6); + EMS_Boiler.wWCirc = _bitRead(7, 7); - EMS_Boiler.curBurnPow = data[4]; - EMS_Boiler.selBurnPow = data[3]; // burn power max setting + EMS_Boiler.curBurnPow = _toByte(4); + EMS_Boiler.selBurnPow = _toByte(3); // burn power max setting - EMS_Boiler.flameCurr = _toFloat(15, data); + EMS_Boiler.flameCurr = _toShort(15); // read the service code / installation status as appears on the display - EMS_Boiler.serviceCodeChar[0] = char(data[18]); // ascii character 1 - EMS_Boiler.serviceCodeChar[1] = char(data[19]); // ascii character 2 + EMS_Boiler.serviceCodeChar[0] = char(_toByte(18)); // ascii character 1 + EMS_Boiler.serviceCodeChar[1] = char(_toByte(19)); // ascii character 2 EMS_Boiler.serviceCodeChar[2] = '\0'; // null terminate string // read error code - EMS_Boiler.serviceCode = (data[20] << 8) + data[21]; + EMS_Boiler.serviceCode = _toShort(20); - if (data[17] == 0xFF) { // missing value for system pressure - EMS_Boiler.sysPress = 0; - } else { - EMS_Boiler.sysPress = (((float)data[17]) / (float)10); - } + // system pressure. FF means missing + EMS_Boiler.sysPress = _toByte(17); // this is *10 // at this point do a quick check to see if the hot water or heating is active _checkActive(); @@ -1030,23 +1005,23 @@ void _process_UBAMonitorFast(uint8_t src, uint8_t * data, uint8_t length) { * received every 60 seconds */ void _process_UBAMonitorSlow(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Boiler.extTemp = _toFloat(0, data); // 0x8000 if not available - EMS_Boiler.boilTemp = _toFloat(2, data); // 0x8000 if not available - EMS_Boiler.pumpMod = data[9]; - EMS_Boiler.burnStarts = _toLong(10, data); - EMS_Boiler.burnWorkMin = _toLong(13, data); - EMS_Boiler.heatWorkMin = _toLong(19, data); + EMS_Boiler.extTemp = _toShort(0); // 0x8000 if not available + EMS_Boiler.boilTemp = _toShort(2); // 0x8000 if not available + EMS_Boiler.pumpMod = _toByte(9); + EMS_Boiler.burnStarts = _toLong(10); + EMS_Boiler.burnWorkMin = _toLong(13); + EMS_Boiler.heatWorkMin = _toLong(19); } - /** * type 0xB1 - data from the RC10 thermostat (0x17) * For reading the temp values only * received every 60 seconds + * e.g. 17 0B 91 00 80 1E 00 CB 27 00 00 00 00 05 01 00 CB 00 (CRC=47), #data=14 */ void _process_RC10StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC10StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = ((float)data[EMS_TYPE_RC10StatusMessage_curr]) / (float)10; + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC10StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toByte(EMS_TYPE_RC10StatusMessage_curr); // is * 10 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1057,8 +1032,8 @@ void _process_RC10StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { * received every 60 seconds */ void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC20StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC20StatusMessage_curr, data); + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC20StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC20StatusMessage_curr); // is * 10 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1069,8 +1044,8 @@ void _process_RC20StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { * received every 60 seconds */ void _process_RC30StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC30StatusMessage_setpoint]) / (float)2; - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC30StatusMessage_curr, data); + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC30StatusMessage_setpoint); // is * 2 + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC30StatusMessage_curr); // note, its 2 bytes here EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1081,13 +1056,13 @@ void _process_RC30StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { * received every 60 seconds */ void _process_RC35StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.setpoint_roomTemp = ((float)data[EMS_TYPE_RC35StatusMessage_setpoint]) / (float)2; + EMS_Thermostat.setpoint_roomTemp = _toByte(EMS_TYPE_RC35StatusMessage_setpoint); // is * 2 // check if temp sensor is unavailable if ((data[0] == 0x7D) && (data[1] = 0x00)) { - EMS_Thermostat.curr_roomTemp = EMS_VALUE_FLOAT_NOTSET; + EMS_Thermostat.curr_roomTemp = EMS_VALUE_SHORT_NOTSET; } else { - EMS_Thermostat.curr_roomTemp = _toFloat(EMS_TYPE_RC35StatusMessage_curr, data); + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_RC35StatusMessage_curr); } EMS_Thermostat.day_mode = bitRead(data[EMS_OFFSET_RC35Get_mode_day], 1); //get day mode flag EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT @@ -1095,11 +1070,11 @@ void _process_RC35StatusMessage(uint8_t src, uint8_t * data, uint8_t length) { /** * type 0x0A - data from the Nefit Easy/TC100 thermostat (0x18) - 31 bytes long - * The Easy has a digital precision of its floats to 2 decimal places, so values is divided by 100 + * The Easy has a digital precision of its floats to 2 decimal places, so values must be divided by 100 */ void _process_EasyStatusMessage(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.curr_roomTemp = ((float)(((data[EMS_TYPE_EasyStatusMessage_curr] << 8) + data[9]))) / 100; - EMS_Thermostat.setpoint_roomTemp = ((float)(((data[EMS_TYPE_EasyStatusMessage_setpoint] << 8) + data[11]))) / 100; + EMS_Thermostat.curr_roomTemp = _toShort(EMS_TYPE_EasyStatusMessage_curr); // is *100 + EMS_Thermostat.setpoint_roomTemp = _toShort(EMS_TYPE_EasyStatusMessage_setpoint); // is *100 EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1117,7 +1092,7 @@ void _process_RC10Set(uint8_t src, uint8_t * data, uint8_t length) { * received only after requested */ void _process_RC20Set(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC20Set_mode]; + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC20Set_mode); } /** @@ -1125,7 +1100,7 @@ void _process_RC20Set(uint8_t src, uint8_t * data, uint8_t length) { * received only after requested */ void _process_RC30Set(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC30Set_mode]; + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC30Set_mode); } /** @@ -1134,7 +1109,7 @@ void _process_RC30Set(uint8_t src, uint8_t * data, uint8_t length) { * received only after requested */ void _process_RC35Set(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Thermostat.mode = data[EMS_OFFSET_RC35Set_mode]; + EMS_Thermostat.mode = _toByte(EMS_OFFSET_RC35Set_mode); } /** @@ -1148,10 +1123,10 @@ void _process_RCOutdoorTempMessage(uint8_t src, uint8_t * data, uint8_t length) * SM10Monitor - type 0x97 */ void _process_SM10Monitor(uint8_t src, uint8_t * data, uint8_t length) { - EMS_Other.SM10collectorTemp = _toFloat(2, data); // collector temp from SM10 - EMS_Other.SM10bottomTemp = _toFloat(5, data); // bottom temp from SM10 - EMS_Other.SM10pumpModulation = data[4]; // modulation solar pump - EMS_Other.SM10pump = bitRead(data[6], 1); // active if bit 1 is set (to 1) + EMS_Other.SM10collectorTemp = _toShort(2); // collector temp from SM10, is *10 + EMS_Other.SM10bottomTemp = _toShort(5); // bottom temp from SM10, is *10 + EMS_Other.SM10pumpModulation = _toByte(4); // modulation solar pump + EMS_Other.SM10pump = _bitRead(5, 1); // active if bit 1 is set (to 1) EMS_Sys_Status.emsRefreshed = true; // triggers a send the values back via MQTT } @@ -1181,12 +1156,12 @@ void _process_RCTime(uint8_t src, uint8_t * data, uint8_t length) { return; // not supported } - EMS_Thermostat.hour = data[2]; - EMS_Thermostat.minute = data[4]; - EMS_Thermostat.second = data[5]; - EMS_Thermostat.day = data[3]; - EMS_Thermostat.month = data[1]; - EMS_Thermostat.year = data[0]; + EMS_Thermostat.hour = _toByte(2); + EMS_Thermostat.minute = _toByte(4); + EMS_Thermostat.second = _toByte(5); + EMS_Thermostat.day = _toByte(3); + EMS_Thermostat.month = _toByte(1); + EMS_Thermostat.year = _toByte(0); } /** @@ -1199,9 +1174,9 @@ void _process_Version(uint8_t src, uint8_t * data, uint8_t length) { return; } - uint8_t product_id = data[0]; + uint8_t product_id = _toByte(0); char version[10] = {0}; - snprintf(version, sizeof(version), "%02d.%02d", data[1], data[2]); + snprintf(version, sizeof(version), "%02d.%02d", _toByte(1), _toByte(2)); // see if its a known boiler int i = 0; @@ -1510,10 +1485,11 @@ char * ems_getThermostatDescription(char * buffer) { strlcat(buffer, _hextoa(EMS_Thermostat.type_id, tmp), size); } - strlcat(buffer, " Product ID:", size); + strlcat(buffer, " (Product ID:", size); strlcat(buffer, itoa(EMS_Thermostat.product_id, tmp, 10), size); strlcat(buffer, " Version:", size); strlcat(buffer, EMS_Thermostat.version, size); + strlcat(buffer, ")", size); } return buffer; @@ -1546,10 +1522,11 @@ char * ems_getBoilerDescription(char * buffer) { strlcat(buffer, _hextoa(EMS_Boiler.type_id, tmp), size); } - strlcat(buffer, " Product ID:", size); + strlcat(buffer, " (Product ID:", size); strlcat(buffer, itoa(EMS_Boiler.product_id, tmp, 10), size); strlcat(buffer, " Version:", size); strlcat(buffer, EMS_Boiler.version, size); + strlcat(buffer, ")", size); } return buffer; @@ -1635,6 +1612,7 @@ void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh) { // if we're preventing all outbound traffic, quit if (EMS_Sys_Status.emsTxDisabled) { + myDebug("in Silent Mode. All Tx is disabled."); return; } diff --git a/src/ems.h b/src/ems.h index ddee54043..8211d76e8 100644 --- a/src/ems.h +++ b/src/ems.h @@ -27,9 +27,8 @@ #define EMS_VALUE_INT_ON 1 // boolean true #define EMS_VALUE_INT_OFF 0 // boolean false #define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit ints +#define EMS_VALUE_SHORT_NOTSET 0x8000 // for 2-byte shorts #define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs -#define EMS_VALUE_SHORT_NOTSET 0xFFFF // for 2-byte shorts -#define EMS_VALUE_FLOAT_NOTSET -255 // float #define EMS_THERMOSTAT_READ_YES true #define EMS_THERMOSTAT_READ_NO false @@ -175,8 +174,8 @@ typedef struct { // UBAParameterWW // UBAMonitorFast uint8_t selFlowTemp; // Selected flow temperature - float curFlowTemp; // Current flow temperature - float retTemp; // Return temperature + int16_t curFlowTemp; // Current flow temperature + int16_t retTemp; // Return temperature uint8_t burnGas; // Gas on/off uint8_t fanWork; // Fan on/off uint8_t ignWork; // Ignition on/off @@ -185,21 +184,21 @@ typedef struct { // UBAParameterWW uint8_t wWCirc; // Circulation on/off uint8_t selBurnPow; // Burner max power uint8_t curBurnPow; // Burner current power - float flameCurr; // Flame current in micro amps - float sysPress; // System pressure + uint16_t flameCurr; // Flame current in micro amps + uint8_t sysPress; // System pressure char serviceCodeChar[3]; // 2 character status/service code uint16_t serviceCode; // error/service code // UBAMonitorSlow - float extTemp; // Outside temperature - float boilTemp; // Boiler temperature + int16_t extTemp; // Outside temperature + int16_t boilTemp; // Boiler temperature uint8_t pumpMod; // Pump modulation uint32_t burnStarts; // # burner starts uint32_t burnWorkMin; // Total burner operating time uint32_t heatWorkMin; // Total heat operating time // UBAMonitorWWMessage - float wWCurTmp; // Warm Water current temperature: + int16_t wWCurTmp; // Warm Water current temperature: uint32_t wWStarts; // Warm Water # starts uint32_t wWWorkM; // Warm Water # minutes uint8_t wWOneTime; // Warm Water one time function on/off @@ -228,31 +227,31 @@ typedef struct { // UBAParameterWW */ typedef struct { // SM10 Solar Module - SM10Monitor - bool SM10; // set true if there is a SM10 available - float SM10collectorTemp; // collector temp from SM10 - float SM10bottomTemp; // bottom temp from SM10 - uint8_t SM10pumpModulation; // modulation solar pump - uint8_t SM10pump; // pump active + bool SM10; // set true if there is a SM10 available + int16_t SM10collectorTemp; // collector temp from SM10 + int16_t SM10bottomTemp; // bottom temp from SM10 + uint8_t SM10pumpModulation; // modulation solar pump + uint8_t SM10pump; // pump active } _EMS_Other; // Thermostat data typedef struct { - uint8_t type_id; // the type ID of the thermostat - uint8_t model_id; // which Thermostat type - uint8_t product_id; - bool read_supported; - bool write_supported; - char version[10]; - float setpoint_roomTemp; // current set temp - float curr_roomTemp; // current room temp - uint8_t mode; // 0=low, 1=manual, 2=auto - bool day_mode; // 0=night, 1=day - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t day; - uint8_t month; - uint8_t year; + uint8_t type_id; // the type ID of the thermostat + uint8_t model_id; // which Thermostat type + uint8_t product_id; + bool read_supported; + bool write_supported; + char version[10]; + int16_t setpoint_roomTemp; // current set temp + int16_t curr_roomTemp; // current room temp + uint8_t mode; // 0=low, 1=manual, 2=auto + bool day_mode; // 0=night, 1=day + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t day; + uint8_t month; + uint8_t year; } _EMS_Thermostat; // call back function signature for processing telegram types