diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index 7cd4fa0f5..e76e40a7f 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -69,15 +69,16 @@ typedef struct { uint8_t dallas_sensors; // count of dallas sensors // custom params - bool shower_timer; // true if we want to report back on shower times - bool shower_alert; // true if we want the alert of cold water - bool led; // LED on/off - bool listen_mode; // stop automatic Tx on/off - uint16_t publish_time; // frequency of MQTT publish in seconds - uint8_t led_gpio; // pin for LED - uint8_t dallas_gpio; // pin for attaching external dallas temperature sensors - bool dallas_parasite; // on/off is using parasite - uint8_t tx_mode; // TX mode 1,2 or 3 + bool shower_timer; // true if we want to report back on shower times + bool shower_alert; // true if we want the alert of cold water + bool led; // LED on/off + bool listen_mode; // stop automatic Tx on/off + uint16_t publish_time; // frequency of MQTT publish in seconds + uint8_t led_gpio; // pin for LED + uint8_t dallas_gpio; // pin for attaching external dallas temperature sensors + bool dallas_parasite; // on/off is using parasite + uint8_t tx_mode; // TX mode 1,2 or 3 + uint8_t master_thermostat; // Product ID of master thermostat to use } _EMSESP_Settings; typedef struct { @@ -99,6 +100,7 @@ static const command_t project_cmds[] PROGMEM = { {true, "shower_alert ", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"}, {true, "publish_time ", "set frequency for publishing data to MQTT (0=automatic)"}, {true, "tx_mode ", "changes Tx logic. 1=EMS generic, 2=EMS+, 3=HT3"}, + {true, "master_thermostat [product id]", "product id to use as the master thermostat. Use no args for list."}, {false, "info", "show current values deciphered from the EMS messages"}, {false, "log ")); } } + + // master_thermostat + if (strcmp(setting, "master_thermostat") == 0) { + if (wc == 1) { + // show list + char device_string[100]; + myDebug_P(PSTR("Available thermostat Product ids:")); + for (std::list<_Detected_Device>::iterator it = Devices.begin(); it != Devices.end(); ++it) { + if (it->device_type == EMS_DEVICE_TYPE_THERMOSTAT) { + strlcpy(device_string, (it)->device_desc_p, sizeof(device_string)); + myDebug_P(PSTR(" %d = %s"), (it)->product_id, device_string); + } + } + myDebug_P(PSTR("Usage: set master_thermostat ")); + } else if (wc == 2) { + uint8_t pid = atoi(value); + EMSESP_Settings.master_thermostat = pid; + ems_setMasterThermostat(pid); + ok = true; + } + } } if (action == MYESP_FSACTION_LIST) { @@ -1087,11 +1113,18 @@ bool SetListCallback(MYESP_FSACTION_t action, uint8_t wc, const char * setting, myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Settings.listen_mode ? "on" : "off"); myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Settings.shower_timer ? "on" : "off"); myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Settings.shower_alert ? "on" : "off"); + if (EMSESP_Settings.publish_time) { myDebug_P(PSTR(" publish_time=%d"), EMSESP_Settings.publish_time); } else { myDebug_P(PSTR(" publish_time=0 (always publish on data received)"), EMSESP_Settings.publish_time); } + + if (EMSESP_Settings.master_thermostat) { + myDebug_P(PSTR(" master_thermostat=%d"), EMSESP_Settings.master_thermostat); + } else { + myDebug_P(PSTR(" master_thermostat=0 (use first one detected)"), EMSESP_Settings.master_thermostat); + } } return ok; @@ -1360,7 +1393,7 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { // this is used for example for comfort, flowtemp myESP.mqttSubscribe(TOPIC_BOILER_CMD); - // these three need to be unqiue topics + // these three need to be unique topics myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWACTIVATED); myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWONETIME); myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP); @@ -1396,6 +1429,8 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) { const char * command = doc["cmd"]; // Check whatever the command is and act accordingly + + // we only have one now, this is for the shower coldshot if (strcmp(command, TOPIC_SHOWER_COLDSHOT) == 0) { _showerColdShotStart(); return; @@ -1708,7 +1743,7 @@ void WebCallback(JsonObject root) { boiler["ok"] = false; } - // For SM10/SM100 Solar Module + // For SM10/SM100/SM200 Solar Module JsonObject sm = root.createNestedObject("sm"); if (ems_getSolarModuleEnabled()) { sm["ok"] = true; @@ -1766,15 +1801,16 @@ void WebCallback(JsonObject root) { // Most of these will be overwritten after the SPIFFS config file is loaded void initEMSESP() { // general settings - EMSESP_Settings.shower_timer = false; - EMSESP_Settings.shower_alert = false; - EMSESP_Settings.led = true; // LED is on by default - EMSESP_Settings.listen_mode = false; - EMSESP_Settings.publish_time = DEFAULT_PUBLISHTIME; - EMSESP_Settings.dallas_sensors = 0; - EMSESP_Settings.led_gpio = EMSESP_LED_GPIO; - EMSESP_Settings.dallas_gpio = EMSESP_DALLAS_GPIO; - EMSESP_Settings.tx_mode = EMS_TXMODE_DEFAULT; // default tx mode + EMSESP_Settings.shower_timer = false; + EMSESP_Settings.shower_alert = false; + EMSESP_Settings.led = true; // LED is on by default + EMSESP_Settings.listen_mode = false; + EMSESP_Settings.publish_time = DEFAULT_PUBLISHTIME; + EMSESP_Settings.dallas_sensors = 0; + EMSESP_Settings.led_gpio = EMSESP_LED_GPIO; + EMSESP_Settings.dallas_gpio = EMSESP_DALLAS_GPIO; + EMSESP_Settings.tx_mode = EMS_TXMODE_DEFAULT; // default tx mode + EMSESP_Settings.master_thermostat = 0; // shower settings EMSESP_Shower.timerStart = 0; diff --git a/src/ems.cpp b/src/ems.cpp index 4801b2de2..6c709cf67 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -71,6 +71,11 @@ void ems_Device_remove_flags(unsigned int flags) { EMS_Sys_Status.emsRefreshedFlags &= ~flags; } +// returns true if HT3, other Buderus protocol +bool ems_isHT3() { + return (EMS_Sys_Status.emsIDMask == 0x80); +} + // init stats and counters and buffers void ems_init() { ems_clearDeviceList(); // init the device map @@ -171,9 +176,9 @@ void ems_init() { EMS_Boiler.pump_mod_min = EMS_VALUE_INT_NOTSET; // Boiler circuit pump modulation min. power % // Solar Module values - EMS_SolarModule.collectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10/SM100 - EMS_SolarModule.bottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10/SM100 - EMS_SolarModule.pumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10/SM100 + EMS_SolarModule.collectorTemp = EMS_VALUE_SHORT_NOTSET; // collector temp from SM10/SM100/SM200 + EMS_SolarModule.bottomTemp = EMS_VALUE_SHORT_NOTSET; // bottom temp from SM10/SM100/SM200 + EMS_SolarModule.pumpModulation = EMS_VALUE_INT_NOTSET; // modulation solar pump SM10/SM100/SM200 EMS_SolarModule.pump = EMS_VALUE_BOOL_NOTSET; // pump active EMS_SolarModule.EnergyLastHour = EMS_VALUE_USHORT_NOTSET; EMS_SolarModule.EnergyToday = EMS_VALUE_USHORT_NOTSET; @@ -410,6 +415,10 @@ void ems_setTxMode(uint8_t mode) { EMS_Sys_Status.emsTxMode = mode; } +void ems_setMasterThermostat(uint8_t product_id) { + EMS_Sys_Status.emsMasterThermostat = product_id; +} + /** * debug print a telegram to telnet/serial including the CRC */ @@ -1241,7 +1250,7 @@ void _process_RCPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) { // the whole telegram // e.g. Thermostat -> all, telegram: 10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00 // 10 00 FF 00 01 A5 80 00 01 30 28 00 30 28 01 54 03 03 01 01 54 02 A8 00 00 11 01 03 FF FF 00 - // or prtial, e.g. for modes: + // or partial, e.g. for modes: // manual : 10 00 FF 0A 01 A5 02 // auto : 10 00 FF 0A 01 A5 03 _setValue(EMS_RxTelegram, &EMS_Thermostat.hc[hc].curr_roomTemp, EMS_OFFSET_RCPLUSStatusMessage_curr); // value is * 10 @@ -1665,6 +1674,17 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) { strlcat(version, ".", sizeof(version)); strlcat(version, _smallitoa(EMS_RxTelegram->data[offset + 2], buf), sizeof(version)); + // some devices store the protocol type (HT3, Buderus) in tbe last byte + // we don't do anything with this yet. + if (EMS_RxTelegram->data_length >= 10) { + uint8_t protocol_type = EMS_RxTelegram->data[9]; + if (protocol_type == 2) { + // it's a junkers + } else if (protocol_type >= 3) { + // it's buderus + } + } + // scan through known devices matching the productid uint8_t product_id = EMS_RxTelegram->data[offset]; uint8_t i = 0; @@ -1677,7 +1697,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) { i++; } - // if not found, just add it + // if not found, just add it as an unknown device if (!typeFound) { (void)_addDevice(EMS_DEVICE_TYPE_UNKNOWN, product_id, device_id, nullptr, version); return; @@ -1701,13 +1721,17 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) { strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version)); ems_getBoilerValues(); // get Boiler values that we would usually have to wait for } else if (type == EMS_DEVICE_TYPE_THERMOSTAT) { - EMS_Thermostat.device_id = device_id; - EMS_Thermostat.device_flags = (flags & 0x7F); // remove 7th bit - EMS_Thermostat.write_supported = (flags & EMS_DEVICE_FLAG_NO_WRITE) == 0; - EMS_Thermostat.product_id = product_id; - EMS_Thermostat.device_desc_p = device_desc_p; - strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version)); - ems_getThermostatValues(); // get Thermostat values + // we can only support a single thermostat currently, so check which product_id we may have chosen + // to be the master - see https://github.com/proddy/EMS-ESP/issues/238 + if ((EMS_Sys_Status.emsMasterThermostat == 0) || (EMS_Sys_Status.emsMasterThermostat == product_id)) { + EMS_Thermostat.device_id = device_id; + EMS_Thermostat.device_flags = (flags & 0x7F); // remove 7th bit + EMS_Thermostat.write_supported = (flags & EMS_DEVICE_FLAG_NO_WRITE) == 0; + EMS_Thermostat.product_id = product_id; + EMS_Thermostat.device_desc_p = device_desc_p; + strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version)); + ems_getThermostatValues(); // get Thermostat values + } } else if (type == EMS_DEVICE_TYPE_SOLAR) { EMS_SolarModule.device_id = device_id; EMS_SolarModule.product_id = product_id; @@ -2459,8 +2483,14 @@ void ems_setWarmWaterActivated(bool activated) { EMS_TxTelegram.type = EMS_TYPE_UBAParameterWW; EMS_TxTelegram.offset = EMS_OFFSET_UBAParameterWW_wwactivated; EMS_TxTelegram.length = EMS_MIN_TELEGRAM_LENGTH; - EMS_TxTelegram.type_validate = EMS_ID_NONE; // don't validate - EMS_TxTelegram.dataValue = (activated ? 0xFF : 0x00); // 0xFF is on, 0x00 is off + EMS_TxTelegram.type_validate = EMS_ID_NONE; // don't validate + + // https://github.com/proddy/EMS-ESP/issues/268 + if (ems_isHT3()) { + EMS_TxTelegram.dataValue = (activated ? 0x08 : 0x00); // 0x08 is on, 0x00 is off + } else { + EMS_TxTelegram.dataValue = (activated ? 0xFF : 0x00); // 0xFF is on, 0x00 is off + } EMS_TxQueue.push(EMS_TxTelegram); } diff --git a/src/ems.h b/src/ems.h index ecb10e951..7afda360a 100644 --- a/src/ems.h +++ b/src/ems.h @@ -137,6 +137,7 @@ typedef struct { uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80 uint8_t emsPollAck[1]; // acknowledge buffer for Poll uint8_t emsTxMode; // Tx mode 1, 2 or 3 + uint8_t emsMasterThermostat; // product ID for the default thermostat to use char emsDeviceMap[EMS_SYS_DEVICEMAP_LENGTH]; // contents of 0x07 telegram with bitmasks for all active EMS devices } _EMS_Sys_Status; @@ -336,7 +337,7 @@ typedef struct { _EMS_Mixing_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits } _EMS_Mixing; -// Solar Module - SM10/SM100/ISM1 +// Solar Module - SM10/SM100/SM200/ISM1 typedef struct { uint8_t device_id; // the device ID of the Solar Module uint8_t device_flags; // Solar Module flags @@ -421,6 +422,7 @@ void ems_setWarmWaterModeComfort(uint8_t comfort); void ems_setModels(); void ems_setTxDisabled(bool b); void ems_setTxMode(uint8_t mode); +void ems_setMasterThermostat(uint8_t product_id); char * ems_getDeviceDescription(_EMS_DEVICE_TYPE device_type, char * buffer, bool name_only = false); bool ems_getDeviceTypeDescription(uint8_t device_id, char * buffer); void ems_getThermostatValues(); @@ -444,6 +446,7 @@ bool ems_getTxDisabled(); void ems_Device_add_flags(unsigned int flags); bool ems_Device_has_flags(unsigned int flags); void ems_Device_remove_flags(unsigned int flags); +bool ems_isHT3(); // private functions uint8_t _crcCalculator(uint8_t * data, uint8_t len);