diff --git a/lib/MyESP/MyESP.cpp b/lib/MyESP/MyESP.cpp index 7e8dd644c..8e68bd862 100644 --- a/lib/MyESP/MyESP.cpp +++ b/lib/MyESP/MyESP.cpp @@ -2165,9 +2165,9 @@ void MyESP::loop() { _systemCheckLoop(); _heartbeatCheck(); _bootupSequence(); - webServer.handleClient(); // web server client requests + webServer.handleClient(); _telnetHandle(); - _mqttConnect(); // MQTT + _mqttConnect(); yield(); // ...and breath } diff --git a/platformio.ini-example b/platformio.ini-example index f2fa1f1a2..c99ece822 100644 --- a/platformio.ini-example +++ b/platformio.ini-example @@ -7,7 +7,7 @@ default_envs = debug [common] -; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DNO_SERIAL -DNO_GLOBAL_EEPROM +; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DNO_SERIAL -DNO_GLOBAL_EEPROM -DLOGICANALYZER extra_flags = -DNO_GLOBAL_EEPROM [env] diff --git a/src/ems-esp.cpp b/src/ems-esp.cpp index d1a80600f..a95f8ed7f 100644 --- a/src/ems-esp.cpp +++ b/src/ems-esp.cpp @@ -755,6 +755,14 @@ void publishValues(bool force) { if (EMS_Boiler.wWHeat != EMS_VALUE_INT_NOTSET) rootBoiler["wWHeat"] = _bool_to_char(s, EMS_Boiler.wWHeat); + // **** also add burnStarts, burnWorkMin, heatWorkMin + if (abs(EMS_Boiler.burnStarts) != EMS_VALUE_LONG_NOTSET) + rootBoiler["burnStarts"] = (double)EMS_Boiler.burnStarts; + if (abs(EMS_Boiler.burnWorkMin) != EMS_VALUE_LONG_NOTSET) + rootBoiler["burnWorkMin"] = (double)EMS_Boiler.burnWorkMin; + if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET) + rootBoiler["heatWorkMin"] = (double)EMS_Boiler.heatWorkMin; + rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; @@ -1842,25 +1850,41 @@ void showerCheck() { // SETUP // void setup() { - initEMSESP(); // init parameters + // LA trigger create a small puls to show setup is starting... + INIT_MARKERS(0); + LA_PULSE(50); + // GPIO15 has a pull down, so we must set it to HIGH + pinMode(15, OUTPUT); + digitalWrite(15,1); + + // init our own parameters + initEMSESP(); + + // call ems.cpp's init function to set all the internal params + ems_init(); + + systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable + + // set up myESP for Wifi, MQTT, MDNS and Telnet myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands - myESP.setWIFI(NULL, NULL, WIFICallback); // empty ssid and password as we take this from the config file - myESP.setMQTT(NULL, - NULL, - NULL, - MQTT_BASE, - MQTT_KEEPALIVE, - MQTT_QOS, - MQTT_RETAIN, - MQTT_WILL_TOPIC, - MQTT_WILL_ONLINE_PAYLOAD, - MQTT_WILL_OFFLINE_PAYLOAD, - MQTTCallback); // MQTT host, username and password taken from the SPIFFS settings - myESP.setOTA(OTACallback_pre, OTACallback_post); // OTA callback which is called when OTA is starting and stopping - myESP.setSettings(FSCallback, SettingsCallback); // custom settings in SPIFFS - myESP.setWeb(WebCallback); // web custom settings - myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION); // start up all the services + myESP.setWIFI(NULL, NULL, WIFICallback); // empty ssid and password as we take this from the config file + + // MQTT host, username and password taken from the SPIFFS settings + myESP.setMQTT( + NULL, NULL, NULL, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_ONLINE_PAYLOAD, MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback); + + // OTA callback which is called when OTA is starting and stopping + myESP.setOTA(OTACallback_pre, OTACallback_post); + + // custom settings in SPIFFS + myESP.setSettings(FSCallback, SettingsCallback); + + // web custom settings + myESP.setWeb(WebCallback); + + // start up all the services + myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION); // at this point we have all the settings from our internall SPIFFS config file // fire up the UART now diff --git a/src/ems.cpp b/src/ems.cpp index ae996911f..68d0e2ff3 100644 --- a/src/ems.cpp +++ b/src/ems.cpp @@ -732,9 +732,23 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { EMS_RxTelegram.timestamp = millis(); EMS_RxTelegram.length = length; - // check if we just received a single byte - // it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80) - // or either a return code like 0x01 or 0x04 from the last Write command + /* + * check if we just received a single byte + * it could well be a Poll request from the boiler for us, which will have a value of 0x8B (0x0B | 0x80) + * or either a return code like 0x01 or 0x04 from the last Write command + * Roger Wilco: we have different types here: + * EMS_ID_ME && length == 1 && EMS_TX_STATUS_IDLE && EMS_RX_STATUS_IDLE: polling request + * EMS_ID_ME && length > 1 && EMS_TX_STATUS_IDLE && EMS_RX_STATUS_IDLE: direct telegram + * (EMS_TX_SUCCESS || EMS_TX_ERROR) && EMS_TX_STATUS_WAIT: response, free the EMS bus + * + * In addition, it may happen that we where interrupted (f.e. by WIFI activity) and the + * buffer isn't valid anymore, so we must not answer at all... + */ + if (EMS_Sys_Status.emsRxStatus != EMS_RX_STATUS_IDLE) { + myDebug_P(PSTR("** We missed the bus - Rx non-idle!")); + return; + } + if (length == 1) { uint8_t value = telegram[0]; // 1st byte of data package @@ -824,6 +838,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) { // Assume at this point we have something that vaguely resembles a telegram in the format [src] [dest] [type] [offset] [data] [crc] // validate the CRC, if it's bad ignore it if (telegram[length - 1] != _crcCalculator(telegram, length)) { + LA_PULSE(200); EMS_Sys_Status.emxCrcErr++; if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_VERBOSE) { _debugPrintTelegram("Corrupt telegram: ", &EMS_RxTelegram, COLOR_RED, true); diff --git a/src/ems.h b/src/ems.h index 34a0662ba..406714a05 100644 --- a/src/ems.h +++ b/src/ems.h @@ -12,6 +12,50 @@ #include +/* debug helper for logic analyzer + * create marker puls on GPIOx + * ° for Rx, we use GPIO14 + * ° for Tx, we use GPIO12 + */ +#ifdef LOGICANALYZER + #define RX_MARK_PIN 14 + #define TX_MARK_PIN 12 + + #define RX_MARK_MASK (1<> USRXC) & 0xFF) { @@ -39,20 +39,20 @@ static void emsuart_rx_intr_handler(void * para) { // clear Rx FIFO full and Rx FIFO timeout interrupts USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO); } + GPIO_L(RX_MARK_MASK); // BREAK detection = End of EMS data block if (USIS(EMSUART_UART) & ((1 << UIBD))) { - ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them - + ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt pEMSRxBuf->length = length; os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package + ETS_UART_INTR_ENABLE(); // re-enable UART interrupts system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity - - ETS_UART_INTR_ENABLE(); // re-enable UART interrupts + RX_PULSE(EMSUART_BIT_TIME / 2); } } @@ -63,21 +63,22 @@ static void emsuart_rx_intr_handler(void * para) { */ static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) { _EMSRxBuf * pCurrent = pEMSRxBuf; - uint8_t length = pCurrent->length; // number of bytes including the BRK at the end + pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer + uint8_t length = pCurrent->length; // number of bytes including the BRK at the end + pCurrent->length = 0; // validate and transmit the EMS buffer, excluding the BRK if (length == 2) { + RX_PULSE(20); // it's a poll or status code, single byte and ok to send on ems_parseTelegram((uint8_t *)pCurrent->buffer, 1); } else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1) && (pCurrent->buffer[length - 2] != 0x00)) { // ignore double BRK at the end, possibly from the Tx loopback // also telegrams with no data value + RX_PULSE(40); ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK } - - memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe - - pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer + // memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe } /* @@ -117,11 +118,14 @@ void ICACHE_FLASH_ATTR emsuart_init() { // conf1 params // UCTOE = RX TimeOut enable (default is 1) - // UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 2 characters (default is 2) + // UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2) // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf - USC1(EMSUART_UART) = 0; // reset config first - USC1(EMSUART_UART) = (EMS_MAX_TELEGRAM_LENGTH << UCFFT) | (0x02 << UCTOT) | (1 << UCTOE); // enable interupts + // + // change: we set UCFFT to 1 to get an immediate indicator about incoming trafffic. + // Otherwise, we're only noticed by UCTOT or RxBRK! + USC1(EMSUART_UART) = 0; // reset config first + USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts // set interrupts for triggers USIC(EMSUART_UART) = 0xFFFF; // clear all interupts @@ -177,6 +181,7 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() { // To create a 11-bit we set TXD_BRK bit so the break signal will // automatically be sent when the tx fifo is empty tmp = (1 << UCBRK); + GPIO_H(TX_MARK_MASK); USC0(EMSUART_UART) |= (tmp); // set bit if (EMS_Sys_Status.emsTxMode <= 1) { // classic mode and ems+ (0, 1) @@ -186,86 +191,128 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() { } USC0(EMSUART_UART) &= ~(tmp); // clear bit + GPIO_L(TX_MARK_MASK); } /* * Send to Tx, ending with a */ -void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { - if (len == 0) - return; - - // temp code until we get mode 2 working without resets - - if (EMS_Sys_Status.emsTxMode == 0) { // classic mode logic - for (uint8_t i = 0; i < len; i++) { - USF(EMSUART_UART) = buf[i]; - } - emsuart_tx_brk(); // send - } else if (EMS_Sys_Status.emsTxMode == 1) { // With extra tx delay for EMS+ - for (uint8_t i = 0; i < len; i++) { - USF(EMSUART_UART) = buf[i]; - delayMicroseconds(EMSUART_TX_BRK_WAIT); // https://github.com/proddy/EMS-ESP/issues/23# - } - emsuart_tx_brk(); // send - } else if (EMS_Sys_Status.emsTxMode == 3) { // Junkers logic by @philrich - for (uint8_t i = 0; i < len; i++) { - USF(EMSUART_UART) = buf[i]; - - // just to be safe wait for tx fifo empty (needed?) - while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0) - ; - - // wait until bits are sent on wire - delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP); - } - emsuart_tx_brk(); // send - } else if (EMS_Sys_Status.emsTxMode == 2) { - /* - * based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch - * we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO. - * after sending the last char we poll the Rx status until either - * - size(Rx FIFO) == size(Tx-Telegram) - * - is detected - * At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode. - */ - ETS_UART_INTR_DISABLE(); // disable rx interrupt - - // clear Rx status register - USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo - emsuart_flush_fifos(); - - // throw out the telegram... - for (uint8_t i = 0; i < len;) { - USF(EMSUART_UART) = buf[i++]; // send each Tx byte - // wait for echo from busmaster - while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) < i || (USIS(EMSUART_UART) & (1 << UIBD)))) { - delayMicroseconds(EMSUART_BIT_TIME); // burn CPU cycles... +_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { + _EMS_TX_STATUS result = EMS_TX_STATUS_OK; + if (len) { + LA_PULSE(50); + // temp code until we get mode 2 working without resets + if (EMS_Sys_Status.emsTxMode == 0) { // classic mode logic + for (uint8_t i = 0; i < len; i++) { + TX_PULSE(EMSUART_BIT_TIME / 4); + USF(EMSUART_UART) = buf[i]; } - } + emsuart_tx_brk(); // send + } else if (EMS_Sys_Status.emsTxMode == 1) { // With extra tx delay for EMS+ + for (uint8_t i = 0; i < len; i++) { + TX_PULSE(EMSUART_BIT_TIME / 4); + USF(EMSUART_UART) = buf[i]; + delayMicroseconds(EMSUART_TX_BRK_WAIT); // https://github.com/proddy/EMS-ESP/issues/23# + } + emsuart_tx_brk(); // send + } else if (EMS_Sys_Status.emsTxMode == 3) { // Junkers logic by @philrich + for (uint8_t i = 0; i < len; i++) { + TX_PULSE(EMSUART_BIT_TIME / 4); + USF(EMSUART_UART) = buf[i]; - // we got the whole telegram in the Rx buffer - // on Rx-BRK (bus collision), we simply enable Rx and leave it - // otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT. - // worst case, we'll see an additional Rx-BRK... - if (!(USIS(EMSUART_UART) & (1 << UIBD))) { - // no bus collision - send terminating BRK signal - USC0(EMSUART_UART) |= (1 << UCLBE); // enable loopback - USC0(EMSUART_UART) |= (1 << UCBRK); // set + // just to be safe wait for tx fifo empty (needed?) + while (((USS(EMSUART_UART) >> USTXC) & 0xff) != 0) + ; - // wait until BRK detected... - while (!(USIS(EMSUART_UART) & (1 << UIBD))) { - delayMicroseconds(EMSUART_BIT_TIME); + // wait until bits are sent on wire + delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP); + } + emsuart_tx_brk(); // send + } else if (EMS_Sys_Status.emsTxMode == 2) { + /* + * + * based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch + * we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO. + * after sending the last char we poll the Rx status until either + * - size(Rx FIFO) == size(Tx-Telegram) + * - is detected + * At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode. + * + * EMS-Bus error handling + * 1. Busmaster stops echoing on Tx w/o permission + * 2. Busmaster cancel telegram by sending a BRK + * + * Case 1. is handled by a watchdog counter which is reset on each + * Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus + * some smart guess for processing time on targeted EMS device. + * We set EMS_Sys_Status.emsTxStatus to EMS_TX_WTD_TIMEOUT and return + * + * Case 2. is handled via a BRK chk during transmission. + * We set EMS_Sys_Status.emsTxStatus to EMS_TX_BRK_DETECT and return + * + */ + +// shorter busy poll... +#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8) +#define EMS_TX_TO_COUNT ((20 + 10000 / EMSUART_BIT_TIME) * 8) + uint16_t wdc = EMS_TX_TO_COUNT; + + ETS_UART_INTR_DISABLE(); // disable rx interrupt + + // clear Rx status register + USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo + emsuart_flush_fifos(); + + // throw out the telegram... + for (uint8_t i = 0; i < len && result == EMS_TX_STATUS_OK;) { + GPIO_H(TX_MARK_MASK); + + wdc = EMS_TX_TO_COUNT; + volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF; + USF(EMSUART_UART) = buf[i++]; // send each Tx byte + // wait for echo from busmaster + GPIO_L(TX_MARK_MASK); + + while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) { + delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles... + if (--wdc == 0) { + EMS_Sys_Status.emsTxStatus = result = EMS_TX_WTD_TIMEOUT; + break; + } + if (USIR(EMSUART_UART) & (1 << UIBD)) { + USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ + EMS_Sys_Status.emsTxStatus = result = EMS_TX_BRK_DETECT; + } + } } - USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear + // we got the whole telegram in the Rx buffer + // on Rx-BRK (bus collision), we simply enable Rx and leave it + // otherwise we send the final Tx-BRK in the loopback and re=enable Rx-INT. + // worst case, we'll see an additional Rx-BRK... + if (result != EMS_TX_STATUS_OK) { + LA_PULSE(200); // mark Tx error + } else { + // neither bus collision nor timeout - send terminating BRK signal + GPIO_H(TX_MARK_MASK); + if (!(USIS(EMSUART_UART) & (1 << UIBD))) { + // no bus collision - send terminating BRK signal + USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set - USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ - USC0(EMSUART_UART) &= ~(1 << UCLBE); // disable loopback + // wait until BRK detected... + while (!(USIR(EMSUART_UART) & (1 << UIBD))) { + delayMicroseconds(EMSUART_BUSY_WAIT); + } + + USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear + USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ + } + GPIO_L(TX_MARK_MASK); + } + ETS_UART_INTR_ENABLE(); // receive anything from FIFO... } - - ETS_UART_INTR_ENABLE(); // receive anything from FIFO... } + return result; } /* diff --git a/src/emsuart.h b/src/emsuart.h index c49e2f6a8..14e00ce1f 100644 --- a/src/emsuart.h +++ b/src/emsuart.h @@ -8,6 +8,7 @@ #pragma once #include +#include #define EMSUART_UART 0 // UART 0 #define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity) @@ -35,5 +36,5 @@ typedef struct { void ICACHE_FLASH_ATTR emsuart_init(); void ICACHE_FLASH_ATTR emsuart_stop(); void ICACHE_FLASH_ATTR emsuart_start(); -void ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); +_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); void ICACHE_FLASH_ATTR emsuart_tx_poll(); diff --git a/src/my_config.h b/src/my_config.h index 20575b25d..f83ff92b9 100644 --- a/src/my_config.h +++ b/src/my_config.h @@ -84,10 +84,15 @@ // can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio' #define EMSESP_LED_GPIO LED_BUILTIN +#ifdef LOGICANALYZER +#define EMSESP_DALLAS_GPIO D1 +#define EMSESP_DALLAS_PARASITE false +#else // set this if using an external temperature sensor like a DS18B20 // D5 is the default on a bbqkees board #define EMSESP_DALLAS_GPIO D5 #define EMSESP_DALLAS_PARASITE false +#endif // By default the EMS bus will be scanned for known devices based on the product ids in ems_devices.h // You can override the Thermostat and Boiler types here diff --git a/src/version.h b/src/version.h index 6c7a7827f..69c10001e 100644 --- a/src/version.h +++ b/src/version.h @@ -6,5 +6,5 @@ #pragma once #define APP_NAME "EMS-ESP" -#define APP_VERSION "1.8.1b20" +#define APP_VERSION "1.8.1b21" #define APP_HOSTNAME "ems-esp"