diff --git a/CHANGELOG.md b/CHANGELOG.md index afa998283..15e44e9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.1] 2019-01-29 + +### Added + +- The led pin, dallas pin and both thermostat and boiler type IDs can be set with the application, and stored. + +### Changed + +- some minor improvements to autodetect + + ## [1.4.0] 2019-01-27 ### Changed diff --git a/lib/myESP/MyESP.cpp b/lib/myESP/MyESP.cpp index a72456e20..a72638d70 100644 --- a/lib/myESP/MyESP.cpp +++ b/lib/myESP/MyESP.cpp @@ -402,10 +402,8 @@ void MyESP::_consoleShowHelp() { SerialAndTelnet.println(FPSTR("* set")); SerialAndTelnet.println(FPSTR("* set [value]")); SerialAndTelnet.println(FPSTR("* set erase")); - SerialAndTelnet.println(FPSTR("* set led ")); - SerialAndTelnet.println(FPSTR("*")); - // print custom commands if available. Take from progmem + // print custom commands if available. Taken from progmem if (_telnetcommand_callback) { // find the longest key length so we can right align it uint8_t max_len = 0; @@ -513,8 +511,8 @@ void MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value) SerialAndTelnet.printf("%s changed to %s\n\r", setting, value); } - if (_fs_saveConfig()) { - SerialAndTelnet.println("Note, some changes will only have effect after a device reboot (use ! command)"); + if (fs_saveConfig()) { + SerialAndTelnet.println("Note, some changes will only have effect after the ESP is restarted (use ! command)"); } } @@ -811,8 +809,8 @@ bool MyESP::_fs_loadConfig() { // use configFile.readString configFile.readBytes(buf.get(), size); - StaticJsonBuffer<500> jsonBuffer; // https://arduinojson.org/v5/assistant/ - JsonObject & json = jsonBuffer.parseObject(buf.get()); + StaticJsonBuffer jsonBuffer; + JsonObject & json = jsonBuffer.parseObject(buf.get()); const char * value; @@ -831,18 +829,19 @@ bool MyESP::_fs_loadConfig() { value = json["mqtt_password"]; _mqtt_password = (value) ? strdup(value) : NULL; - // callback for custom settings - (_fs_callback)(MYESP_FSACTION_LOAD, json); + // callback for loading custom settings + // ok is false if there's a problem loading a custom setting (e.g. does not exist) + bool ok = (_fs_callback)(MYESP_FSACTION_LOAD, json); configFile.close(); - return true; + return ok; } // save settings to spiffs -bool MyESP::_fs_saveConfig() { - StaticJsonBuffer<500> jsonBuffer; // https://arduinojson.org/v5/assistant/ - JsonObject & json = jsonBuffer.createObject(); +bool MyESP::fs_saveConfig() { + StaticJsonBuffer jsonBuffer; + JsonObject & json = jsonBuffer.createObject(); json["wifi_ssid"] = _wifi_ssid; json["wifi_password"] = _wifi_password; @@ -850,8 +849,8 @@ bool MyESP::_fs_saveConfig() { json["mqtt_username"] = _mqtt_username; json["mqtt_password"] = _mqtt_password; - // callback for custom settings - (_fs_callback)(MYESP_FSACTION_SAVE, json); + // callback for saving custom settings + (void)(_fs_callback)(MYESP_FSACTION_SAVE, json); File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { @@ -872,11 +871,12 @@ void MyESP::_fs_setup() { return; } - // _fs_printConfig(); // for debugging + //_fs_printConfig(); // for debugging - // load the config file. if it doesn't exist create it with anything that was specified + // load the config file. if it doesn't exist create it if (!_fs_loadConfig()) { - _fs_saveConfig(); + myDebug_P(PSTR("[FS] Re-creating config file")); + fs_saveConfig(); } } @@ -939,4 +939,4 @@ void MyESP::loop() { yield(); // ...and breath } -MyESP myESP; +MyESP myESP; // create instance diff --git a/lib/myESP/MyESP.h b/lib/myESP/MyESP.h index c1bb50598..61a62cf10 100644 --- a/lib/myESP/MyESP.h +++ b/lib/myESP/MyESP.h @@ -61,8 +61,11 @@ #define COLOR_CYAN "\x1B[0;36m" #define COLOR_WHITE "\x1B[0;37m" +// SPIFFS +#define SPIFFS_MAXSIZE 500 // https://arduinojson.org/v5/assistant/ + typedef struct { - char key[30]; + char key[40]; char description[100]; } command_t; @@ -72,7 +75,7 @@ typedef std::function typedef std::function wifi_callback_f; typedef std::function telnetcommand_callback_f; typedef std::function telnet_callback_f; -typedef std::function fs_callback_f; +typedef std::function fs_callback_f; typedef std::function fs_settings_callback_f; // calculates size of an 2d array at compile time @@ -113,6 +116,7 @@ class MyESP { // FS void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback); + bool fs_saveConfig(); // general void end(); @@ -178,7 +182,6 @@ class MyESP { // fs void _fs_setup(); - bool _fs_saveConfig(); bool _fs_loadConfig(); void _fs_printConfig(); void _fs_eraseConfig(); diff --git a/src/ems-esp.ino b/src/ems-esp.ino index 2304f0ea5..3de035866 100644 --- a/src/ems-esp.ino +++ b/src/ems-esp.ino @@ -32,7 +32,7 @@ DS18 ds18; #define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) // timers, all values are in seconds -#define PUBLISHVALUES_TIME 120 // every 2 minutes post MQTT values +#define PUBLISHVALUES_TIME 120 // every 2 minutes publish MQTT values Ticker publishValuesTimer; #define SYSTEMCHECK_TIME 20 // every 20 seconds check if Boiler is online @@ -41,7 +41,7 @@ Ticker systemCheckTimer; #define REGULARUPDATES_TIME 60 // every minute a call is made to fetch data from EMS devices manually Ticker regularUpdatesTimer; -#define LEDCHECK_TIME 1 // every second blink heartbeat LED +#define LEDCHECK_TIME 1 // every second blink the heartbeat LED Ticker ledcheckTimer; // thermostat scan - for debugging @@ -51,26 +51,6 @@ uint8_t scanThermostat_count = 0; Ticker showerColdShotStopTimer; -// thermostat -#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values -#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes -#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes -#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature -#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature -#define THERMOSTAT_MODE "thermostat_mode" // mode - -// boiler -#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values -#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running -#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on - -// shower time -#define TOPIC_SHOWERTIME "showertime" // for sending shower time results -#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic -#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic -#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command -#define SHOWER_ALARM "shower_alarm" // for notifying that shower time has reached its limit - // if using the shower timer, change these settings #define SHOWER_PAUSE_TIME 15000 // in ms. 15 seconds, max time if water is switched off & on during a shower #define SHOWER_MIN_DURATION 120000 // in ms. 2 minutes, before recognizing its a shower @@ -83,6 +63,8 @@ typedef struct { bool led_enabled; // LED on/off unsigned long timestamp; // for internal timings, via millis() uint8_t dallas_sensors; // count of dallas sensors + uint8_t led_gpio; + uint8_t dallas_gpio; } _EMSESP_Status; typedef struct { @@ -95,10 +77,16 @@ typedef struct { command_t PROGMEM project_cmds[] = { + {"set led ", "toggle status LED on/off"}, + {"set led_gpio ", "set the LED pin (onboard=2)"}, + {"set dallas_gpio ", "set the pin for the external Dallas temperature sensor (D5=14)"}, + {"set thermostat_type ", "set the thermostat type id (e.g. 10 for 0x10)"}, + {"set boiler_type ", "set the boiler type id (e.g. 8 for 0x08)"}, + {"info", "show the values"}, {"log ", "set logging mode to none, basic, thermostat only, raw or verbose"}, {"poll", "toggle EMS poll request on/off"}, - {"tx", "toggle EMX Tx on/off"}, + {"tx", "toggle EMX Tx line on/off"}, {"publish", "publish values to MQTT"}, {"types", "list supported EMS telegram type IDs"}, {"queue", "print the Tx queue"}, @@ -349,7 +337,9 @@ void showInfo() { _renderIntValue("Boiler circuit pump modulation min. power", "%", EMS_Boiler.pump_mod_min); // UBAMonitorSlow - _renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp); + if (EMS_Boiler.extTemp != EMS_VALUE_FLOAT_NOTSET) { + _renderFloatValue("Outside temperature", "C", EMS_Boiler.extTemp); + } _renderFloatValue("Boiler temperature", "C", EMS_Boiler.boilTemp); _renderIntValue("Pump modulation", "%", EMS_Boiler.pumpMod); _renderLongValue("Burner # restarts", "times", EMS_Boiler.burnStarts); @@ -576,7 +566,7 @@ char * _readWord() { } // initiate a force scan by sending type read requests from 0 to FF to the thermostat -// used to analyze repsonses for debugging +// used to analyze responses for debugging void startThermostatScan(uint8_t start) { ems_setLogging(EMS_SYS_LOGGING_THERMOSTAT); publishValuesTimer.detach(); @@ -589,22 +579,66 @@ void startThermostatScan(uint8_t start) { // callback for loading/saving settings to the file system (SPIFFS) -void FSCallback(MYESP_FSACTION action, JsonObject & json) { +bool FSCallback(MYESP_FSACTION action, JsonObject & json) { + bool ok = true; if (action == MYESP_FSACTION_LOAD) { - EMSESP_Status.led_enabled = (bool)json["led"]; + // led + if (json.containsKey("led")) { + EMSESP_Status.led_enabled = (bool)json["led"]; + } else { + ok = false; + } + + // led_gpio + if (json.containsKey("led_gpio")) { + EMSESP_Status.led_gpio = json["led_gpio"]; + } else { + ok = false; + } + + // dallas_gpio + if (json.containsKey("dallas_gpio")) { + EMSESP_Status.dallas_gpio = json["dallas_gpio"]; + } else { + ok = false; + } + + // thermostat_type + if (json.containsKey("thermostat_type")) { + EMS_Thermostat.type_id = json["thermostat_type"]; + } else { + EMS_Thermostat.type_id = EMSESP_THERMOSTAT_TYPE; // set default + ok = false; + } + + // boiler_type + if (json.containsKey("boiler_type")) { + EMS_Boiler.type_id = json["boiler_type"]; + } else { + EMS_Boiler.type_id = EMSESP_BOILER_TYPE; // set default + ok = false; + } } if (action == MYESP_FSACTION_SAVE) { - json["led"] = EMSESP_Status.led_enabled; + json["led"] = EMSESP_Status.led_enabled; + json["led_gpio"] = EMSESP_Status.led_gpio; + json["dallas_gpio"] = EMSESP_Status.dallas_gpio; + json["thermostat_type"] = EMS_Thermostat.type_id; + json["boiler_type"] = EMS_Boiler.type_id; } + + return ok; // all ok } -// callback for custom settings +// callback for custom settings when showing Stored Settings // wc is number of arguments after the 'set' command +// returns true if successful bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) { bool ok = false; if (action == MYESP_FSACTION_SET) { + // led if ((strcmp(setting, "led") == 0) && (wc == 2)) { if (strcmp(value, "on") == 0) { EMSESP_Status.led_enabled = true; @@ -613,13 +647,53 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c EMSESP_Status.led_enabled = false; ok = true; // let's make sure LED is really off - digitalWrite(BOILER_LED, (BOILER_LED == LED_BUILTIN) ? HIGH : LOW); // light off. For onboard high=off + digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? HIGH : LOW); // light off. For onboard high=off } } + + // led_gpio + if ((strcmp(setting, "led_gpio") == 0) && (wc == 2)) { + EMSESP_Status.led_gpio = atoi(value); + ok = true; + } + + // dallas_gpio + if ((strcmp(setting, "dallas_gpio") == 0) && (wc == 2)) { + EMSESP_Status.dallas_gpio = atoi(value); + ok = true; + } + + // thermostat_type + if (strcmp(setting, "thermostat_type") == 0) { + EMS_Thermostat.type_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE); + ok = true; + } + + // boiler_type + if (strcmp(setting, "boiler_type") == 0) { + EMS_Boiler.type_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE); + ok = true; + } } if (action == MYESP_FSACTION_LIST) { myDebug(" led=%s", EMSESP_Status.led_enabled ? "on" : "off"); + myDebug(" led_gpio=%d", EMSESP_Status.led_gpio); + myDebug(" dallas_gpio=%d", EMSESP_Status.dallas_gpio); + + if (EMS_Thermostat.type_id == EMS_ID_NONE) { + myDebug(" thermostat_type="); + + } else { + myDebug(" thermostat_type=%02X", EMS_Thermostat.type_id); + } + + if (EMS_Boiler.type_id == EMS_ID_NONE) { + myDebug(" boiler_type="); + + } else { + myDebug(" boiler_type=%02X", EMS_Boiler.type_id); + } } return ok; @@ -629,7 +703,7 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c // we set the logging here void TelnetCallback(uint8_t event) { if (event == TELNET_EVENT_CONNECT) { - ems_setLogging(EMS_SYS_LOGGING_NONE); + ems_setLogging(EMS_SYS_LOGGING_DEFAULT); } else if (event == TELNET_EVENT_DISCONNECT) { ems_setLogging(EMS_SYS_LOGGING_NONE); } @@ -861,9 +935,7 @@ void WIFICallback() { myDebug("[UART] Opened Rx/Tx connection"); #endif - // now that we're connected, set up the boiler and thermostat - // the boiler's version will be requested and if there is no thermostat hardcoded it will try - // and find the first one + // now that we're connected, find out what boiler and thermostats we have specified ems_discoverModels(); } @@ -876,6 +948,9 @@ void initEMSESP() { EMSESP_Status.timestamp = millis(); EMSESP_Status.dallas_sensors = 0; + EMSESP_Status.led_gpio = EMSESP_LED_GPIO; + EMSESP_Status.dallas_gpio = EMSESP_DALLAS_GPIO; + // shower settings EMSESP_Shower.timerStart = 0; EMSESP_Shower.timerPause = 0; @@ -892,14 +967,14 @@ void do_publishValues() { } // callback to light up the LED, called via Ticker every second -// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << BOILER_LED)); // 4 is on, 8 is off +// fast way is to use WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + (state ? 4 : 8), (1 << EMSESP_Status.led_gpio)); // 4 is on, 8 is off void do_ledcheck() { if (EMSESP_Status.led_enabled) { if (ems_getBusConnected()) { - digitalWrite(BOILER_LED, (BOILER_LED == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off + digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? LOW : HIGH); // light on. For onboard LED high=off } else { - int state = digitalRead(BOILER_LED); - digitalWrite(BOILER_LED, !state); + int state = digitalRead(EMSESP_Status.led_gpio); + digitalWrite(EMSESP_Status.led_gpio, !state); } } } @@ -1019,6 +1094,12 @@ void showerCheck() { // SETUP // void setup() { + // init our own parameters + initEMSESP(); + + // call ems.cpp's init function to set all the internal params + ems_init(); + #ifndef DEBUG_SUPPORT // Timers using Ticker library publishValuesTimer.attach(PUBLISHVALUES_TIME, do_publishValues); // post MQTT values @@ -1031,24 +1112,22 @@ void setup() { myESP.setWIFI(WIFI_SSID, WIFI_PASSWORD, WIFICallback); myESP.setMQTT( MQTT_HOST, MQTT_USER, MQTT_PASS, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_PAYLOAD, MQTTCallback); + + // custom settings in SPIFFS myESP.setSettings(FSCallback, SettingsCallback); - myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION); // start it all up - - // init our own parameters - initEMSESP(); + // start up all the services + myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION); // set pin for LED - pinMode(BOILER_LED, OUTPUT); - digitalWrite(BOILER_LED, (BOILER_LED == LED_BUILTIN) ? HIGH : LOW); // light off. For onboard high=off - ledcheckTimer.attach(LEDCHECK_TIME, do_ledcheck); // blink heartbeat LED + if (EMSESP_Status.led_gpio != EMS_VALUE_INT_NOTSET) { + pinMode(EMSESP_Status.led_gpio, OUTPUT); + digitalWrite(EMSESP_Status.led_gpio, (EMSESP_Status.led_gpio == LED_BUILTIN) ? HIGH : LOW); // light off. For onboard high=off + ledcheckTimer.attach(LEDCHECK_TIME, do_ledcheck); // blink heartbeat LED + } // check for Dallas sensors - EMSESP_Status.dallas_sensors = ds18.setup(TEMPERATURE_SENSOR_PIN); - - // init the EMS bus - // call ems.cpp's init function to set all the internal params - ems_init(MY_THERMOSTAT_MODELID); + EMSESP_Status.dallas_sensors = ds18.setup(EMSESP_Status.dallas_gpio); // returns #sensors } // diff --git a/src/ems.cpp b/src/ems.cpp index 80e77f032..93fb75635 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -143,7 +143,7 @@ uint8_t _last_TxTelgramCRC; // CRC of last Tx sent, for checking duplicates // init stats and counters and buffers // uses -255 or 255 for values that haven't been set yet (EMS_VALUE_INT_NOTSET and EMS_VALUE_FLOAT_NOTSET) -void ems_init(uint8_t thermostat_modelid) { +void ems_init() { // overall status EMS_Sys_Status.emsRxPgks = 0; EMS_Sys_Status.emsTxPkgs = 0; @@ -224,15 +224,13 @@ void ems_init(uint8_t thermostat_modelid) { EMS_Boiler.tapwaterActive = EMS_VALUE_INT_NOTSET; // Hot tap water is on/off EMS_Boiler.heatingActive = EMS_VALUE_INT_NOTSET; // Central heating is on/off - // boiler is hardcoded - EMS_Boiler.type_id = EMS_ID_BOILER; // fixed at 0x08 + // set boiler type EMS_Boiler.product_id = 0; strlcpy(EMS_Boiler.version, "not set", sizeof(EMS_Boiler.version)); // set thermostat model - EMS_Thermostat.type_id = EMS_ID_NONE; // fixed at 0x08 + EMS_Thermostat.model_id = EMS_MODEL_NONE; EMS_Thermostat.product_id = 0; - _ems_setThermostatModel(thermostat_modelid); strlcpy(EMS_Thermostat.version, "not set", sizeof(EMS_Thermostat.version)); // counters @@ -240,8 +238,8 @@ void ems_init(uint8_t thermostat_modelid) { _emsTxRetryCount = 0; _last_TxTelgramCRC = 0; - // default logging is non - ems_setLogging(EMS_SYS_LOGGING_NONE); + // default logging is none + ems_setLogging(EMS_SYS_LOGGING_DEFAULT); } // Getters and Setters for parameters @@ -469,15 +467,15 @@ void _ems_sendTelegram() { return; } - // if Tx is disabled, don't do anything and ignore the request - // this could be because the boiler has yet to be found and the type_id is still empty + // if there is no destination, also delete it from the queue if (EMS_TxTelegram.dest == EMS_ID_NONE) { EMS_Sys_Status.emsTxStatus = EMS_TX_IDLE; // finished sending EMS_TxQueue.shift(); // remove from queue return; } - // if there is no destination, also delete it from the queue + // if Tx is disabled, don't do anything and ignore the request + // this could be because the boiler has yet to be found and the type_id is still empty if (!EMS_Sys_Status.emsTxEnabled) { myDebug("Tx is disabled. Ignoring %s request to 0x%02X.", ((EMS_TxTelegram.action == EMS_TX_TELEGRAM_WRITE) ? "write" : "read"), @@ -516,6 +514,7 @@ void _ems_sendTelegram() { // myDebug("Duplicate message, just sent this one, so removing from queue!"); EMS_Sys_Status.emsTxStatus = EMS_TX_IDLE; // finished sending EMS_TxQueue.shift(); // remove the last Tx from the queue + return; } _last_TxTelgramCRC = crc; @@ -770,8 +769,8 @@ void _ems_processTelegram(uint8_t * telegram, uint8_t length) { */ void _processType(uint8_t * telegram, uint8_t length) { // header - uint8_t src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes - uint8_t dest = telegram[1] & 0x7F; // remove 8th bit to handle both reads and writes + uint8_t src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here + uint8_t dest = telegram[1] & 0x7F; uint8_t type = telegram[2]; uint8_t * data = telegram + 4; // data block starts at position 5 @@ -781,27 +780,33 @@ void _processType(uint8_t * telegram, uint8_t length) { return; } - // did we request this telegram? If so it would be either a read or a validate telegram and still on the - // Tx queue with the same type + // did we request this telegram? If so it would be either a read or a validate telegram and still + // on top of the Tx queue with the same type. because we don't remove a Tx from the queue after a send // if its a validate check the value, or if its a read, update the Read counter // then we can safely removed the read/validate from the queue if ((dest == EMS_ID_ME) && (!EMS_TxQueue.isEmpty())) { _EMS_TxTelegram EMS_TxTelegram = EMS_TxQueue.first(); // get current Tx package we last sent - // do the types match? If so we were expecting this response back to us + // do the types match? If so we were expecting this telegram response back to us if (EMS_TxTelegram.type == type) { - emsaurt_tx_poll(); // send Acknowledgement back to free the EMS bus + emsaurt_tx_poll(); // send Acknowledgement back to free the EMS bus since we have the telegram - // if last action was a read, we are just happy that we actually got a response back + // if last action was a read, go ahead and process it if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_READ) { - EMS_Sys_Status.emsRxPgks++; // increment rx counter - _emsTxRetryCount = 0; // reset retry count - _ems_processTelegram(telegram, length); // and process the telegram + EMS_Sys_Status.emsRxPgks++; // increment rx counter + _emsTxRetryCount = 0; // reset retry count + // + // and process the telegram. This calls the main function to process each type + // + _ems_processTelegram(telegram, length); if (EMS_TxTelegram.forceRefresh) { ems_setEmsRefreshed(true); // set the MQTT refresh flag to force sending to MQTT } EMS_TxQueue.shift(); // remove read from queue, all done now - } else if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_VALIDATE) { + return; // quit + } + + if (EMS_TxTelegram.action == EMS_TX_TELEGRAM_VALIDATE) { // this read was for a validate. Do a compare on the 1 byte result from the last write uint8_t dataReceived = data[0]; // only a single byte is returned after a read if (EMS_TxTelegram.comparisonValue == dataReceived) { @@ -816,7 +821,7 @@ void _processType(uint8_t * telegram, uint8_t length) { } else { // write failed. if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("Last write failed. Compared set value 0x%02X with received value 0x%02X.", + myDebug("Last write failed. Compared set value 0x%02X with received value 0x%02X", EMS_TxTelegram.comparisonValue, dataReceived); } @@ -838,9 +843,18 @@ void _processType(uint8_t * telegram, uint8_t length) { EMS_TxQueue.unshift(EMS_TxTelegram); // add back to queue making it next in line } } + + return; } } // telegram was for us, but seems we didn't ask for it + // so kinda invalidates the last action on the queue remove from queue + myDebug("Telegram sent to us but we didn't ask for it! (src=0x%02X dest=0x%02X type=0x%02X)", + telegram[0] & 0x7F, + telegram[1] & 0x7F, + telegram[2]); + // ems_printTxQueue(); + // EMS_TxQueue.shift(); // remove validate from queue } else { // we didn't request it, was for somebody else or a broadcast, process it anyway _ems_processTelegram(telegram, length); @@ -1055,6 +1069,7 @@ void _process_Version(uint8_t * data, uint8_t length) { return; } + bool do_save = false; uint8_t product_id = data[0]; char version[10] = {0}; snprintf(version, sizeof(version), "%02d.%02d", data[1], data[2]); @@ -1072,29 +1087,33 @@ void _process_Version(uint8_t * data, uint8_t length) { if (typeFound) { // its a boiler - if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("Boiler found. Model %s with TypeID 0x%02X, Product ID %d, Version %s", + myDebug("Boiler type device found. Model %s with TypeID 0x%02X, Product ID %d, Version %s", + Boiler_Types[i].model_string, + Boiler_Types[i].type_id, + product_id, + version); + + // if its a boiler set it + // it will take the first one found in the list + if ((EMS_Boiler.type_id == EMS_ID_NONE) || (EMS_Boiler.type_id == Boiler_Types[i].type_id)) { + myDebug("* Setting Boiler type to Model %s, TypeID 0x%02X, Product ID %d, Version %s", Boiler_Types[i].model_string, Boiler_Types[i].type_id, product_id, version); - } - // if its a boiler set it (even if it was already set) - if (Boiler_Types[i].type_id == EMS_ID_BOILER) { - if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("* Setting boiler to this new type"); - } EMS_Boiler.type_id = Boiler_Types[i].type_id; EMS_Boiler.product_id = Boiler_Types[i].product_id; strlcpy(EMS_Boiler.version, version, sizeof(EMS_Boiler.version)); + do_save = true; + ems_getBoilerValues(); // get Boiler values that we would usually have to wait for } return; } - // its not a boiler, maybe its a known thermostat + // its not a boiler, maybe its a known thermostat? i = 0; while (i < _Thermostat_Types_max) { if (Thermostat_Types[i].product_id == product_id) { @@ -1115,11 +1134,14 @@ void _process_Version(uint8_t * data, uint8_t length) { } // if we don't have a thermostat set, use this one - // if its the one we hard coded, refresh the data anyway - if ((EMS_Thermostat.model_id == EMS_MODEL_NONE) || (EMS_Thermostat.model_id == Thermostat_Types[i].model_id)) { - if (EMS_Sys_Status.emsLogging >= EMS_SYS_LOGGING_BASIC) { - myDebug("* Setting thermostat to this new type"); - } + if ((EMS_Thermostat.type_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE) + || (EMS_Thermostat.type_id == Thermostat_Types[i].type_id)) { + myDebug("* Setting Thermostat type to Model %s, TypeID 0x%02X, Product ID %d, Version %s", + Thermostat_Types[i].model_string, + Thermostat_Types[i].type_id, + product_id, + version); + EMS_Thermostat.model_id = Thermostat_Types[i].model_id; EMS_Thermostat.type_id = Thermostat_Types[i].type_id; EMS_Thermostat.read_supported = Thermostat_Types[i].read_supported; @@ -1127,12 +1149,19 @@ void _process_Version(uint8_t * data, uint8_t length) { EMS_Thermostat.product_id = product_id; strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version)); + do_save = true; + // get Thermostat values (if supported) ems_getThermostatValues(); } } else { myDebug("Unrecognized device found. Product ID %d, Version %s", product_id, version); } + + // if the boiler or thermostat values have changed, save them to SPIFFS + if (do_save) { + myESP.fs_saveConfig(); + } } /* @@ -1140,15 +1169,15 @@ void _process_Version(uint8_t * data, uint8_t length) { */ void ems_discoverModels() { // boiler - ems_doReadCommand(EMS_TYPE_Version, EMS_ID_BOILER); // get version details of boiler - ems_getBoilerValues(); // fetch values from boiler, instead of waiting for broadcasts + ems_doReadCommand(EMS_TYPE_Version, EMS_Boiler.type_id); // get version details of boiler + ems_getBoilerValues(); // fetch values from boiler, instead of waiting for broadcasts // thermostat - if (EMS_Thermostat.model_id == EMS_MODEL_NONE) { + // if it hasn't been set, auto discover it + if (EMS_Thermostat.type_id == EMS_ID_NONE) { ems_scanDevices(); // auto-discover it } else { // set the model as hardcoded (see my_devices.h) and fetch the version and product id - _ems_setThermostatModel(EMS_Thermostat.model_id); ems_doReadCommand(EMS_TYPE_Version, EMS_Thermostat.type_id); ems_getThermostatValues(); } diff --git a/src/ems.h b/src/ems.h index c3461e116..eb306ba80 100644 --- a/src/ems.h +++ b/src/ems.h @@ -13,19 +13,15 @@ #include // EMS IDs -#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs -#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key" -#define EMS_ID_BOILER 0x08 // Fixed - boilers are always 0x08. I think. +#define EMS_ID_NONE 0x00 // Fixed - used as a dest in broadcast messages and empty type IDs +#define EMS_ID_ME 0x0B // Fixed - our device, hardcoded as the "Service Key" +#define EMS_ID_DEFAULT_BOILER 0x08 #define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC // max length of a telegram, including CRC, for Rx and Tx. -// This can differs per firmware version and typically 32 is the max #define EMS_MAX_TELEGRAM_LENGTH 99 -// Common for all EMS devices -#define EMS_TYPE_Version 0x02 // version of the UBA controller (boiler) - // default values #define EMS_VALUE_INT_ON 1 // boolean true #define EMS_VALUE_INT_OFF 0 // boolean false @@ -60,6 +56,8 @@ #define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue +//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE +#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE /* EMS UART transfer status */ typedef enum { @@ -251,7 +249,7 @@ typedef struct { // function definitions extern void ems_parseTelegram(uint8_t * telegram, uint8_t len); -void ems_init(uint8_t thermostat_modelid); +void ems_init(); void ems_doReadCommand(uint8_t type, uint8_t dest, bool forceRefresh = false); void ems_sendRawTelegram(char * telegram); diff --git a/src/ems_devices.h b/src/ems_devices.h index b9a9553eb..987bb8936 100644 --- a/src/ems_devices.h +++ b/src/ems_devices.h @@ -3,7 +3,7 @@ * * Paul Derbyshire - https://github.com/proddy/EMS-ESP * - * See ChangeLog.md for history + * See ChangeLog.md for History * See README.md for Acknowledgments * */ @@ -12,6 +12,12 @@ #include "ems.h" + +/* + * Common + */ +#define EMS_TYPE_Version 0x02 + /* * Boiler... */ @@ -91,11 +97,10 @@ typedef enum { } _EMS_MODEL_ID; -// EMS types for known Buderus devices. This list will be extended when new devices are recognized. +// EMS types for known Buderus/Bosch devices. This list will be extended when new devices are recognized. // format is MODEL_ID, PRODUCT ID, TYPE_ID, DESCRIPTION const _Boiler_Type Boiler_Types[] = { - // various boilers and buderus type devices {EMS_MODEL_UBA, 72, 0x08, "MC10"}, {EMS_MODEL_UBA, 123, 0x08, "Buderus GB172/Nefit Trendline"}, {EMS_MODEL_UBA, 115, 0x08, "Nefit Topline Compact"}, diff --git a/src/my_config.h b/src/my_config.h index 3dd398a40..e127d7b4f 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -10,15 +10,6 @@ #include "ems.h" -// Set your wifi and mqtt params -// If set to NULL (default) you'll need to set them in AP mode using the 'set' command -// as these values are stored in SPIFFs for persisted -#define WIFI_SSID NULL -#define WIFI_PASSWORD NULL -#define MQTT_HOST NULL -#define MQTT_USER NULL -#define MQTT_PASS NULL - // All MQTT topics are prefixed with the following string #define MQTT_BASE "home" #define MQTT_TOPIC_START "start" @@ -29,21 +20,53 @@ #define MQTT_KEEPALIVE 300 #define MQTT_QOS 1 +// thermostat +#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values +#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes +#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes +#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature +#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature +#define THERMOSTAT_MODE "thermostat_mode" // mode + +// boiler +#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values +#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running +#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on + +// shower time +#define TOPIC_SHOWERTIME "showertime" // for sending shower time results +#define TOPIC_SHOWER_TIMER "shower_timer" // toggle switch for enabling the shower logic +#define TOPIC_SHOWER_ALERT "shower_alert" // toggle switch for enabling the shower alarm logic +#define TOPIC_SHOWER_COLDSHOT "shower_coldshot" // used to trigger a coldshot from an MQTT command + // default values for shower logic on/off #define BOILER_SHOWER_TIMER 1 // enable (1) to monitor shower time #define BOILER_SHOWER_ALERT 0 // enable (1) to send alert of cold water when shower time limit has exceeded #define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water +////////////////////////////////////////////////////////////////////////////////////////////////// +// THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) // +////////////////////////////////////////////////////////////////////////////////////////////////// + +// Set your wifi and mqtt params +// If set to NULL (default) you'll need to set them in AP mode using the 'set' command +#define WIFI_SSID NULL +#define WIFI_PASSWORD NULL +#define MQTT_HOST NULL +#define MQTT_USER NULL +#define MQTT_PASS NULL + // Set LED pin used for showing ems bus connection status. Solid is connected, Flashing is error -// can be either the onboard LED on the ESP8266 or external via an external pull-up LED (e.g. D1 on bbqkees' board) +// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED (e.g. D1 on bbqkees' board) // can be enabled and disabled via the 'set led' command -#define BOILER_LED LED_BUILTIN // LED_BULLETIN for onboard LED +// pin can be set by 'set led_gpio' +#define EMSESP_LED_GPIO LED_BUILTIN // set this if using an external temperature sensor like a DS18B20 // D5 is the default on bbqkees' board -#define TEMPERATURE_SENSOR_PIN D5 +#define EMSESP_DALLAS_GPIO D5 // By default the EMS bus will be scanned for known devices based on product ids in ems_devices.h -// You can override the Thermostat model by hardcoding the model type -#define MY_THERMOSTAT_MODELID EMS_MODEL_NONE -//#define MY_THERMOSTAT_MODELID EMS_MODEL_RC35 +// You can override the Thermostat and Boiler types here +#define EMSESP_BOILER_TYPE EMS_ID_NONE +#define EMSESP_THERMOSTAT_TYPE EMS_ID_NONE diff --git a/src/version.h b/src/version.h index 3bcf0df01..7c5bdf310 100644 --- a/src/version.h +++ b/src/version.h @@ -6,5 +6,5 @@ #pragma once #define APP_NAME "EMS-ESP" -#define APP_VERSION "1.4.0" +#define APP_VERSION "1.4.1" #define APP_HOSTNAME "ems-esp"