From 04160106c2aa9bc0638ef77a68996fff72be3bb7 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:02:49 +0200 Subject: [PATCH] Add commands DspLine and DspSpeed Add commands DspLine and DspSpeed (#15856) --- CHANGELOG.md | 2 + RELEASENOTES.md | 2 + tasmota/tasmota_support/support_features.ino | 2 +- ...ff.ino => xdrv_87_esp32_sonoff_tm1621.ino} | 295 ++++++++++++++++-- 4 files changed, 272 insertions(+), 29 deletions(-) rename tasmota/tasmota_xdrv_driver/{xdrv_87_tm1621_sonoff.ino => xdrv_87_esp32_sonoff_tm1621.ino} (52%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b18d77223..fc296d380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file. - Zigbee prepare for Green Power support (#16407) - Command ``SetOption146 1`` to enable display of ESP32 internal temperature - Support for DFRobot SEN0390 V30B ambient light sensor (#16105) +- Command ``DspSpeed 2..127`` to control message rotation speed on display of POWR3xxD and THR3xxD +- Command ``DspLine<1|2> ,,,,...`` to select message(s) on display of POWR3xxD and THR3xxD ### Changed - TasmotaModbus library from v3.5.0 to v3.6.0 (#16351) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a578efce2..3bd0c97ab 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -111,6 +111,8 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo ### Added - Command ``SetOption146 1`` to enable display of ESP32 internal temperature - Command ``StatusRetain`` [#11109](https://github.com/arendst/Tasmota/issues/11109) +- Command ``DspSpeed 2..127`` to control message rotation speed on display of POWR3xxD and THR3xxD +- Command ``DspLine<1|2> ,,,,...`` to select message(s) on display of POWR3xxD and THR3xxD - Support for SGP40 gas and air quality sensor [#16341](https://github.com/arendst/Tasmota/issues/16341) - Support for Modbus writing using ModbusBridge by JeroenSt [#16351](https://github.com/arendst/Tasmota/issues/16351) - Support for DFRobot SEN0390 V30B ambient light sensor [#16105](https://github.com/arendst/Tasmota/issues/16105) diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino index 73ecc03d7..a1410007c 100644 --- a/tasmota/tasmota_support/support_features.ino +++ b/tasmota/tasmota_support/support_features.ino @@ -819,7 +819,7 @@ void ResponseAppendFeatures(void) feature8 |= 0x40000000; // xlgt_09_sm2335.ino #endif #ifdef USE_DISPLAY_TM1621_SONOFF - feature8 |= 0x80000000; // xdrv_87_tm1621_sonoff.ino + feature8 |= 0x80000000; // xdrv_87_esp32_sonoff_tm1621.ino #endif } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino b/tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino similarity index 52% rename from tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino rename to tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino index 1feafe243..6649989ad 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_87_esp32_sonoff_tm1621.ino @@ -1,11 +1,12 @@ /* - xdrv_87_tm1621_sonoff.ino - Sonoff POWR3xxD and THR3xxD display support for Tasmota + xdrv_87_esp32_sonoff_tm1621.ino - Sonoff POWR3xxD and THR3xxD display support for Tasmota SPDX-FileCopyrightText: 2022 Theo Arends SPDX-License-Identifier: GPL-3.0-only */ +#ifdef ESP32 #ifdef USE_DISPLAY_TM1621_SONOFF /*********************************************************************************************\ * Sonoff POWR3xxD and THR3xxD LCD support @@ -13,11 +14,29 @@ * {"NAME":"Sonoff POWR316D","GPIO":[32,0,0,0,0,576,0,0,0,224,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} * {"NAME":"Sonoff POWR320D","GPIO":[32,0,9313,0,9312,576,0,0,0,0,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} * {"NAME":"Sonoff THR316D","GPIO":[32,0,0,0,225,9280,0,0,0,321,0,576,320,9184,9216,0,0,224,0,9248,0,1,0,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} + * {"NAME":"Sonoff THR316D GPIO26","GPIO":[32,0,0,0,225,9280,0,0,0,321,0,576,320,9184,9216,0,0,224,0,9248,0,1,1,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} * {"NAME":"Sonoff THR320D","GPIO":[32,0,0,0,226,9280,0,0,0,321,0,576,320,9184,9216,9312,0,0,9313,9248,0,1,0,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} + * {"NAME":"Sonoff THR320D GPIO26","GPIO":[32,0,0,0,226,9280,0,0,0,321,0,576,320,9184,9216,9312,0,0,9313,9248,0,1,1,3840,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} + * + * DspSpeed 2..127 = Display rotation speed in seconds if more than one value is requested + * DspLine<1|2> ,,,,... = Display specific JSON value and rotate between them + * unit 0 = None + * 1 = Temperature (Line 1 only) + * 2 = %RH (Line 2 only) + * 3 = Both V (Line 1 only) / A (Line 2 only) + * 4 = Both kWh (Line 1 only) / W (Line 2 only) + * DspLine1 0 and DspLine2 0 = Default of temperature/humidity + * + * Example: "SCD30":{"CarbonDioxide":746,"eCO2":727,"Temperature":30.6,"Humidity":43.6,"DewPoint":16.8} + * DspLine1 4,1,3,0 = Temperature and eCO2 + * DspLine2 2,0,5,2 = CarbonDioxide and humidity \*********************************************************************************************/ #define XDRV_87 87 +#define TM1621_ROTATE 5 // Seconds display rotation speed +#define TM1621_MAX_VALUES 8 // Default 8 x two different lines + #define TM1621_PULSE_WIDTH 10 // microseconds (Sonoff = 100) #define TM1621_SYS_EN 0x01 // 0b00000001 @@ -29,6 +48,7 @@ #define TM1621_IRQ_DIS 0x80 // 0b100x0xxx enum Tm1621Device { TM1621_USER, TM1621_POWR316D, TM1621_THR316D }; +enum Tm1621Units { TM1621_NONE, TM1621_TEMPERATURE, TM1621_HUMIDITY, TM1621_VOLTAGE_CURRENT, TM1621_ENERGY_POWER }; const uint8_t tm1621_commands[] = { TM1621_SYS_EN, TM1621_LCD_ON, TM1621_BIAS, TM1621_TIMER_DIS, TM1621_WDT_DIS, TM1621_TONE_OFF, TM1621_IRQ_DIS }; @@ -46,6 +66,7 @@ struct Tm1621 { uint8_t pin_wr; uint8_t state; uint8_t device; + uint8_t display_rotate; uint8_t temp_sensors; uint8_t temp_sensors_rotate; bool celsius; @@ -56,6 +77,96 @@ struct Tm1621 { bool present; } Tm1621; +/*********************************************************************************************\ + * Driver Settings load and save using filesystem +\*********************************************************************************************/ + +const uint32_t XDRV_87_VERSION = 0x0100; // Latest driver version (See settings deltas below) + +typedef struct { + uint32_t crc32; // To detect file changes + uint16_t version; // To detect driver function changes + uint8_t rotate; + uint8_t spare; + uint8_t line[2][TM1621_MAX_VALUES]; + uint8_t unit[2][TM1621_MAX_VALUES]; +} tXdrv87Settings; +tXdrv87Settings Xdrv87Settings; + +uint32_t Xdrv87SettingsCrc32(void) { + // Use Tasmota CRC calculation function + return GetCfgCrc32((uint8_t*)&Xdrv87Settings +4, sizeof(tXdrv87Settings) -4); // Skip crc32 +} + +void Xdrv87SettingsDefault(void) { + // Init default values in case file is not found + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 " D_USE_DEFAULTS)); + + memset(&Xdrv87Settings, 0x00, sizeof(tXdrv87Settings)); + Xdrv87Settings.version = XDRV_87_VERSION; + // Init any other parameter in struct Xdrv87Settings + Xdrv87Settings.rotate = TM1621_ROTATE; +} + +void Xdrv87SettingsDelta(void) { + // Fix possible setting deltas + if (Xdrv87Settings.version != SSPM_VERSION) { // Fix version dependent changes + + // Set current version and save settings + Xdrv87Settings.version = SSPM_VERSION; + Xdrv87SettingsSave(); + } +} + +void Xdrv87SettingsLoad(void) { + // Init default values in case file is not found + Xdrv87SettingsDefault(); + + // Try to load file /.drvset087 + char filename[20]; + // Use for drivers: + snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_87); + +#ifdef USE_UFILESYS + if (TfsLoadFile(filename, (uint8_t*)&Xdrv87Settings, sizeof(tXdrv87Settings))) { + // Fix possible setting deltas + Xdrv87SettingsDelta(); + + AddLog(LOG_LEVEL_INFO, PSTR("CFG: XDRV87 loaded from file")); + } else { + // File system not ready: No flash space reserved for file system + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 ERROR File system not ready or file not found")); + } +#else + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 ERROR File system not enabled")); +#endif // USE_UFILESYS +} + +void Xdrv87SettingsSave(void) { + // Called from FUNC_SAVE_SETTINGS every SaveData second and at restart + if (Xdrv87SettingsCrc32() != Xdrv87Settings.crc32) { + // Try to save file /.drvset087 + Xdrv87Settings.crc32 = Xdrv87SettingsCrc32(); + + char filename[20]; + // Use for drivers: + snprintf_P(filename, sizeof(filename), PSTR(TASM_FILE_DRIVER), XDRV_87); + +#ifdef USE_UFILESYS + if (TfsSaveFile(filename, (const uint8_t*)&Xdrv87Settings, sizeof(tXdrv87Settings))) { + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 saved to file")); + } else { + // File system not ready: No flash space reserved for file system + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 ERROR File system not ready or unable to save file")); + } +#else + AddLog(LOG_LEVEL_DEBUG, PSTR("CFG: XDRV87 ERROR File system not enabled")); +#endif // USE_UFILESYS + } +} + +/*********************************************************************************************/ + void TM1621StopSequence(void) { digitalWrite(Tm1621.pin_cs, 1); // Stop command sequence delayMicroseconds(TM1621_PULSE_WIDTH / 2); @@ -194,7 +305,9 @@ void TM1621PreInit(void) { pinMode(Tm1621.pin_wr, OUTPUT); digitalWrite(Tm1621.pin_wr, 1); - Tm1621.state = 100; + Xdrv87SettingsLoad(); + + Tm1621.state = 200; AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1621")); } @@ -225,18 +338,48 @@ void TM1621Init(void) { TM1621SendRows(); } -float TM1621GetTemperatureValues(uint32_t index) { - char *start = ResponseData(); - int data_start = ResponseLength(); +float TM1621GetValues(uint32_t index, bool refresh) { + if (refresh) { + ResponseClear(); + XsnsCall(FUNC_JSON_APPEND); + XdrvCall(FUNC_JSON_APPEND); + } + if (!ResponseLength()) { return NAN; } + char *start = ResponseData() +1; // Starts with ",..." so skip comma - XsnsCall(FUNC_JSON_APPEND); - XdrvCall(FUNC_JSON_APPEND); - - if (data_start == ResponseLength()) { return NAN; } + if (refresh) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TM1: Sensors '%s'"), start); + } float value = NAN; uint32_t idx = 0; - char *data = start; // Invalid JSON ,"HTU21":{"Temperature":30.7,"Humidity":39.0,"DewPoint":15.2},"BME680":{"Temperature":30.0,"Humidity":50.4,"DewPoint":18.5,"Pressure":1009.6,"Gas":1660.52},"ESP32":{"Temperature":53.3} + char *data = start; // "HTU21":{"Temperature":30.7,"Humidity":39.0,"DewPoint":15.2},"BME680":{"Temperature":30.0,"Humidity":50.4,"DewPoint":18.5,"Pressure":1009.6,"Gas":1660.52},"ESP32":{"Temperature":53.3} + while (data) { + data = strstr_P(data, PSTR("\":")); + if (data) { + idx++; + data += 2; + if (idx == index) { + value = CharToFloat(data); + break; + } + } + } + return value; +} + +float TM1621GetTemperatureValues(uint32_t index) { + ResponseClear(); + XsnsCall(FUNC_JSON_APPEND); + XdrvCall(FUNC_JSON_APPEND); + if (!ResponseLength()) { return NAN; } + + char *start = ResponseData() +1; // Starts with ",..." so skip comma + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("TM1: Sensors '%s'"), start); + + float value = NAN; + uint32_t idx = 0; + char *data = start; // "HTU21":{"Temperature":30.7,"Humidity":39.0,"DewPoint":15.2},"BME680":{"Temperature":30.0,"Humidity":50.4,"DewPoint":18.5,"Pressure":1009.6,"Gas":1660.52},"ESP32":{"Temperature":53.3} while (data) { data = strstr_P(data, PSTR(D_JSON_TEMPERATURE)); if (data) { @@ -257,30 +400,64 @@ float TM1621GetTemperatureValues(uint32_t index) { } void TM1621Show(void) { + Tm1621.celsius = false; + Tm1621.fahrenheit = false; + Tm1621.humidity = false; + Tm1621.voltage = false; + Tm1621.kwh = false; + snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR(" ")); + snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR(" ")); + + if ((Xdrv87Settings.line[0][0] > 0) || (Xdrv87Settings.line[1][0] > 0)) { + float value = TM1621GetValues(Xdrv87Settings.line[0][Tm1621.display_rotate], 1); + if (!isnan(value)) { + ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &value); + if (TM1621_TEMPERATURE == Xdrv87Settings.unit[0][Tm1621.display_rotate]) { + if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit + Tm1621.fahrenheit = true; + } else { + Tm1621.celsius = true; + } + } + Tm1621.voltage = (TM1621_VOLTAGE_CURRENT == Xdrv87Settings.unit[0][Tm1621.display_rotate]); + Tm1621.kwh = (4 == Xdrv87Settings.unit[0][Tm1621.display_rotate]); + } + value = TM1621GetValues(Xdrv87Settings.line[1][Tm1621.display_rotate], 0); + if (!isnan(value)) { + ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &value); + Tm1621.humidity = (TM1621_HUMIDITY == Xdrv87Settings.unit[1][Tm1621.display_rotate]); + Tm1621.voltage = (TM1621_VOLTAGE_CURRENT == Xdrv87Settings.unit[1][Tm1621.display_rotate]); + Tm1621.kwh = (TM1621_ENERGY_POWER == Xdrv87Settings.unit[1][Tm1621.display_rotate]); + } + uint32_t max = 0; + while ((max < TM1621_MAX_VALUES) && ((Xdrv87Settings.line[0][max] > 0) || (Xdrv87Settings.line[1][max] > 0))) { max++; } + Tm1621.display_rotate++; + if (Tm1621.display_rotate >= max) { + Tm1621.display_rotate = 0; + } + TM1621SendRows(); + return; + } + if (TM1621_POWR316D == Tm1621.device) { - static uint32_t display = 0; - if (0 == display) { - Tm1621.kwh = false; + if (0 == Tm1621.display_rotate) { +// Tm1621.kwh = false; ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.voltage[0]); ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.current[0]); Tm1621.voltage = true; - display = 1; + Tm1621.display_rotate = 1; } else { - Tm1621.voltage = false; +// Tm1621.voltage = false; ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.total[0]); ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.active_power[0]); Tm1621.kwh = true; - display = 0; + Tm1621.display_rotate = 0; } TM1621SendRows(); + return; } if (TM1621_THR316D == Tm1621.device) { - Tm1621.celsius = false; - Tm1621.fahrenheit = false; - Tm1621.humidity = false; - snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR(" ")); - snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR(" ")); if (!isnan(TasmotaGlobal.temperature_celsius)) { float temperature = ConvertTempToFahrenheit(TasmotaGlobal.temperature_celsius); if (TasmotaGlobal.humidity == 0.0f) { // No humidity so check for more temperature sensors @@ -312,22 +489,77 @@ void TM1621Show(void) { Tm1621.humidity = true; ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &TasmotaGlobal.humidity); } - TM1621SendRows(); } + TM1621SendRows(); } void TM1621EverySecond(void) { Tm1621.state++; - if (5 == Tm1621.state) { - TM1621Show(); - Tm1621.state = 0; + if (Tm1621.state > 127) { + if (202 == Tm1621.state) { + TM1621Init(); + Tm1621.state = 0; + } + } else { + if (Tm1621.state >= Xdrv87Settings.rotate) { + TM1621Show(); + Tm1621.state = 0; + } } - if (102 == Tm1621.state) { - TM1621Init(); - Tm1621.state = 0; +} + +/*********************************************************************************************\ + * Command +\*********************************************************************************************/ + +const char kTm1621Commands[] PROGMEM = "Dsp|" // No prefix + "Line|Speed"; +void (*const kTm1621Command[])(void) PROGMEM = { + &CmndDspLine, &CmndDspSpeed }; + +void CmndDspLine(void) { + // DspLine1 ,,,,... = Rotate between indexes + // DspLine1 0 and DspLine2 0 = Default of temperature/humidity + // Teleperiod: "SCD30":{"CarbonDioxide":746,"eCO2":727,"Temperature":30.6,"Humidity":43.6,"DewPoint":16.8} + // DspLine1 4,1,3,0 = Temperature and eCO2 + // DspLine2 2,0,5,2 = CarbonDioxide and humidity + // Unit 0 = None + // 1 = temperature (Line 1 only) + // 2 = %RH (Line 2 only) + // 3 = Both V (Line 1 only) / A (Line 2 only) + // 4 = Both kWh (Line 1 only) / W (Line 2 only) + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { + if (XdrvMailbox.data_len > 0) { + uint32_t parm[2 * TM1621_MAX_VALUES] = { 0 }; + ParseParameters(2 * TM1621_MAX_VALUES, parm); + for (uint32_t i = 0; i < TM1621_MAX_VALUES; i++) { + uint32_t j = i << 1; + Xdrv87Settings.line[XdrvMailbox.index -1][i] = parm[j]; + Xdrv87Settings.unit[XdrvMailbox.index -1][i] = parm[j +1]; + } + } + char values[8 * TM1621_MAX_VALUES]; + values[0] = '\0'; + uint32_t i = 0; + do { + snprintf_P(values, sizeof(values), PSTR("%s%s%d,%d"), + values, (i > 0) ? "," : "", Xdrv87Settings.line[XdrvMailbox.index -1][i], Xdrv87Settings.unit[XdrvMailbox.index -1][i]); + i++; + } while ((i < TM1621_MAX_VALUES) && (Xdrv87Settings.line[XdrvMailbox.index -1][i] > 0)); + ResponseCmndIdxChar(values); } } +void CmndDspSpeed(void) { + // DspSpeed 2..127 = Rotation speed + if (XdrvMailbox.data_len > 0) { + if (XdrvMailbox.payload > 1) { // We need at least 2 seconds to poll all sensors + Xdrv87Settings.rotate = XdrvMailbox.payload &0x7F; // Max 127 seconds + } + } + ResponseCmndNumber(Xdrv87Settings.rotate); +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -343,9 +575,16 @@ bool Xdrv87(uint8_t function) { case FUNC_EVERY_SECOND: TM1621EverySecond(); break; + case FUNC_SAVE_SETTINGS: + Xdrv87SettingsSave(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kTm1621Commands, kTm1621Command); + break; } } return result; } #endif // USE_DISPLAY_TM1621_SONOFF +#endif // ESP32