From faf90677bd53d733db0e08d741679d2af7568d8c Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Mon, 23 Feb 2026 17:26:17 +0100 Subject: [PATCH] MiElHVAC improvements (#24486) * Update xdrv_44_miel_hvac.ino * Update xdrv_44_miel_hvac.ino - added energy sensors POWER, ENERGY TOTAL, FREQUENCY - other changes and improvements --- .../tasmota_xdrv_driver/xdrv_44_miel_hvac.ino | 202 +++++++++++++----- 1 file changed, 147 insertions(+), 55 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino b/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino index c79fcf091..77ba3b30e 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino @@ -28,10 +28,12 @@ #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif +// clang-format off #ifndef CTASSERT #define CTASSERT(x) extern char _ctassert[(x) ? 1 : -1 ] \ __attribute__((__unused__)) #endif +// clang-format on #define MIEL_HVAC_LOGNAME "MiElHVAC" @@ -43,8 +45,10 @@ #define D_CMND_MIEL_HVAC_SETSWINGH "HVACSetSwingH" #define D_CMND_MIEL_HVAC_SETAIRDIRECTION "HVACSetAirDirection" #define D_CMND_MIEL_HVAC_SETPROHIBIT "HVACSetProhibit" +#define D_CMND_MIEL_HVAC_SETPURIFY "HVACSetPurify" #define D_CMND_MIEL_HVAC_REMOTETEMP "HVACRemoteTemp" #define D_CMND_MIEL_HVAC_REMOTETEMP_AUTO_CLEAR_TIME "HVACRemoteTempClearTime" +#define D_CMND_MIEL_HVAC_SEND_COMMAND "HVACSendCommand" #include @@ -119,21 +123,23 @@ struct miel_hvac_data_settings uint8_t _pad2[1]; uint8_t widevane; #define MIEL_HVAC_SETTINGS_WIDEVANE_MASK 0x0f -#define MIEL_HVAC_SETTINGS_WIDEVANE_AUTO 0x00 #define MIEL_HVAC_SETTINGS_WIDEVANE_LL 0x01 #define MIEL_HVAC_SETTINGS_WIDEVANE_L 0x02 #define MIEL_HVAC_SETTINGS_WIDEVANE_C 0x03 #define MIEL_HVAC_SETTINGS_WIDEVANE_R 0x04 #define MIEL_HVAC_SETTINGS_WIDEVANE_RR 0x05 -#define MIEL_HVAC_SETTINGS_WIDEVANE_LR 0x08 +#define MIEL_HVAC_SETTINGS_WIDEVANE_LC 0x06 +#define MIEL_HVAC_SETTINGS_WIDEVANE_RC 0x07 +#define MIEL_HVAC_SETTINGS_WIDEVANE_SPLIT 0x08 #define MIEL_HVAC_SETTINGS_WIDEVANE_SWING 0x0c -#define MIEL_HVAC_SETTINGS_WIDEVANE_ADJ 0x80 +#define MIEL_HVAC_SETTINGS_WIDEVANE_ISEE 0x80 uint8_t temp05; uint8_t _pad3[2]; uint8_t airdirection; #define MIEL_HVAC_SETTINGS_AIRDIRECTION_EVEN 0x00 #define MIEL_HVAC_SETTINGS_AIRDIRECTION_INDIRECT 0x01 #define MIEL_HVAC_SETTINGS_AIRDIRECTION_DIRECT 0x02 +#define MIEL_HVAC_SETTINGS_AIRDIRECTION_OFF 0x03 }; struct miel_hvac_data_roomtemp @@ -284,13 +290,13 @@ struct miel_hvac_msg_update_settings uint8_t one; uint16_t flags; #define MIEL_HVAC_SETTINGS_F_WIDEVANE (1 << 0) +#define MIEL_HVAC_SETTINGS_F_AIRDIRECTION (1 << 3) #define MIEL_HVAC_SETTINGS_F_POWER (1 << 8) #define MIEL_HVAC_SETTINGS_F_MODE (1 << 9) #define MIEL_HVAC_SETTINGS_F_TEMP (1 << 10) #define MIEL_HVAC_SETTINGS_F_FAN (1 << 11) #define MIEL_HVAC_SETTINGS_F_VANE (1 << 12) #define MIEL_HVAC_SETTINGS_F_PROHIBIT (1 << 13) -#define MIEL_HVAC_SETTINGS_F_AIRDIRECTION (1 << 14) uint8_t power; uint8_t mode; uint8_t temp; @@ -435,14 +441,16 @@ static const struct miel_hvac_map miel_hvac_vane_map[] = { }; static const struct miel_hvac_map miel_hvac_widevane_map[] = { - {MIEL_HVAC_SETTINGS_WIDEVANE_AUTO, "auto"}, {MIEL_HVAC_SETTINGS_WIDEVANE_LL, "left"}, {MIEL_HVAC_SETTINGS_WIDEVANE_L, "left_middle"}, {MIEL_HVAC_SETTINGS_WIDEVANE_C, "center"}, {MIEL_HVAC_SETTINGS_WIDEVANE_R, "right"}, {MIEL_HVAC_SETTINGS_WIDEVANE_RR, "right_middle"}, - {MIEL_HVAC_SETTINGS_WIDEVANE_LR, "split"}, + {MIEL_HVAC_SETTINGS_WIDEVANE_LC, "left_center"}, + {MIEL_HVAC_SETTINGS_WIDEVANE_RC, "right_center"}, + {MIEL_HVAC_SETTINGS_WIDEVANE_SPLIT, "split"}, {MIEL_HVAC_SETTINGS_WIDEVANE_SWING, "swing"}, + {MIEL_HVAC_SETTINGS_WIDEVANE_ISEE, "isee"}, }; static const struct miel_hvac_map miel_hvac_prohibit_map[] = { @@ -457,11 +465,17 @@ static const struct miel_hvac_map miel_hvac_prohibit_map[] = { }; static const struct miel_hvac_map miel_hvac_airdirection_map[] = { - {MIEL_HVAC_SETTINGS_AIRDIRECTION_EVEN, "even"}, {MIEL_HVAC_SETTINGS_AIRDIRECTION_INDIRECT, "indirect"}, {MIEL_HVAC_SETTINGS_AIRDIRECTION_DIRECT, "direct"}, + {MIEL_HVAC_SETTINGS_AIRDIRECTION_EVEN, "even"}, + {MIEL_HVAC_SETTINGS_AIRDIRECTION_OFF, "off"}, }; +// static const struct miel_hvac_map miel_hvac_purify_map[] = { +// {MIEL_HVAC_SETTINGS_PURIFY_OFF "off"}, +// {MIEL_HVAC_SETTINGS_PURIFY_ON, "on"}, +// }; + static const struct miel_hvac_map miel_hvac_compressor_map[] = { {MIEL_HVAC_STATUS_COMPRESSOR_OFF, "off"}, {MIEL_HVAC_STATUS_COMPRESSOR_ON, "on"}, @@ -660,6 +674,9 @@ miel_hvac_parse(struct miel_hvac_softc *sc, uint8_t byte) return (nstate); case MIEL_HVAC_P_SKIP_CKSUM: return (MIEL_HVAC_P_START); + default: + AddLog(LOG_LEVEL_ERROR, PSTR(MIEL_HVAC_LOGNAME ": unknown parser state %d, resetting"), p->p_state); + return (MIEL_HVAC_P_START); } p->p_sum += byte; @@ -875,11 +892,12 @@ miel_hvac_cmnd_settemp(void) { struct miel_hvac_softc *sc = miel_hvac_sc; struct miel_hvac_msg_update_settings *update = &sc->sc_settings_update; + float degc; if (XdrvMailbox.data_len == 0) return; - float degc = CharToFloat(XdrvMailbox.data); + degc = strtof(XdrvMailbox.data, nullptr); if (degc < MIEL_HVAC_SETTINGS_TEMP_MIN || degc > MIEL_HVAC_SETTINGS_TEMP_MAX) { miel_hvac_respond_unsupported(); @@ -898,7 +916,7 @@ miel_hvac_cmnd_settemp(void) update->temp05 = miel_hvac_deg2temp(degc); } - ResponseCmndFloat(degc, Settings->flag2.temperature_resolution); + ResponseCmndNumber(degc); } static void @@ -961,6 +979,7 @@ miel_hvac_cmnd_setwidevane(void) miel_hvac_respond_unsupported(); return; } + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE); update->widevane = e->byte; @@ -983,8 +1002,52 @@ miel_hvac_cmnd_setairdirection(void) miel_hvac_respond_unsupported(); return; } - update->flags |= htons(MIEL_HVAC_SETTINGS_F_AIRDIRECTION); - update->airdirection = e->byte; + + switch (e->byte) + { + case MIEL_HVAC_SETTINGS_AIRDIRECTION_INDIRECT: // 0x80 + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE) | htons(MIEL_HVAC_SETTINGS_F_AIRDIRECTION); + update->widevane = 0x80; + update->airdirection = e->byte; + break; + case MIEL_HVAC_SETTINGS_AIRDIRECTION_DIRECT: // 0xaa + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE) | htons(MIEL_HVAC_SETTINGS_F_AIRDIRECTION); + update->widevane = 0x80; + update->airdirection = e->byte; + break; + case MIEL_HVAC_SETTINGS_AIRDIRECTION_EVEN: // 0x28 + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE) | htons(MIEL_HVAC_SETTINGS_F_AIRDIRECTION); + update->widevane = 0x80; + update->airdirection = e->byte; + break; + case MIEL_HVAC_SETTINGS_AIRDIRECTION_OFF: // 0x8c + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE); + update->widevane = 0x8c; + break; + } + + ResponseCmndChar_P(e->name); +} + +static void +miel_hvac_cmnd_setpurify(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update_settings *update = &sc->sc_settings_update; + const struct miel_hvac_map *e; + + if (XdrvMailbox.data_len == 0) + return; + + e = miel_hvac_map_byname(XdrvMailbox.data, miel_hvac_airdirection_map, nitems(miel_hvac_airdirection_map)); + if (e == NULL) + { + miel_hvac_respond_unsupported(); + return; + } + + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE); + update->widevane = e->byte; ResponseCmndChar_P(e->name); } @@ -1089,6 +1152,34 @@ miel_hvac_cmnd_remotetemp(void) remotetemp_clear = control == MIEL_HVAC_REMOTETEMP_SET ? true : false; } +static void +miel_hvac_cmnd_send_command(void) +{ + struct miel_hvac_softc *sc = miel_hvac_sc; + struct miel_hvac_msg_update_settings *update = &sc->sc_settings_update; + uint8_t val; + + if (XdrvMailbox.data_len == 0) + return; + + // Parse hex or decimal string input (e.g., "0x08" or "08") + char *endptr; + long parsed = strtol(XdrvMailbox.data, &endptr, 0); // base 0 allows hex or decimal + + if (endptr == XdrvMailbox.data || parsed < 0 || parsed > 255) + { + miel_hvac_respond_unsupported(); + return; + } + + val = (uint8_t)parsed; + + update->flags |= htons(MIEL_HVAC_SETTINGS_F_WIDEVANE); + update->widevane = val; + + ResponseCmndChar_P(XdrvMailbox.data); +} + #ifdef MIEL_HVAC_DEBUG static void miel_hvac_cmnd_request(void) @@ -1131,7 +1222,7 @@ miel_hvac_publish_settings(struct miel_hvac_softc *sc) char hex[(sizeof(sc->sc_settings) + 1) * 2]; char temp[33]; const char *name; - const char *name_swing_h; + bool widevane_isee = (set->widevane == 0x80 || set->widevane == 0xaa || set->widevane == 0x28); name = miel_hvac_map_byval(set->power, miel_hvac_power_map, nitems(miel_hvac_power_map)); if (name != NULL) @@ -1168,16 +1259,16 @@ miel_hvac_publish_settings(struct miel_hvac_softc *sc) ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGV "\":\"%s\""), name); } - name_swing_h = miel_hvac_map_byval(set->widevane & MIEL_HVAC_SETTINGS_WIDEVANE_MASK, miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); - if (name_swing_h != NULL) - { - ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGH "\":\"%s\""), name_swing_h); - } - - name = miel_hvac_map_byval(set->airdirection, miel_hvac_airdirection_map, nitems(miel_hvac_airdirection_map)); + name = widevane_isee ? "isee" : miel_hvac_map_byval(set->widevane & MIEL_HVAC_SETTINGS_WIDEVANE_MASK, miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); if (name != NULL) { - ResponseAppend_P(PSTR(",\"AirDirection\":\"%s\""), name_swing_h == "auto" ? name : "OFF"); + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGH "\":\"%s\""), name); + } + + name = widevane_isee ? miel_hvac_map_byval(set->airdirection, miel_hvac_airdirection_map, nitems(miel_hvac_airdirection_map)) : "off"; + if (name != NULL) + { + ResponseAppend_P(PSTR(",\"AirDirection\":\"%s\""), name); } name = miel_hvac_map_byval(set->prohibit, miel_hvac_prohibit_map, nitems(miel_hvac_prohibit_map)); @@ -1351,7 +1442,6 @@ miel_hvac_sensor(struct miel_hvac_softc *sc) { const char *name; const char *mode; - const char *name_swing_h; ResponseAppend_P(PSTR(",\"MiElHVAC\":{")); @@ -1360,17 +1450,19 @@ miel_hvac_sensor(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]; + bool widevane_isee = (set->widevane == 0x80 || set->widevane == 0xaa || set->widevane == 0x28); name = miel_hvac_map_byval(set->power, miel_hvac_power_map, nitems(miel_hvac_power_map)); if (name != NULL) { - ResponseAppend_P(PSTR("\"Power\":\"%s\""), name); + ResponseAppend_P(PSTR("\"" D_JSON_IRHVAC_POWER "\":\"%s\""), name); } mode = miel_hvac_map_byval(set->mode & MIEL_HVAC_SETTINGS_MODE_MASK, miel_hvac_mode_map, nitems(miel_hvac_mode_map)); if (mode != NULL) { - ResponseAppend_P(PSTR(",\"Mode\":\"%s\""), mode); + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_MODE "\":\"%s\""), mode); + ResponseAppend_P(PSTR(",\"HA" D_JSON_IRHVAC_MODE "\":\"%s\""), set->power ? mode : "off"); } if (set->temp05 == 0) @@ -1387,25 +1479,25 @@ miel_hvac_sensor(struct miel_hvac_softc *sc) name = miel_hvac_map_byval(set->fan, miel_hvac_fan_map, nitems(miel_hvac_fan_map)); if (name != NULL) { - ResponseAppend_P(PSTR(",\"FanSpeed\":\"%s\""), name); + 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(",\"SwingV\":\"%s\""), name); + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGV "\":\"%s\""), name); } - name_swing_h = miel_hvac_map_byval(set->widevane & MIEL_HVAC_SETTINGS_WIDEVANE_MASK, miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); - if (name_swing_h != NULL) - { - ResponseAppend_P(PSTR(",\"SwingH\":\"%s\""), name_swing_h); - } - - name = miel_hvac_map_byval(set->airdirection, miel_hvac_airdirection_map, nitems(miel_hvac_airdirection_map)); + name = widevane_isee ? "isee" : miel_hvac_map_byval(set->widevane & MIEL_HVAC_SETTINGS_WIDEVANE_MASK, miel_hvac_widevane_map, nitems(miel_hvac_widevane_map)); if (name != NULL) { - ResponseAppend_P(PSTR(",\"AirDirection\":\"%s\""), name_swing_h == "auto" ? name : "off"); + ResponseAppend_P(PSTR(",\"" D_JSON_IRHVAC_SWINGH "\":\"%s\""), name); + } + + name = widevane_isee ? miel_hvac_map_byval(set->airdirection, miel_hvac_airdirection_map, nitems(miel_hvac_airdirection_map)) : "off"; + if (name != NULL) + { + ResponseAppend_P(PSTR(",\"AirDirection\":\"%s\""), name); } name = miel_hvac_map_byval(set->prohibit, miel_hvac_prohibit_map, nitems(miel_hvac_prohibit_map)); @@ -1433,7 +1525,7 @@ miel_hvac_sensor(struct miel_hvac_softc *sc) float temp = miel_hvac_roomtemp2deg(rt->temp05); dtostrfd(ConvertTemp(temp), Settings->flag2.temperature_resolution, room_temp); } - ResponseAppend_P(PSTR(",\"Temperature\":\"%s\""), room_temp); + ResponseAppend_P(PSTR(",\"RoomTemperature\":\"%s\""), room_temp); ResponseAppend_P(PSTR(",\"RemoteTemperatureSensorState\":\"%s\""), remotetemp_clear ? "on" : "off"); char remotetempautocleartime[33]; @@ -1497,23 +1589,23 @@ miel_hvac_sensor(struct miel_hvac_softc *sc) char hex[(sizeof(sc->sc_status) + 1) * 2]; name = miel_hvac_map_byval(status->compressor, miel_hvac_compressor_map, nitems(miel_hvac_compressor_map)); - ResponseAppend_P(PSTR(",\"Compressor\":\"%s\""), name != NULL ? name : "N/A"); + ResponseAppend_P(PSTR(",\"CompressorState\":\"%s\""), name != NULL ? name : "N/A"); unsigned int compressor_frequency = status->compressorfrequency; char compressorfrequency[33]; utoa(compressor_frequency, compressorfrequency, 10); - ResponseAppend_P(PSTR(",\"CompressorFrequency\":\"%s\""), compressorfrequency); + ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":\"%s\""), compressorfrequency); uint16_t combined_power = ((uint16_t)status->operationpower << 8) | (uint16_t)status->operationpower1; char operationpower[33]; dtostrfd((float)combined_power, 0, operationpower); - ResponseAppend_P(PSTR(",\"OperationPower\":\"%s\""), operationpower); + ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":\"%s\""), operationpower); uint16_t combined_energy = ((uint16_t)status->operationenergy << 8) | (uint16_t)status->operationenergy1; float operationenergy_in_kWh = (float)combined_energy / 10.0; char operationenergy[33]; dtostrfd((float)operationenergy_in_kWh, 1, operationenergy); - ResponseAppend_P(PSTR(",\"OperationEnergy\":\"%s\""), operationenergy); + ResponseAppend_P(PSTR(",\"" D_JSON_ENERGY "\":\"%s\""), operationenergy); ResponseAppend_P(PSTR(",\"Status\":\"%s\""), ToHex_P((uint8_t *)&sc->sc_status, sizeof(sc->sc_status), hex, sizeof(hex))); } @@ -1557,10 +1649,10 @@ miel_hvac_sensor(struct miel_hvac_softc *sc) enum miel_hvac_connect_states { + MIEL_HVAC_CONNECT_S_2400, 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, }; @@ -1650,7 +1742,6 @@ miel_hvac_tick(struct miel_hvac_softc *sc) /*********************************************************************************************\ * Interface \*********************************************************************************************/ - static const char miel_hvac_cmnd_names[] PROGMEM = // No prefix "|" D_CMND_MIEL_HVAC_SETFANSPEED @@ -1661,8 +1752,10 @@ static const char miel_hvac_cmnd_names[] PROGMEM = "|" D_CMND_MIEL_HVAC_SETSWINGH "|" D_CMND_MIEL_HVAC_SETAIRDIRECTION "|" D_CMND_MIEL_HVAC_SETPROHIBIT + "|" D_CMND_MIEL_HVAC_SETPURIFY "|" D_CMND_MIEL_HVAC_REMOTETEMP "|" D_CMND_MIEL_HVAC_REMOTETEMP_AUTO_CLEAR_TIME + "|" D_CMND_MIEL_HVAC_SEND_COMMAND #ifdef MIEL_HVAC_DEBUG "|" "HVACRequest" @@ -1678,8 +1771,10 @@ static void (*const miel_hvac_cmnds[])(void) PROGMEM = { &miel_hvac_cmnd_setwidevane, &miel_hvac_cmnd_setairdirection, &miel_hvac_cmnd_setprohibit, + &miel_hvac_cmnd_setpurify, &miel_hvac_cmnd_remotetemp, &miel_hvac_cmnd_remotetemp_auto_clear_time, + &miel_hvac_cmnd_send_command, #ifdef MIEL_HVAC_DEBUG &miel_hvac_cmnd_request, #endif @@ -1687,18 +1782,15 @@ static void (*const miel_hvac_cmnds[])(void) PROGMEM = { bool Xdrv44(uint32_t function) { - bool result = false; - struct miel_hvac_softc *sc = miel_hvac_sc; - - switch (function) + if (function == FUNC_PRE_INIT) { - case FUNC_PRE_INIT: miel_hvac_pre_init(); - return (false); + return false; } + struct miel_hvac_softc *sc = miel_hvac_sc; if (sc == NULL) - return (false); + return false; switch (function) { @@ -1706,8 +1798,7 @@ bool Xdrv44(uint32_t function) miel_hvac_loop(sc); break; case FUNC_SET_DEVICE_POWER: - result = miel_hvac_set_power(sc); - break; + return miel_hvac_set_power(sc); case FUNC_EVERY_250_MSECOND: if (sc->sc_connected) miel_hvac_tick(sc); @@ -1718,7 +1809,9 @@ bool Xdrv44(uint32_t function) case FUNC_EVERY_100_MSECOND: case FUNC_EVERY_200_MSECOND: case FUNC_EVERY_SECOND: - if (remotetemp_clear && ((millis() - remotetemp_last_call_time) > remotetemp_auto_clear_time || remotetemp_last_call_time == 0)) + if (remotetemp_clear && + (remotetemp_last_call_time == 0 || + millis() - remotetemp_last_call_time > remotetemp_auto_clear_time)) { miel_hvac_remotetemp_auto_clear(); } @@ -1731,13 +1824,12 @@ bool Xdrv44(uint32_t function) miel_hvac_publish_settings(sc); break; case FUNC_COMMAND: - result = DecodeCommand(miel_hvac_cmnd_names, miel_hvac_cmnds); - break; + return DecodeCommand(miel_hvac_cmnd_names, miel_hvac_cmnds); case FUNC_ACTIVE: - result = true; - break; + return true; } - return (result); + + return false; } #endif // USE_MIEL_HVAC