From 760d702fd093c33d054112c6c10f4f64f433f7f5 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 2 Oct 2020 15:10:21 +0200 Subject: [PATCH] Add optional support for Mitsubishi Electric HVAC Add optional support for Mitsubishi Electric HVAC by David Gwynne (#9237) --- BUILDS.md | 1 + tasmota/CHANGELOG.md | 1 + tasmota/language/bg_BG.h | 2 + tasmota/language/cs_CZ.h | 2 + tasmota/language/de_DE.h | 2 + tasmota/language/el_GR.h | 2 + tasmota/language/en_GB.h | 2 + tasmota/language/es_ES.h | 2 + tasmota/language/fr_FR.h | 4 +- tasmota/language/he_HE.h | 2 + tasmota/language/hu_HU.h | 2 + tasmota/language/it_IT.h | 2 + tasmota/language/ko_KO.h | 2 + tasmota/language/nl_NL.h | 2 + tasmota/language/pl_PL.h | 2 + tasmota/language/pt_BR.h | 2 + tasmota/language/pt_PT.h | 2 + tasmota/language/ro_RO.h | 2 + tasmota/language/ru_RU.h | 2 + tasmota/language/sk_SK.h | 2 + tasmota/language/sv_SE.h | 2 + tasmota/language/tr_TR.h | 2 + tasmota/language/uk_UA.h | 2 + tasmota/language/zh_CN.h | 2 + tasmota/language/zh_TW.h | 2 + tasmota/my_user_config.h | 1 + tasmota/support_features.ino | 4 +- tasmota/tasmota_configurations.h | 5 + tasmota/tasmota_template.h | 9 +- tasmota/xdrv_44_miel_hvac.ino | 1295 ++++++++++++++++++++++++++++++ tools/decode-status.py | 4 +- 31 files changed, 1363 insertions(+), 5 deletions(-) create mode 100644 tasmota/xdrv_44_miel_hvac.ino diff --git a/BUILDS.md b/BUILDS.md index 023c8ebec..3740f5708 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -149,6 +149,7 @@ | USE_HRXL | - | - | - | - | x | - | - | | USE_TASMOTA_CLIENT | - | - | - | - | - | - | - | | USE_OPENTHERM | - | - | - | - | - | - | - | +| USE_MIEL_HVAC | - | - | - | - | - | - | - | | USE_TCP_BRIDGE | - | - | - | - | - | - | - | zbbridge | | | | | | | | | | USE_NRF24 | - | - | - | - | - | - | - | diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 9314f6188..b6eaf46dd 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,6 +7,7 @@ - Remove auto config update for all Friendlynames and Switchtopic from versions before 8.x - Change redesigning ESP8266 GPIO internal representation in line with ESP32 - Change new IR Raw compact format (#9444) +- Add optional support for Mitsubishi Electric HVAC by David Gwynne (#9237) ### 8.5.1 20201002 diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index e0543019c..3d607e4bf 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index c7190998c..659632efd 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index 272f67988..2f01b0e59 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index 74a7b625f..c2429d55a 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 18d655ce3..54e4a6850 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 674b38eab..8adc6e74e 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index be1fd4f93..f1e9dc0f2 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -714,7 +714,9 @@ #define D_SENSOR_TCP_TXD "TCP Tx" #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" -#define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_IEM3000_RX "iEM3000 RX"#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" + // Units #define D_UNIT_AMPERE "A" #define D_UNIT_CELSIUS "C" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 0de84e609..75f95fda4 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index 23a7be121..1adb960cb 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 70360f15c..3e94ce149 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP - RX" #define D_SENSOR_IEM3000_TX "iEM3000 - TX" #define D_SENSOR_IEM3000_RX "iEM3000 - RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index d08aea216..7aadf0b78 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index 6a40607c9..5b190884a 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index 7e9d25e5c..18eedb5dc 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index 88f1d0c86..e6fe1a938 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index 83bbbc778..1106827ee 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 00ab45bcf..ed9848857 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 13068198f..1796d1b30 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 911581ee7..240e86003 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index 73ecd4bab..72030d9ce 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index e2b496d1e..ddfe84e43 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index 388aecc9a..5c583da06 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index 220f866e9..b3f1de4e9 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "安" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index aca69ad6b..5ee1cd862 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -719,6 +719,8 @@ #define D_SENSOR_TCP_RXD "TCP Rx" #define D_SENSOR_IEM3000_TX "iEM3000 TX" #define D_SENSOR_IEM3000_RX "iEM3000 RX" +#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx" +#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx" // Units #define D_UNIT_AMPERE "安培" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index eed9062f0..6c2f002a7 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -639,6 +639,7 @@ #define USE_TASMOTA_CLIENT_FLASH_SPEED 57600 // Usually 57600 for 3.3V variants and 115200 for 5V variants #define USE_TASMOTA_CLIENT_SERIAL_SPEED 57600 // Depends on the sketch that is running on the Uno/Pro Mini //#define USE_OPENTHERM // Add support for OpenTherm (+15k code) +//#define USE_MIEL_HVAC // Add support for Mitsubishi Electric HVAC serial interface (+5k code) // -- Power monitoring sensors -------------------- #define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index dc49a757f..a4cc5aab7 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -607,7 +607,9 @@ void GetFeatures(void) #if defined(USE_I2C) && defined(USE_VL53L1X) feature6 |= 0x02000000; // xsns_77_vl53l1x.ino #endif -// feature6 |= 0x04000000; +#ifdef USE_MIEL_HVAC + feature6 |= 0x04000000; // xdrv_44_miel_hvac.ino +#endif // feature6 |= 0x08000000; // feature6 |= 0x10000000; #if defined(ESP32) && defined(USE_TTGO_WATCH) diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index f94844bf9..6a08a9dc6 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -158,6 +158,7 @@ #define USE_HRXL // Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) //#define USE_TASMOTA_CLIENT // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) //#define USE_OPENTHERM // Add support for OpenTherm (+15k code) +//#define USE_MIEL_HVAC // Add support for Mitsubishi Electric HVAC serial interface (+5k code) //#define USE_MCP9808 // Add support for MCP9808 temperature sensor (+0k9 code) //#define USE_HP303B // Add support for HP303B temperature and pressure sensor (I2C address 0x76 or 0x77) (+6k2 code) @@ -392,6 +393,7 @@ #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_TASMOTA_CLIENT // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) +#undef USE_MIEL_HVAC // Disable support for Mitsubishi Electric HVAC serial interface (+5k code) #undef USE_DHT // Disable support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor #undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI @@ -504,6 +506,7 @@ #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_TASMOTA_CLIENT // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) +#undef USE_MIEL_HVAC // Disable support for Mitsubishi Electric HVAC serial interface (+5k code) #undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_ADE7953 // Disable ADE7953 Energy monitor as used on Shelly 2.5 (I2C address 0x38) (+1k5) @@ -638,6 +641,7 @@ #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_TASMOTA_CLIENT // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) +#undef USE_MIEL_HVAC // Disable support for Mitsubishi Electric HVAC serial interface (+5k code) //#undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor @@ -771,6 +775,7 @@ #undef USE_HRXL // Disable support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) #undef USE_TASMOTA_CLIENT // Disable support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) #undef USE_OPENTHERM // Disable support for OpenTherm (+15k code) +#undef USE_MIEL_HVAC // Disable support for Mitsubishi Electric HVAC serial interface (+5k code) #undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index 53841b333..6fbc3e4d7 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -146,6 +146,8 @@ enum UserSelectablePins { GPIO_IEM3000_TX, GPIO_IEM3000_RX, // IEM3000 Serial interface GPIO_ZIGBEE_RST, // Zigbee reset GPIO_DYP_RX, + GPIO_MIEL_HVAC_TX, // Mitsubishi Electric HVAC TX pin + GPIO_MIEL_HVAC_RX, // Mitsubishi Electric HVAC RX pin GPIO_SENSOR_END }; enum ProgramSelectablePins { @@ -248,7 +250,8 @@ const char kSensorNames[] PROGMEM = D_SENSOR_LMT01_PULSE "|" D_SENSOR_IEM3000_TX "|" D_SENSOR_IEM3000_RX "|" D_SENSOR_ZIGBEE_RST "|" - D_SENSOR_DYP_RX + D_SENSOR_DYP_RX "|" + D_SENSOR_MIEL_HVAC_TX "|" D_SENSOR_MIEL_HVAC_RX ; const char kSensorNamesFixed[] PROGMEM = @@ -575,6 +578,10 @@ const uint16_t kGpioNiceList[] PROGMEM = { AGPIO(GPIO_TELEINFO_RX), AGPIO(GPIO_TELEINFO_ENABLE), #endif +#ifdef USE_MIEL_HVAC + AGPIO(GPIO_MIEL_HVAC_TX), // Mitsubishi Electric HVAC TX pin + AGPIO(GPIO_MIEL_HVAC_RX), // Mitsubishi Electric HVAC RX pin +#endif #ifdef ESP32 #ifdef USE_WEBCAM diff --git a/tasmota/xdrv_44_miel_hvac.ino b/tasmota/xdrv_44_miel_hvac.ino new file mode 100644 index 000000000..cd08d1c54 --- /dev/null +++ b/tasmota/xdrv_44_miel_hvac.ino @@ -0,0 +1,1295 @@ +/* + xdrv_44_miel_hvac.ino - Mitsubishi Electric HVAC support for Tasmota + + Copyright (C) 2020 David Gwynne + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_MIEL_HVAC +/*********************************************************************************************\ + * Mitsubishi Electric HVAC serial interface +\*********************************************************************************************/ + +#define XDRV_44 44 + +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) + +#define CTASSERT(x) extern char _ctassert[(x) ? 1 : -1 ] \ + __attribute__((__unused__)) + +#define MIEL_HVAC_LOGNAME "MiElHVAC" + +#define D_CMND_MIEL_HVAC_SETFANSPEED "HVACSetFanSpeed" +#define D_CMND_MIEL_HVAC_SETMODE "HVACSetMode" +#define D_CMND_MIEL_HVAC_SETHAMODE "HVACSetHAMode" +#define D_CMND_MIEL_HVAC_SETTEMP "HVACSetTemp" +#define D_CMND_MIEL_HVAC_SETSWINGV "HVACSetSwingV" +#define D_CMND_MIEL_HVAC_SETSWINGH "HVACSetSwingH" +#define D_CMND_MIEL_HVAC_REMOTETEMP "HVACRemoteTemp" + +#include + +/* from hvac */ +struct miel_hvac_header { + uint8_t start; +#define MIEL_HVAC_H_START 0xfc + uint8_t type; +#define MIEL_HVAC_H_TYPE_UPDATED 0x61 +#define MIEL_HVAC_H_TYPE_DATA 0x62 +#define MIEL_HVAC_H_TYPE_CONNECTED 0x7a + uint8_t middle1; +#define MIEL_HVAC_H_MIDDLE1 0x01 + uint8_t middle2; +#define MIEL_HVAC_H_MIDDLE2 0x30 + uint8_t len; +}; + +struct miel_hvac_data_settings { + uint8_t _pad1[2]; + uint8_t power; + uint8_t mode; +#define MIEL_HVAC_SETTINGS_MODE_MASK 0x7f + uint8_t temp; + uint8_t fan; + uint8_t vane; + uint8_t _pad2[2]; + uint8_t widevane; +#define MIEL_HVAC_SETTTINGS_WIDEVANE_MASK \ + 0x0f +}; + +struct miel_hvac_data_roomtemp { + uint8_t _pad1[2]; + uint8_t temp; +}; + +struct miel_hvac_data_status { + uint8_t _pad1[2]; + uint8_t compressor; + uint8_t operation; +}; + +struct miel_hvac_data { + uint8_t type; +#define MIEL_HVAC_DATA_T_SETTINGS 0x02 +#define MIEL_HVAC_DATA_T_ROOMTEMP 0x03 +#define MIEL_HVAC_DATA_T_TIMER 0x05 +#define MIEL_HVAC_DATA_T_STATUS 0x06 +#define MIEL_HVAC_DATA_T_STAGE 0x09 + + union { + struct miel_hvac_data_settings + settings; + struct miel_hvac_data_roomtemp + roomtemp; + struct miel_hvac_data_status + status; + + uint8_t bytes[15]; + } data; +}; + +CTASSERT(sizeof(struct miel_hvac_data) == 16); + +CTASSERT(offsetof(struct miel_hvac_data, data.settings.power) == 3); +CTASSERT(offsetof(struct miel_hvac_data, data.settings.mode) == 4); +CTASSERT(offsetof(struct miel_hvac_data, data.settings.temp) == 5); +CTASSERT(offsetof(struct miel_hvac_data, data.settings.fan) == 6); +CTASSERT(offsetof(struct miel_hvac_data, data.settings.vane) == 7); +CTASSERT(offsetof(struct miel_hvac_data, data.settings.widevane) == 10); + +CTASSERT(offsetof(struct miel_hvac_data, data.roomtemp.temp) == 3); + +/* to hvac */ + +#define MIEL_HVAC_H_TYPE_CONNECT 0x5a +static const uint8_t miel_hvac_msg_connect[] = { 0xca, 0x01 }; + +#define MIEL_HVAC_H_TYPE_REQUEST 0x42 + +struct miel_hvac_msg_request { + uint8_t type; +#define MIEL_HVAC_REQUEST_SETTINGS 0x02 +#define MIEL_HVAC_REQUEST_ROOMTEMP 0x03 +#define MIEL_HVAC_REQUEST_TIMERS 0x05 +#define MIEL_HVAC_REQUEST_STATUS 0x06 +#define MIEL_HVAC_REQUEST_STAGE 0x09 + uint8_t zero[15]; +}; + +#define MIEL_HVAC_H_TYPE_UPDATE 0x41 + +struct miel_hvac_msg_update { + uint8_t one; + uint16_t flags; +#define MIEL_HVAC_UPDATE_F_WIDEVANE (1 << 0) +#define MIEL_HVAC_UPDATE_F_POWER (1 << 8) +#define MIEL_HVAC_UPDATE_F_MODE (1 << 9) +#define MIEL_HVAC_UPDATE_F_TEMP (1 << 10) +#define MIEL_HVAC_UPDATE_F_FAN (1 << 11) +#define MIEL_HVAC_UPDATE_F_VANE (1 << 12) + uint8_t power; +#define MIEL_HVAC_UPDATE_POWER_OFF 0x00 +#define MIEL_HVAC_UPDATE_POWER_ON 0x01 + uint8_t mode; +#define MIEL_HVAC_UPDATE_MODE_HEAT 0x01 +#define MIEL_HVAC_UPDATE_MODE_DRY 0x02 +#define MIEL_HVAC_UPDATE_MODE_COOL 0x03 +#define MIEL_HVAC_UPDATE_MODE_FAN 0x07 +#define MIEL_HVAC_UPDATE_MODE_AUTO 0x08 + uint8_t temp; +#define MIEL_HVAC_UPDATE_TEMP_MIN 16 +#define MIEL_HVAC_UPDATE_TEMP_MAX 31 + uint8_t fan; +#define MIEL_HVAC_UPDATE_FAN_AUTO 0x00 +#define MIEL_HVAC_UPDATE_FAN_QUIET 0x01 +#define MIEL_HVAC_UPDATE_FAN_1 0x02 +#define MIEL_HVAC_UPDATE_FAN_2 0x03 +#define MIEL_HVAC_UPDATE_FAN_3 0x05 +#define MIEL_HVAC_UPDATE_FAN_4 0x06 + uint8_t vane; +#define MIEL_HVAC_UPDATE_VANE_AUTO 0x00 +#define MIEL_HVAC_UPDATE_VANE_1 0x01 +#define MIEL_HVAC_UPDATE_VANE_2 0x02 +#define MIEL_HVAC_UPDATE_VANE_3 0x03 +#define MIEL_HVAC_UPDATE_VANE_4 0x04 +#define MIEL_HVAC_UPDATE_VANE_5 0x05 +#define MIEL_HVAC_UPDATE_VANE_SWING 0x07 + uint8_t _pad1[5]; + uint8_t widevane; +#define MIEL_HVAC_UPDATE_WIDEVANE_MASK 0x0f +#define MIEL_HVAC_UPDATE_WIDEVANE_LL 0x01 +#define MIEL_HVAC_UPDATE_WIDEVANE_L 0x02 +#define MIEL_HVAC_UPDATE_WIDEVANE_LL 0x01 +#define MIEL_HVAC_UPDATE_WIDEVANE_L 0x02 +#define MIEL_HVAC_UPDATE_WIDEVANE_C 0x03 +#define MIEL_HVAC_UPDATE_WIDEVANE_R 0x04 +#define MIEL_HVAC_UPDATE_WIDEVANE_RR 0x05 +#define MIEL_HVAC_UPDATE_WIDEVANE_LR 0x08 +#define MIEL_HVAC_UPDATE_WIDEVANE_SWING 0x0c +#define MIEL_HVAC_UPDATE_WIDEVANE_ADJ 0x80 + uint8_t _pad2[2]; +} __packed; + +CTASSERT(sizeof(struct miel_hvac_msg_update) == 16); +#define MIEL_HVAC_OFFS(_v) ((_v) - sizeof(struct miel_hvac_header)) +CTASSERT(offsetof(struct miel_hvac_msg_update, flags) == MIEL_HVAC_OFFS(6)); +CTASSERT(offsetof(struct miel_hvac_msg_update, power) == MIEL_HVAC_OFFS(8)); +CTASSERT(offsetof(struct miel_hvac_msg_update, mode) == MIEL_HVAC_OFFS(9)); +CTASSERT(offsetof(struct miel_hvac_msg_update, temp) == MIEL_HVAC_OFFS(10)); +CTASSERT(offsetof(struct miel_hvac_msg_update, fan) == MIEL_HVAC_OFFS(11)); +CTASSERT(offsetof(struct miel_hvac_msg_update, vane) == MIEL_HVAC_OFFS(12)); +CTASSERT(offsetof(struct miel_hvac_msg_update, widevane) == MIEL_HVAC_OFFS(18)); + +static inline uint8_t +miel_hvac_deg2temp(uint8_t deg) +{ + return (31 - deg); +} + +static inline uint8_t +miel_hvac_temp2deg(uint8_t temp) +{ + return (31 - temp); +} + +static inline unsigned int +miel_hvac_roomtemp2deg(uint8_t roomtemp) +{ + return ((unsigned int)roomtemp + 10); +} + +struct miel_hvac_msg_remotetemp { + uint8_t seven; + uint8_t control; +#define MIEL_HVAC_REMOTETEMP_CLR 0x00 +#define MIEL_HVAC_REMOTETEMP_SET 0x01 + /* setting for older units expressed as .5C units starting at 8C */ + uint8_t temp_old; +#define MIEL_HVAC_REMOTETEMP_OLD_MIN 8 +#define MIEL_HVAC_REMOTETEMP_OLD_MAX 38 +#define MIEL_HVAC_REMOTETEMP_OLD_FACTOR 2 + /* setting for newer units expressed as .5C units starting at -63C */ + uint8_t temp; +#define MIEL_HVAC_REMOTETEMP_MIN -63 +#define MIEL_HVAC_REMOTETEMP_MAX 63 +#define MIEL_HVAC_REMOTETEMP_OFFSET 64 +#define MIEL_HVAC_REMOTETEMP_FACTOR 2 + uint8_t _pad2[12]; +}; + +CTASSERT(sizeof(struct miel_hvac_msg_remotetemp) == 16); + +static inline uint8_t +miel_hvac_cksum_fini(uint8_t sum) +{ + return (0xfc - sum); +} + +struct miel_hvac_map { + uint8_t byte; + const char *name; +}; + +static const struct miel_hvac_map miel_hvac_mode_map[] = { + { MIEL_HVAC_UPDATE_MODE_HEAT, "heat" }, + { MIEL_HVAC_UPDATE_MODE_DRY, "dry" }, + { MIEL_HVAC_UPDATE_MODE_COOL, "cool" }, + { MIEL_HVAC_UPDATE_MODE_FAN, "fan_only" }, + { MIEL_HVAC_UPDATE_MODE_AUTO, "auto" }, +}; + +static const struct miel_hvac_map miel_hvac_fan_map[] = { + { MIEL_HVAC_UPDATE_FAN_AUTO, "auto" }, + { MIEL_HVAC_UPDATE_FAN_QUIET, "quiet" }, + { MIEL_HVAC_UPDATE_FAN_1, "1" }, + { MIEL_HVAC_UPDATE_FAN_2, "2" }, + { MIEL_HVAC_UPDATE_FAN_3, "3" }, + { MIEL_HVAC_UPDATE_FAN_4, "4" }, +}; + +static const struct miel_hvac_map miel_hvac_vane_map[] = { + { MIEL_HVAC_UPDATE_VANE_AUTO, "auto" }, + { MIEL_HVAC_UPDATE_VANE_1, "1" }, + { MIEL_HVAC_UPDATE_VANE_2, "2" }, + { MIEL_HVAC_UPDATE_VANE_3, "3" }, + { MIEL_HVAC_UPDATE_VANE_4, "4" }, + { MIEL_HVAC_UPDATE_VANE_5, "5" }, + { MIEL_HVAC_UPDATE_VANE_SWING, "swing" }, +}; + +static const struct miel_hvac_map miel_hvac_widevane_map[] = { + { MIEL_HVAC_UPDATE_WIDEVANE_LL, "LL" }, + { MIEL_HVAC_UPDATE_WIDEVANE_L, "L" }, + { MIEL_HVAC_UPDATE_WIDEVANE_C, "C" }, + { MIEL_HVAC_UPDATE_WIDEVANE_R, "R" }, + { MIEL_HVAC_UPDATE_WIDEVANE_RR, "RR" }, + { MIEL_HVAC_UPDATE_WIDEVANE_LR, "split" }, + { MIEL_HVAC_UPDATE_WIDEVANE_SWING, "swing" }, +}; + +enum miel_hvac_parser_state { + MIEL_HVAC_P_START, + MIEL_HVAC_P_TYPE, + MIEL_HVAC_P_MIDDLE1, + MIEL_HVAC_P_MIDDLE2, + MIEL_HVAC_P_LEN, + MIEL_HVAC_P_DATA, + MIEL_HVAC_P_CKSUM, + + MIEL_HVAC_P_SKIP, + MIEL_HVAC_P_SKIP_CKSUM, +}; + +#define MIEL_HVAC_DATABUFLEN 64 + +struct miel_hvac_parser { + enum miel_hvac_parser_state + p_state; + uint8_t p_type; + uint8_t p_sum; + uint8_t p_len; + uint8_t p_off; + uint8_t p_data[MIEL_HVAC_DATABUFLEN]; +}; + +struct miel_hvac_softc { + TasmotaSerial *sc_serial; + struct miel_hvac_parser sc_parser; + + unsigned int sc_device; + unsigned int sc_tick; + bool sc_settings_set; + bool sc_connected; + + struct miel_hvac_data sc_settings; + struct miel_hvac_data sc_temp; + struct miel_hvac_data sc_status; + struct miel_hvac_data sc_stage; + + struct miel_hvac_msg_update + sc_update; + struct miel_hvac_msg_remotetemp + sc_remotetemp; +}; + +static inline bool +miel_hvac_update_pending(struct miel_hvac_softc *sc) +{ + struct miel_hvac_msg_update *update = &sc->sc_update; + + return (update->flags != htons(0)); +} + +static struct miel_hvac_softc *miel_hvac_sc = nullptr; + +static void miel_hvac_input_connected(struct miel_hvac_softc *, + const void *, size_t); +static void miel_hvac_input_data(struct miel_hvac_softc *, + const void *, size_t); +static void miel_hvac_input_updated(struct miel_hvac_softc *, + const void *, size_t); + +static enum miel_hvac_parser_state +miel_hvac_parse(struct miel_hvac_softc *sc, uint8_t byte) +{ + struct miel_hvac_parser *p = &sc->sc_parser; + enum miel_hvac_parser_state nstate = p->p_state; + + switch (p->p_state) { + case MIEL_HVAC_P_START: + if (byte != MIEL_HVAC_H_START) + return (MIEL_HVAC_P_START); + + /* reset state */ + p->p_sum = 0; + + nstate = MIEL_HVAC_P_TYPE; + break; + + case MIEL_HVAC_P_TYPE: + p->p_type = byte; + nstate = MIEL_HVAC_P_MIDDLE1; + break; + + case MIEL_HVAC_P_MIDDLE1: + if (byte != MIEL_HVAC_H_MIDDLE1) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": parse state MIDDLE1 expected %02x got %02x" + ", restarting"), MIEL_HVAC_H_MIDDLE1, byte); + return (MIEL_HVAC_P_START); + } + + nstate = MIEL_HVAC_P_MIDDLE2; + break; + + case MIEL_HVAC_P_MIDDLE2: + if (byte != MIEL_HVAC_H_MIDDLE2) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": parse state MIDDLE2 expected %02x got %02x" + ", restarting"), MIEL_HVAC_H_MIDDLE2, byte); + return (MIEL_HVAC_P_START); + } + + nstate = MIEL_HVAC_P_LEN; + break; + + case MIEL_HVAC_P_LEN: + if (byte == 0) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": skipping 0 byte message type 0x%02x"), + p->p_type); + return (MIEL_HVAC_P_SKIP_CKSUM); + } + + p->p_len = byte; + p->p_off = 0; + + switch (p->p_type) { + case MIEL_HVAC_H_TYPE_CONNECTED: + case MIEL_HVAC_H_TYPE_DATA: + case MIEL_HVAC_H_TYPE_UPDATED: + break; + default: + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": skipping unknown message type 0x%02x"), + p->p_type); + return (MIEL_HVAC_P_SKIP); + } + + if (byte > sizeof(p->p_data)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": skipping %u data bytes of message type 0x%02x"), + p->p_len, p->p_type); + return (MIEL_HVAC_P_SKIP); + } + + nstate = MIEL_HVAC_P_DATA; + break; + + case MIEL_HVAC_P_DATA: + p->p_data[p->p_off++] = byte; + if (p->p_off >= p->p_len) + nstate = MIEL_HVAC_P_CKSUM; + break; + + case MIEL_HVAC_P_CKSUM: + if (miel_hvac_cksum_fini(p->p_sum) != byte) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": checksum failed, restarting")); + return (MIEL_HVAC_P_START); + } + + switch (p->p_type) { + case MIEL_HVAC_H_TYPE_CONNECTED: + miel_hvac_input_connected(sc, p->p_data, p->p_len); + break; + case MIEL_HVAC_H_TYPE_DATA: + miel_hvac_input_data(sc, p->p_data, p->p_len); + break; + case MIEL_HVAC_H_TYPE_UPDATED: + miel_hvac_input_updated(sc, p->p_data, p->p_len); + break; + } + + /* this message is done, wait for another */ + return (MIEL_HVAC_P_START); + + case MIEL_HVAC_P_SKIP: + if (++p->p_off >= p->p_len) + return (MIEL_HVAC_P_SKIP_CKSUM); + return (nstate); + case MIEL_HVAC_P_SKIP_CKSUM: + return (MIEL_HVAC_P_START); + } + + p->p_sum += byte; + + return (nstate); +} + +static uint8_t +miel_hvac_write(struct miel_hvac_softc *sc, const uint8_t *bytes, size_t len) +{ + TasmotaSerial *serial = sc->sc_serial; + uint8_t cksum = 0; + size_t i; + + for (i = 0; i < len; i++) { + uint8_t b = bytes[i]; + serial->write(b); + cksum += b; + } + + return (cksum); +} + +static void +miel_hvac_send(struct miel_hvac_softc *sc, uint8_t type, + const void *data, size_t len) +{ + TasmotaSerial *serial = sc->sc_serial; + struct miel_hvac_header h = { + MIEL_HVAC_H_START, + type, + MIEL_HVAC_H_MIDDLE1, + MIEL_HVAC_H_MIDDLE2, + (uint8_t)len, + }; + uint8_t cksum = 0; + + cksum += miel_hvac_write(sc, (const uint8_t *)&h, sizeof(h)); + cksum += miel_hvac_write(sc, (const uint8_t *)data, len); + + char hex_h[(sizeof(h) + 1) * 2]; + char hex_d[(len + 1) * 2]; + AddLog_P2(LOG_LEVEL_DEBUG, + PSTR(MIEL_HVAC_LOGNAME ": sending %s %s %02x"), + ToHex_P((uint8_t *)&h, sizeof(h), hex_h, sizeof(hex_h)), + ToHex_P((uint8_t *)data, len, hex_d, sizeof(hex_d)), + miel_hvac_cksum_fini(cksum)); + + serial->write(miel_hvac_cksum_fini(cksum)); + serial->flush(); +} + +#define miel_hvac_send_connect(_sc) \ + miel_hvac_send((_sc), MIEL_HVAC_H_TYPE_CONNECT, \ + miel_hvac_msg_connect, sizeof(miel_hvac_msg_connect)) + +static const struct miel_hvac_map * +miel_hvac_map_byname(const char *name, + const struct miel_hvac_map *m, size_t n) +{ + const struct miel_hvac_map *e; + size_t i; + + for (i = 0; i < n; i++) { + e = &m[i]; + if (strcasecmp(e->name, name) == 0) + return (e); + } + + return (NULL); +} + +static const char * +miel_hvac_map_byval(uint8_t byte, + const struct miel_hvac_map *m, size_t n) +{ + const struct miel_hvac_map *e; + size_t i; + + for (i = 0; i < n; i++) { + e = &m[i]; + if (byte == e->byte) + return (e->name); + } + + return (NULL); +} + +static void +miel_hvac_request(struct miel_hvac_softc *sc, uint8_t type) +{ + struct miel_hvac_msg_request request = { type }; + + miel_hvac_send(sc, MIEL_HVAC_H_TYPE_REQUEST, + &request, sizeof(request)); +} + +static void +miel_hvac_init_update(struct miel_hvac_msg_update *update) +{ + memset(update, 0, sizeof(*update)); + update->one = 1; +} + +static inline void +miel_hvac_send_update(struct miel_hvac_softc *sc, + const struct miel_hvac_msg_update *update) +{ + miel_hvac_send(sc, MIEL_HVAC_H_TYPE_UPDATE, update, sizeof(*update)); +} + +static inline void +miel_hvac_send_remotetemp(struct miel_hvac_softc *sc, + const struct miel_hvac_msg_remotetemp *remotetemp) +{ + miel_hvac_send(sc, MIEL_HVAC_H_TYPE_UPDATE, + remotetemp, sizeof(*remotetemp)); +} + +static bool +miel_hvac_set_power(struct miel_hvac_softc *sc) +{ + struct miel_hvac_msg_update *update = &sc->sc_update; + uint16_t source = XdrvMailbox.payload; + + if (source == SRC_SWITCH) + return (false); + + update->flags |= htons(MIEL_HVAC_UPDATE_F_POWER); + update->power = (XdrvMailbox.index & (1 << sc->sc_device)) ? + MIEL_HVAC_UPDATE_POWER_ON : MIEL_HVAC_UPDATE_POWER_OFF; + + return (true); +} + +static void +miel_hvac_respond_unsupported(void) +{ + ResponseCmndChar_P(PSTR("Unsupported")); +} + +static void +miel_hvac_cmnd_setfanspeed(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + e = miel_hvac_map_byname(XdrvMailbox.data, + miel_hvac_fan_map, nitems(miel_hvac_fan_map)); + if (e == NULL) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_FAN); + update->fan = e->byte; + + ResponseCmndChar_P(e->name); +} + +static void +miel_hvac_cmnd_setmode(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + e = miel_hvac_map_byname(XdrvMailbox.data, + miel_hvac_mode_map, nitems(miel_hvac_mode_map)); + if (e == NULL) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_MODE); + update->mode = e->byte; + + ResponseCmndChar_P(e->name); +} + +static void +miel_hvac_cmnd_sethamode(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + if (strcasecmp(XdrvMailbox.data, "off") == 0) { + update->flags |= htons(MIEL_HVAC_UPDATE_F_POWER); + update->power = MIEL_HVAC_UPDATE_POWER_OFF; + ResponseCmndChar_P(PSTR("off")); + return; + } + + /* + * I could set power and then call miel_hvac_cmnd_setmode, + * but that would mean power gets turned on even if there's + * an invalid argument. + */ + e = miel_hvac_map_byname(XdrvMailbox.data, + miel_hvac_mode_map, nitems(miel_hvac_mode_map)); + if (e == NULL) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_POWER) | + htons(MIEL_HVAC_UPDATE_F_MODE); + update->power = MIEL_HVAC_UPDATE_POWER_ON; + update->mode = e->byte; + + ResponseCmndChar_P(e->name); +} + +static void +miel_hvac_cmnd_settemp(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + unsigned long degc; + + if (XdrvMailbox.data_len == 0) + return; + + degc = strtoul(XdrvMailbox.data, nullptr, 0); + if (degc < MIEL_HVAC_UPDATE_TEMP_MIN || + degc > MIEL_HVAC_UPDATE_TEMP_MAX) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_TEMP); + update->temp = miel_hvac_deg2temp(degc); + + ResponseCmndNumber(degc); +} + +static void +miel_hvac_cmnd_setvane(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + e = miel_hvac_map_byname(XdrvMailbox.data, + miel_hvac_vane_map, nitems(miel_hvac_vane_map)); + if (e == NULL) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_VANE); + update->vane = e->byte; + + ResponseCmndChar_P(e->name); +} + +static void +miel_hvac_cmnd_setwidevane(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update *update = &sc->sc_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + e = miel_hvac_map_byname(XdrvMailbox.data, + miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); + if (e == NULL) { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_UPDATE_F_WIDEVANE); + update->widevane = e->byte; + + ResponseCmndChar_P(e->name); +} + +static inline uint8_t +miel_hvac_remotetemp_degc2old(long degc) +{ + /* + * If a remote temperature reading is provided that cannot be + * represented by the temp_old field, implicitly clamp it to the + * supported min or max. The hardware does this anyway if you + * provide a high value, but without this the min value will + * underflow and turn a high value that the hardware thinks is 38. + */ + + if (degc < MIEL_HVAC_REMOTETEMP_OLD_MIN) + degc = MIEL_HVAC_REMOTETEMP_OLD_MIN; + else if (degc > MIEL_HVAC_REMOTETEMP_OLD_MAX) + degc = MIEL_HVAC_REMOTETEMP_OLD_MIN; + + return ((degc - MIEL_HVAC_REMOTETEMP_OLD_MIN) * + MIEL_HVAC_REMOTETEMP_OLD_FACTOR); +} + +static void +miel_hvac_cmnd_remotetemp(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_remotetemp *rt = &sc->sc_remotetemp; + uint8_t control = MIEL_HVAC_REMOTETEMP_SET; + long degc; + + if (XdrvMailbox.data_len == 0) + return; + + if (strcasecmp(XdrvMailbox.data, "clear") == 0) { + control = MIEL_HVAC_REMOTETEMP_CLR; + degc = 0; + + ResponseCmndChar_P("clear"); + } else { + degc = strtol(XdrvMailbox.data, nullptr, 0); + + /* clamp the argument to supported values */ + if (degc < MIEL_HVAC_REMOTETEMP_MIN) + degc = MIEL_HVAC_REMOTETEMP_MIN; + else if (degc > MIEL_HVAC_REMOTETEMP_MAX) + degc = MIEL_HVAC_REMOTETEMP_MAX; + + ResponseCmndNumber(degc); + } + + memset(rt, 0, sizeof(*rt)); + rt->seven = 0x7; + rt->control = control; + + /* + * Different HVACs (or more likely different generations + * of these HVACs) have different ways to encode the remote + * temperature value. This provides both of them to hopefully + * support all known types of HVACs. + */ + + rt->temp_old = miel_hvac_remotetemp_degc2old(degc); + rt->temp = (degc + MIEL_HVAC_REMOTETEMP_OFFSET) * + MIEL_HVAC_REMOTETEMP_OLD_FACTOR; +} + +#ifdef MIEL_HVAC_DEBUG +static void +miel_hvac_cmnd_request(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + uint8_t type = MIEL_HVAC_REQUEST_ROOMTEMP; + + if (XdrvMailbox.data_len > 0) + type = strtoul(XdrvMailbox.data, nullptr, 0); + + miel_hvac_request(sc, type); + + ResponseCmndDone(); +} +#endif + +/* + * serial data handlers + */ + +static void +miel_hvac_log_bytes(struct miel_hvac_softc *sc, const char *name, + const void *buf, size_t len) +{ + char hex[(MIEL_HVAC_DATABUFLEN + 1) * 2]; + const unsigned char *b = (const unsigned char *)buf; + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": response %s %s"), name, ToHex_P(b, len, hex, sizeof(hex))); +} + +static void +miel_hvac_input_connected(struct miel_hvac_softc *sc, + const void *buf, size_t len) +{ + AddLog_P2(LOG_LEVEL_INFO, + PSTR(MIEL_HVAC_LOGNAME ": connected to Mitsubishi Electric HVAC")); + sc->sc_connected = 1; +} + +static void +miel_hvac_publish_settings(struct miel_hvac_softc *sc) +{ + const struct miel_hvac_data_settings *set = + &sc->sc_settings.data.settings; + char hex[(sizeof(sc->sc_settings) + 1) * 2]; + char temp[33]; + const char *name; + + Response_P(PSTR("{\"" D_JSON_IRHVAC_POWER "\":\"%s\""), + set->power ? "ON" : "OFF"); + + name = miel_hvac_map_byval( set->mode & + MIEL_HVAC_SETTINGS_MODE_MASK, + miel_hvac_mode_map, nitems(miel_hvac_mode_map)); + if (name != NULL) { + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_MODE "\":\"%s\""), + name); + ResponseAppend_P(PSTR(",\"HA" D_JSON_IRHVAC_MODE "\":\"%s\""), + set->power ? name : "off"); + } + + dtostrfd(ConvertTemp(miel_hvac_temp2deg(set->temp)), + Settings.flag2.temperature_resolution, temp); + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_TEMP "\":%s"), temp); + + name = miel_hvac_map_byval(set->fan, + miel_hvac_fan_map, nitems(miel_hvac_fan_map)); + if (name != NULL) { + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_FANSPEED "\":\"%s\""), + name); + } + + name = miel_hvac_map_byval(set->vane, + miel_hvac_vane_map, nitems(miel_hvac_vane_map)); + if (name != NULL) { + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGV "\":\"%s\""), + name); + } + + name = miel_hvac_map_byval(set->widevane & + MIEL_HVAC_SETTTINGS_WIDEVANE_MASK, + miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); + if (name != NULL) { + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGH "\":\"%s\""), + name); + } + + ResponseAppend_P(PSTR(",\"Bytes\":\"%s\""), + ToHex_P((uint8_t *)&sc->sc_settings, sizeof(sc->sc_settings), + hex, sizeof(hex))); + + ResponseAppend_P(PSTR("}")); + + MqttPublishPrefixTopic_P(TELE, PSTR("HVACSettings")); + + XdrvRulesProcess(); +} + +static void +miel_hvac_input_settings(struct miel_hvac_softc *sc, + const struct miel_hvac_data *d) +{ + const struct miel_hvac_data_settings *set = &d->data.settings; + uint32_t state = set->power ? 1 : 0; /* see ExecuteCommandPower */ + bool publish; + + if (miel_hvac_update_pending(sc)) { + /* + * Don't flop around printing settings that we might be + * changing, but also force them to be published again. + */ + sc->sc_settings_set = 0; + return; + } + + if (bitRead(power, sc->sc_device) != !!state) + ExecuteCommandPower(sc->sc_device, state, SRC_SWITCH); + + publish = (sc->sc_settings_set == 0) || + (memcmp(d, &sc->sc_settings, sizeof(sc->sc_settings)) != 0); + sc->sc_settings_set = 1; + sc->sc_settings = *d; + + if (publish) + miel_hvac_publish_settings(sc); +} + +static void +miel_hvac_data_response(struct miel_hvac_softc *sc, + const struct miel_hvac_data *d) +{ + char hex[(sizeof(*d) + 1) * 2]; + + Response_P(PSTR("{\"Bytes\":\"%s\"}"), + ToHex_P((uint8_t *)d, sizeof(*d), hex, sizeof(hex))); + + MqttPublishPrefixTopic_P(TELE, PSTR("HVACData")); + XdrvRulesProcess(); +} + +static void +miel_hvac_input_sensor(struct miel_hvac_softc *sc, struct miel_hvac_data *dst, + const struct miel_hvac_data *src) +{ + bool publish; + + publish = (memcmp(dst, src, sizeof(*dst)) != 0); + *dst = *src; + + if (publish) + MqttPublishSensor(); + +} + +static void +miel_hvac_input_data(struct miel_hvac_softc *sc, + const void *buf, size_t len) +{ + const struct miel_hvac_data *d; + + miel_hvac_log_bytes(sc, "data", buf, len); + if (len < sizeof(*d)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(MIEL_HVAC_LOGNAME + ": short data response (%zu < %zu)"), len, sizeof(*d)); + return; + } + + d = (const struct miel_hvac_data *)buf; + + switch (d->type) { + case MIEL_HVAC_DATA_T_SETTINGS: + miel_hvac_input_settings(sc, d); + break; + case MIEL_HVAC_DATA_T_ROOMTEMP: + miel_hvac_input_sensor(sc, &sc->sc_temp, d); + break; + case MIEL_HVAC_DATA_T_STATUS: + miel_hvac_input_sensor(sc, &sc->sc_status, d); + break; + case MIEL_HVAC_DATA_T_STAGE: + miel_hvac_input_sensor(sc, &sc->sc_stage, d); + break; + default: + miel_hvac_data_response(sc, d); + break; + } +} + +static void +miel_hvac_input_updated(struct miel_hvac_softc *sc, + const void *buf, size_t len) +{ + miel_hvac_log_bytes(sc, "updated", buf, len); +} + +/* + * FUNC handlers + */ + +static void +miel_hvac_pre_init(void) +{ + struct miel_hvac_softc *sc; + int baudrate = 2400; + + if (!PinUsed(GPIO_MIEL_HVAC_TX) || !PinUsed(GPIO_MIEL_HVAC_RX)) + return; + + sc = (struct miel_hvac_softc *)malloc(sizeof(*sc)); + if (sc == NULL) { + AddLog_P(LOG_LEVEL_ERROR, + PSTR(MIEL_HVAC_LOGNAME ": unable to allocate state")); + return; + } + + memset(sc, 0, sizeof(*sc)); + miel_hvac_init_update(&sc->sc_update); + + sc->sc_serial = new TasmotaSerial(Pin(GPIO_MIEL_HVAC_RX), + Pin(GPIO_MIEL_HVAC_TX), 2); + + if (!sc->sc_serial->begin(baudrate, 2)) { + AddLog_P2(LOG_LEVEL_ERROR, + PSTR(MIEL_HVAC_LOGNAME ": unable to begin serial " + "(baudrate %d)"), baudrate); + goto del; + } + + if (sc->sc_serial->hardwareSerial()) { + ClaimSerial(); + SetSerial(baudrate, TS_SERIAL_8E1); + } + + sc->sc_device = devices_present++; /* claim a POWER device slot */ + + miel_hvac_sc = sc; + return; +del: + delete sc->sc_serial; +free: + free(sc); +} + +static void +miel_hvac_loop(struct miel_hvac_softc *sc) +{ + TasmotaSerial *serial = sc->sc_serial; + + while (serial->available()) { + yield(); + + sc->sc_parser.p_state = miel_hvac_parse(sc, serial->read()); + } +} + +static void +miel_hvac_sensor(struct miel_hvac_softc *sc) +{ + char hex[(sizeof(sc->sc_status) + 1) * 2]; + const char *sep = ""; + + ResponseAppend_P(PSTR("," "\"MiElHVAC\":{")); + + if (sc->sc_temp.type != 0) { + const struct miel_hvac_data_roomtemp *rt = + &sc->sc_temp.data.roomtemp; + unsigned int temp = miel_hvac_roomtemp2deg(rt->temp); + char room_temp[33]; + + dtostrfd(ConvertTemp(temp), + Settings.flag2.temperature_resolution, room_temp); + ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), + room_temp); + + sep = ","; + } + + if (sc->sc_status.type != 0) { + const struct miel_hvac_data_status *s = + &sc->sc_status.data.status; + + ResponseAppend_P(PSTR("%s" "\"Operation\":\"%s\"" "," + "\"Compressor\":\"%s\""), sep, + s->operation ? "ON" : "OFF", + s->compressor ? "ON" : "OFF"); + + sep = ","; + } + + if (sc->sc_temp.type != 0) { + ResponseAppend_P(PSTR("%s" "\"roomtemp\":\"%s\""), sep, + ToHex_P((uint8_t *)&sc->sc_temp, sizeof(sc->sc_temp), + hex, sizeof(hex))); + + sep = ","; + } + + if (sc->sc_status.type != 0) { + ResponseAppend_P(PSTR("%s" "\"status\":\"%s\""), sep, + ToHex_P((uint8_t *)&sc->sc_status, sizeof(sc->sc_status), + hex, sizeof(hex))); + + sep = ","; + } + + if (sc->sc_stage.type != 0) { + ResponseAppend_P(PSTR("%s" "\"stage\":\"%s\""), sep, + ToHex_P((uint8_t *)&sc->sc_stage, sizeof(sc->sc_stage), + hex, sizeof(hex))); + } + + ResponseAppend_P(PSTR("}")); +} + +/* + * This is set up to pace interactions with the aircon so we should + * only have a single outstanding request at a time, and to avoid trying + * to change settings while in the middle of reading them. SETTINGS is + * requested frequently so changes from the IR remote are noticed quickly + * and published. Posting new settings preempts queries for information. + */ + +enum miel_hvac_connect_states { + MIEL_HVAC_CONNECT_S_2400_MSG, + MIEL_HVAC_CONNECT_S_9600, + MIEL_HVAC_CONNECT_S_9600_MSG, + MIEL_HVAC_CONNECT_S_2400, + + MIEL_HVAC_CONNECT_S_COUNT, +}; + +static void +miel_hvac_connect(struct miel_hvac_softc *sc) +{ + TasmotaSerial *serial = sc->sc_serial; + uint32_t baudrate; + unsigned int state; + + state = (sc->sc_tick++ % MIEL_HVAC_CONNECT_S_COUNT); + + switch (state) { + case MIEL_HVAC_CONNECT_S_2400: + baudrate = 2400; + break; + case MIEL_HVAC_CONNECT_S_9600: + baudrate = 9600; + break; + default: + miel_hvac_send_connect(sc); + return; + } + + serial->begin(baudrate, 2); + if (serial->hardwareSerial()) + SetSerial(baudrate, TS_SERIAL_8E1); +} + +static void +miel_hvac_tick(struct miel_hvac_softc *sc) +{ + static const uint8_t updates[] = { + MIEL_HVAC_REQUEST_SETTINGS, + MIEL_HVAC_REQUEST_STATUS, + MIEL_HVAC_REQUEST_SETTINGS, + MIEL_HVAC_REQUEST_ROOMTEMP, + + MIEL_HVAC_REQUEST_SETTINGS, + /* MUZ-GA80VA doesnt respond :( */ + MIEL_HVAC_REQUEST_STAGE, + }; + + unsigned int i; + + if (miel_hvac_update_pending(sc)) { + struct miel_hvac_msg_update *update = &sc->sc_update; + + miel_hvac_send_update(sc, update); + + miel_hvac_init_update(update); + + /* refresh settings on next tick */ + sc->sc_tick = 0; + return; + } + + if (sc->sc_remotetemp.seven) { + struct miel_hvac_msg_remotetemp *remotetemp = + &sc->sc_remotetemp; + + miel_hvac_send_remotetemp(sc, remotetemp); + memset(remotetemp, 0, sizeof(*remotetemp)); + return; + } + + i = (sc->sc_tick++ % nitems(updates)); + + miel_hvac_request(sc, updates[i]); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +static const char miel_hvac_cmnd_names[] PROGMEM = + // No prefix + "|" D_CMND_MIEL_HVAC_SETFANSPEED + "|" D_CMND_MIEL_HVAC_SETMODE + "|" D_CMND_MIEL_HVAC_SETHAMODE + "|" D_CMND_MIEL_HVAC_SETTEMP + "|" D_CMND_MIEL_HVAC_SETSWINGV + "|" D_CMND_MIEL_HVAC_SETSWINGH + "|" D_CMND_MIEL_HVAC_REMOTETEMP +#ifdef MIEL_HVAC_DEBUG + "|" "HVACRequest" +#endif + ; + +static void (*const miel_hvac_cmnds[])(void) PROGMEM = { + &miel_hvac_cmnd_setfanspeed, + &miel_hvac_cmnd_setmode, + &miel_hvac_cmnd_sethamode, /* rain of hate */ + &miel_hvac_cmnd_settemp, + &miel_hvac_cmnd_setvane, + &miel_hvac_cmnd_setwidevane, + &miel_hvac_cmnd_remotetemp, +#ifdef MIEL_HVAC_DEBUG + &miel_hvac_cmnd_request, +#endif +}; + +bool Xdrv44(uint8_t function) { + bool result = false; + struct miel_hvac_softc *sc = miel_hvac_sc; + + switch (function) { + case FUNC_PRE_INIT: + miel_hvac_pre_init(); + return (false); + } + + if (sc == NULL) + return (false); + + switch (function) { + case FUNC_LOOP: + miel_hvac_loop(sc); + break; + + case FUNC_SET_DEVICE_POWER: + result = miel_hvac_set_power(sc); + break; + + case FUNC_EVERY_250_MSECOND: + if (sc->sc_connected) + miel_hvac_tick(sc); + else + miel_hvac_connect(sc); + break; + + case FUNC_EVERY_50_MSECOND: + case FUNC_EVERY_100_MSECOND: + case FUNC_EVERY_200_MSECOND: + case FUNC_EVERY_SECOND: + break; + + case FUNC_JSON_APPEND: + miel_hvac_sensor(sc); + break; + case FUNC_AFTER_TELEPERIOD: + if (sc->sc_settings_set) + miel_hvac_publish_settings(sc); + break; + + case FUNC_COMMAND: + result = DecodeCommand(miel_hvac_cmnd_names, miel_hvac_cmnds); + break; + } + + return (result); +} + +#endif // USE_MIEL_HVAC diff --git a/tools/decode-status.py b/tools/decode-status.py index f1c2c8302..928e68dd1 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -229,7 +229,7 @@ a_features = [[ "USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM", "USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01", "USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_I2S_AUDIO", - "USE_MLX90640","USE_VL53L1X","","", + "USE_MLX90640","USE_VL53L1X","USE_MIEL_HVAC","", "","USE_TTGO_WATCH","USE_ETHERNET","USE_WEBCAM" ],[ "","","","", @@ -267,7 +267,7 @@ else: obj = json.load(fp) def StartDecode(): - print ("\n*** decode-status.py v20200915 by Theo Arends and Jacek Ziolkowski ***") + print ("\n*** decode-status.py v20201002 by Theo Arends and Jacek Ziolkowski ***") # print("Decoding\n{}".format(obj))