From 899a193d014b42ee2181b74820845c3458d1f5ac Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Thu, 16 May 2024 07:13:16 +0200 Subject: [PATCH 01/12] feat(debug): option to publish raw messages --- AquaMQTT/include/config/Configuration.h | 6 ++++ AquaMQTT/include/message/MessageConstants.h | 13 +++++++ AquaMQTT/include/mqtt/MQTTDefinitions.h | 2 ++ AquaMQTT/src/task/MQTTTask.cpp | 39 +++++++++++++++++++++ MQTT.md | 6 ++-- 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/AquaMQTT/include/config/Configuration.h b/AquaMQTT/include/config/Configuration.h index bc64863..b5324d4 100644 --- a/AquaMQTT/include/config/Configuration.h +++ b/AquaMQTT/include/config/Configuration.h @@ -44,6 +44,12 @@ constexpr EOperationMode OPERATION_MODE = EOperationMode::MITM; */ constexpr bool OVERRIDE_TIME_AND_DATE_IN_MITM = true; + +/** + * Choose to publish raw messages represented as hex-string on debug mqtt topics + */ +constexpr bool DEBUG_RAW_SERIAL_MESSAGES = false; + /** * Self explanatory internal settings: most probably you don't want to change them. */ diff --git a/AquaMQTT/include/message/MessageConstants.h b/AquaMQTT/include/message/MessageConstants.h index 7998f27..3b49083 100644 --- a/AquaMQTT/include/message/MessageConstants.h +++ b/AquaMQTT/include/message/MessageConstants.h @@ -1,6 +1,8 @@ #ifndef AQUAMQTT_MESSAGECONSTANTS_H #define AQUAMQTT_MESSAGECONSTANTS_H +#include + #include "mqtt/MQTTDefinitions.h" namespace aquamqtt @@ -177,6 +179,17 @@ static const char* setupStr(HMISetup installation) } } +static void toHexStr(uint8_t* data, uint8_t data_len, char* buffer){ + + const size_t num_bytes = data_len / sizeof(uint8_t); + //char hex_str[num_bytes * 2 + 1]; + for (size_t i = 0; i < num_bytes; i++) + { + sprintf(&buffer[i * 2], "%02X", data[i]); + } + buffer[num_bytes * 2] = '\0'; +} + } // namespace message } // namespace aquamqtt diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 8e8d5a3..663020c 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -113,6 +113,8 @@ const char STATS_ACTIVE_OVERRIDES[] PROGMEM = { "activeOverrides" }; const char STATS_ENABLE_FLAG_PV_HEATPUMP[] PROGMEM = { "flagPVModeHeatPump" }; const char STATS_ENABLE_FLAG_PV_HEATELEMENT[] PROGMEM = { "flagPVModeHeatElement" }; +const char DEBUG[] PROGMEM = { "debug" }; + // CTRL const char AQUAMQTT_RESET_OVERRIDES[] PROGMEM = { "reset" }; diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index ad6aecc..59e9c5f 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -656,6 +656,19 @@ void MQTTTask::updateMainStatus() MAIN_SUBTOPIC, MAIN_STATE_DEFROST); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + if (config::DEBUG_RAW_SERIAL_MESSAGES) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + DEBUG); + + toHexStr(mTransferBuffer, MAIN_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } } #pragma clang diagnostic push @@ -786,6 +799,19 @@ void MQTTTask::updateHMIStatus() HMI_TIMER_WINDOW_B); message.timerWindowStr(false, reinterpret_cast(mPayloadBuffer)); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + if (config::DEBUG_RAW_SERIAL_MESSAGES) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + HMI_SUBTOPIC, + DEBUG); + + toHexStr(mTransferBuffer, HMI_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } } #pragma clang diagnostic push @@ -868,6 +894,19 @@ void MQTTTask::updateEnergyStats() ENERGY_SUBTOPIC, ENERGY_POWER_TOTAL); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + if (config::DEBUG_RAW_SERIAL_MESSAGES) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + ENERGY_SUBTOPIC, + DEBUG); + + toHexStr(mTransferBuffer, ENERGY_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } } } // namespace aquamqtt diff --git a/MQTT.md b/MQTT.md index aef03ec..6223cb1 100644 --- a/MQTT.md +++ b/MQTT.md @@ -51,7 +51,7 @@ Using the prefix, the `$root` topic is created, which is `$prefix/aquamqtt/` and | Configuration AirDuct | `$root/hmi/configAirduct` | Enum | | INT/INT, INT/EXT, EXT/EXT | | Configuration Installation | `$root/hmi/configInstallation` | Enum | | | | HMI SetupState | `$root/hmi/setupState` | Enum | | OK, PARTIAL RESET, FACTORY RESET | - +| Raw Message (Debug Mode Only) | `$root/hmi/debug` | string | | | ### Main Message @@ -67,7 +67,8 @@ Using the prefix, the `$root` topic is created, which is `$prefix/aquamqtt/` and | State: Heating Element On/Off | `$root/main/stateElement` | bool | | | | State: Boiler Backup On/Off | `$root/main/stateExtBoiler` | bool | | | | State: Defrost On/Off Date | `$root/main/stateDefrost` | bool | | | - +| Raw Message (Debug Mode Only) | `$root/main/debug` | string | | | + ### Energy Message @@ -80,6 +81,7 @@ Using the prefix, the `$root` topic is created, which is `$prefix/aquamqtt/` and | Current Power Heatpump | `$root/energy/powerHeatpump` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | | Current Power Heating Element | `$root/energy/powerHeatingElem` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | | Current Power Total | `$root/energy/powerTotal` | uint16 | W | | +| Raw Message (Debug Mode Only) | `$root/energy/debug` | string | | | ## Subscribe Topics From 36299401fe3b24018e615c98aef4de6e8a5eec00 Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Thu, 16 May 2024 07:13:30 +0200 Subject: [PATCH 02/12] feat(protocol): documented latest protocol findings --- PROTOCOL.md | 256 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 205 insertions(+), 51 deletions(-) diff --git a/PROTOCOL.md b/PROTOCOL.md index 1f05826..0589a43 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -17,7 +17,9 @@ The original setup needs 4 of those pins, which are connected to the main contro - VCC 5V (green) - GND (red/brown) - TX/RX, Logical 5V (white) -- Unknown (yellow) +- Unknown/Unused(yellow) + +[*Depending on the heatpump, sometimes there are only three wires.*](https://github.com/tspopp/AquaMQTT/issues/18) ## Overview @@ -89,18 +91,18 @@ The main controller gives the HMI controller permission to send by emitting the | 0 | 35 | Length Field | - | | 1 - 2 | 18 2 | Target Temp. | See Temperature Table, 53°C | | 3 | 66 | OperationMode | See Operation Mode Table | -| 4 | 252 | ? | - | +| 4 | 252 | Command: Change PWM, Command: Change Anti-Trockenheizung | See Commands | | 5 | 0 | Anti-Legionella Mode / AirDuct Mode | 0 == Off, 1 == 1perMonth, 2 == 2perMonth, 3 == 3perMonth, 4 == 4/perMonth, // 0 == AirDuct INT/INT, 16 == AirDuct EXT/INT, 32 == AirDuct EXT/EXT | -| 6 | 240 | Emergency-Mode | Off == 240, On == 241 | +| 6 | 240 | Emergency-Mode, Command: Change Connectivity | "Emergency Mode Off == 240, Emergency Mode On == 241", "Disabled Connectivity: No == 240, Disabled Connectivity: Yes == 16" | | 7 | 17 | InstallationConfig | WP-Only == 0, WP+ExtBoiler-Prio-WP == 1, Wp+ExtBoiler-Opt-WP == 17, Wp+ExtBoiler-Opt-ExtBoiler == 33 , Wp+ExtBoiler-Prio-ExtBoiler == 49 , WP + Solar == 50 | -| 8 | 240 | ? | - | +| 8 | 240 | Command: Change Heat-Exchanger | See Commands | | 9 | 4 | Heating-Element / SetupState | Heating-Element Automatic-Mode == 4, Heating-Element Disabled == 0, Setup Factory Settings == 164, Setup Airduct Set == 36, Setup Finished == 4 | | 10 | 16 | Timer Mode: Window 1 Start | 16 = 04:00h, 12 = 03:00h | | 11 | 56 | Timer Mode: Window 1 Length | 52 = 13h runtime, 56 = 14h runtime| | 12 | 0 | Timer Mode: Window 2 Start | e.g. 52 = 13:00h - Value 0x00 0x00 is supported if Timer 2 is not set. | | 13 | 0 | Timer Mode: Window 2 Length | e.g. 16 = 04h runtime. Max allowed length of both timers in sum is 14h runtime| -| 14 | 255 | ? | - | -| 15 | 255 | ? | - | +| 14 | 255 | Command: Change PV-Input, Change Circulation | - | +| 15 | 255 | Command: Change Min Target Temperature | Note: Range 40-50°C | | 16 | 0 | ? | - | | 17 | 25 | Current Time Seconds | - | | 18 | 151 | Current Day, Current Month in Half-Year | See Formula below | @@ -109,23 +111,22 @@ The main controller gives the HMI controller permission to send by emitting the | 21 | 13 | Current Time Hour | - | | 22 | 0 | TestMode Status | HMI Left TestMode == 0, HMI Entered TestMode == 1, Heatpump TestMode == 2, Heating-Element TestMode == 3, Fan-Slow TestMode == 4 , Fan-Fast TestMode == 5 , Defrost TestMode == 6, Heatpump + EXT Boiler TestMode == 8 | 23 | 0 | ? | - | -| 24 | 255 | ? | - | +| 24 | 255 | Command: Change PWM Value, Contains PWM Setpoint | - | | 25 | 0 | ? | - | | 26 | 9 | ? | - | | 27 | 66 | ? | - | | 28 | 0 | ? | - | | 29 | 0 | ? | - | -| 30 | 255 | ? | - | -| 31 | 255 | ? | - | -| 32 | 255 | ? | - | -| 33 | 255 | ? | - | -| 34 | 255 | ? | - | +| 30 | 255 | Command: Change Anti-Legionalla Temperature Target | See Commands. Note: Valid Range 62-70°C | +| 31 | 255 | Command: Change Installed Wattage Heat Element | See Commands. Note: Valid Range 10-30 (1000 - 3000W) | +| 32 | 255 | Command: Change Brand of Heat-Pump | See Commands. | +| 33 - 34 | 255 255 | Command: Change Boiler Capacity | See Commands. | ##### Byte No. 3: Operation Mode - +Findings... ``` -2dec | 0000 0010: AlwaysOn + ECO Inactive (?) +2dec | 0000 0010: AlwaysOn + ECO Inactive 3dec | 0000 0011: AlwaysOn + Boost 64dec | 0100 0000: TimerMode + Absence 65dec | 0100 0001: TimerMode + ECO Active @@ -134,19 +135,15 @@ The main controller gives the HMI controller permission to send by emitting the 68dec | 0100 0100: TimerMode + AUTO ``` -Maybe: -``` -Bit 3 - 0 interpreted as integer -0: Absence -1: Eco Active -2: Eco Inactive -3: Boost -4: Auto -``` -``` -Bit 6: TimerMode enabled -``` +| Bit Number | Purpose/Function | Other Information | +|------------|-------------------------|-------------------| +| 0 - 3 | Operation Mode | Interpreted as integer, 0 == Absence, 1 == Eco Active, 2 == Eco Inoactive, 3 == Boost, 4 == Auto | +| 4 | ? | | +| 5 | ? | | +| 6 | TimerMode enabled | | +| 7 | ? | | + ##### Temperature Values @@ -197,7 +194,139 @@ Example Table (Byte 19): 46dec = 2023 (First Half) 47dec = 2023 (Second Half) ``` +#### Commands + +Commands are executed by the HMI as soon as placeholder fields/values are replaced by command values. The HMI Controller awaits the change of the Main controller and then resets the placeholder fields to the previous placeholder value. + +##### Change Capactiy + +- Affected Byte Positions: 33, 34 + +Examples: +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +270l: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 14 1 +200l: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 200 0 +``` + +##### Change PWM + +- Affected Byte Positions: 4, 24 +- Allowed Range 50-100% + +Examples: + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +PWM 1 to 50: 35 18 2 65 60 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 50 0 9 66 1 1 255 255 255 255 255 +PWM 1 to 65: 35 18 2 65 60 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 65 0 9 66 1 1 255 255 255 255 255 +PWM 2 to 81: 35 18 2 65 124 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 81 0 9 66 1 1 255 255 255 255 255 +PWM 2 to 60: 35 18 2 65 124 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 60 0 9 66 1 1 255 255 255 255 255 +PWM 3 to 100: 35 18 2 65 188 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 100 0 9 66 1 1 255 255 255 255 255 +PWM 3 to 66: 35 18 2 65 188 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 66 0 9 66 1 1 255 255 255 255 255 +``` + +##### Change Brand + +- Affected Byte Positions: 32 +- 65dec = Atlantic +- 78dec = NoName +- 83dec = Sauter +- 84dec = Thermor + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Atlantic: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 65 255 255 +NoName: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 78 255 255 +Sauter: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 83 255 255 +Thermor: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 84 255 255 +``` + +##### Enable/Disable Heat-Exchanger + +- Affected Byte Positions: 8 +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Yes: 35 18 2 65 252 0 240 32 16 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +No: 35 18 2 65 252 0 240 32 0 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + +##### Enable/Disable Communication + +- Affected Byte Positions: 6 +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Not Disabled: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Disabled: 35 18 2 65 252 0 16 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` +##### Enable/Disable Circulation + +- Affected Byte Positions: 14 +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +No: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 240 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Yes: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 241 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + +##### Enable/Disable PV-Input + +- Affected Byte Positions: 14 + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +No: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 16 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Yes: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 17 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + + +##### Change Min Target Temperature + +- Affected Byte Positions: 16 +- Allowed Range 40-50°C + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +44°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 44 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +40°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 40 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + +##### Enable/Disable Anti-Trockenheizung + + +- Affected Byte Positions: 4 + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Enabled: 35 18 2 65 128 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Disabled: 35 18 2 65 132 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + +##### Change Anti-Legionalle Target Temperature + +- Affected Byte Positions: 30 +- Range: 62-70°C + +Examples: +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +62°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 62 255 255 255 255 +70°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 70 255 255 255 255 +``` + +##### Change Wattage of installed heat-element + +- Affected Byte Positions: 31 +- Range 1000 - 3000W +- Value divided by 100 + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +1000W: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 10 255 255 255 +1600W: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 16 255 255 255 +1700W: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 17 255 255 255 +3000W: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 30 255 255 255 +``` ### Send by the main controller @@ -215,44 +344,46 @@ Example Table (Byte 19): | 11 | 0 | ? | - | | 12 | 0 | ? | - | | 13 | 1 | ? | - | -| 14 | 100 | ? | - | -| 15 | 65 | ? | - | -| 16 | 81 | ? | - | +| 14 | 100 | Setting: PWM Level 3 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller*| +| 15 | 65 | Setting: PWM Level 1 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | +| 16 | 81 | Setting: PWM Level 2 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | | 17 | 8 or 0 | Picture Bitmask | See table below | -| 18 - 19 | 0 0 | Fan-Speed | Either 0 (off) or 650 (lowspeed) or 810 (highspeed) Maybe rpm?| -| 20 | 50 | ? | - | -| 21 | 62 | ? | - | +| 18 - 19 | 0 0 | Fan-Speed | Either 0 (off) or 650 (lowspeed) or 810 (highspeed) Maybe rpm? Does this reflect the PWM settings?| +| 20 | 44 | Setting: Min T Target | *Adjustable from HMI controller* | +| 21 | 62 | Setting: Anti-Legionella T Target | *Adjustable from HMI controller* | | 22 | 0 | ? | - | -| 23 | 255 | ? | - | -| 24 | 255 | ? | - | -| 25 | 255 | ? | - | +| 23 | 255 | Errors? | In Error State this contains Error Code, eg. 7 | +| 24 | 255 | Errors? | In Error State this contains 0 | +| 25 | 255 | Errors? | In Error State this contains 0 | | 26 | 87 | ? | - | | 27 | 9 | ? | - | | 28 | 66 | ? | - | | 29 | 88 | ? | - | | 30 | 2 | ? | - | | 31 | 0 | ? | - | -| 32 | 16 | ? | - | -| 33 | 14 | ? | - | -| 34 | 1 | ? | - | -| 35 | 78 | ? | - | -| 36 | 23 | ? | - | +| 32 | 16 | Setting: Wattage Heat-Element | Note: Multiplied by 100 -> 1600W, *Adjustable from HMI controller* | +| 33 - 34 | 14 1 | Setting: Boiler Capacity (l) | '14 1' == 270l, '200 0' == 200l, *Adjustable from HMI controller* | +| 35 | 78 | Setting: Brand | 65 == Atlantic, 78 == NoName, 83 == Sauter, 84 == Thermor, *Adjustable from HMI controller* | +| 36 | 21 | Setting: Bitflags | See Table below, *Adjustable from HMI controller*| ##### Byte No 17: Picture Bitmask +Findings... ``` -0dec | 0000 0000: Nothing Shown on HMI -8dec | 0000 1000: Fan is turned on (observed via testmode) -9dec | 0000 1001: Heating Element -10dec | 0000 1010: Heatpump -11dec | 0000 1011: Heatpump + Heating Element -14dec | 0000 1110: Heatpump + Boiler Backup -15dec | 0000 1111: Heatpump + Heating Element + Boiler Backup -32dec | 0010 0000: Defrost -34dec | 0010 0010: Defrost + Heatpump -40dec | 0010 1000: Defrost + Fan -41dec | 0010 1001: Defrost + Fan + Heating Element +0dec | 0000 0000: Nothing Shown on HMI +8dec | 0000 1000: Fan is turned on (observed via testmode) +9dec | 0000 1001: Heating Element +10dec | 0000 1010: Heatpump +11dec | 0000 1011: Heatpump + Heating Element +14dec | 0000 1110: Heatpump + Boiler Backup +15dec | 0000 1111: Heatpump + Heating Element + Boiler Backup +32dec | 0010 0000: Defrost +34dec | 0010 0010: Defrost + Heatpump +40dec | 0010 1000: Defrost + Fan +41dec | 0010 1001: Defrost + Fan + Heating Element +64dec | 0100 0000: Unknown, Observed while triggering Error 7 +192dec | 1100 0000: Unknown, Observed while triggering Error 7 ``` | Bit Number | Purpose/Function | Other Information | @@ -266,6 +397,29 @@ Example Table (Byte 19): | 6 | ? | | | 7 | ? | | +##### Byte No 36: Settings Bitmask + +Findings... +``` +5dec | 0000 0101: Communication Enabled, PV enabled, No Circulation, Anti-Trockenheizung +17dec | 0001 0001: Communication Enabled, PV disabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung +20dec | 0001 0100: Communication Enabled, PV enabled, Heat Exchanger not available, No Zirculation, No Anti-Trockenheizung +21dec | 0001 0101: Communication Enabled, PV enabled, No Circulation, No Anti-Trockenheizung +23dec | 0001 0111: Communication Enabled, PV enabled, With Circulation Enabled, No Anti-Trockenheizung +29dec | 0001 1101: Communication Disabled, PV enabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung +``` + +| Bit Number | Purpose/Function | Other Information | +|------------|-------------------------------|-------------------| +| 0 | Heat-Exchanger Available | | +| 1 | Circulation Enabled | | +| 2 | PV Input Enabled | If flag is set, pv input is enabled | +| 3 | Communication Disabled | If flag is set, io-homecontrol/cozytouch is disabled| +| 4 | Anti-Trockenheizung Disabled | If flag is set, there is no anti-trockenheizung | +| 5 | ? / Unused | | +| 6 | ? / Unused | | +| 7 | ? / Unused | | + #### 67 | Byte Number | Example | Purpose/Function | Other Information | From 185abb2030e4491574fe760d68137859783727fc Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Thu, 16 May 2024 17:56:29 +0200 Subject: [PATCH 03/12] feat(parsing): implemented settings provided from main --- AquaMQTT/include/message/MainStatusMessage.h | 28 +++++ AquaMQTT/include/message/MessageConstants.h | 31 ++++- AquaMQTT/include/mqtt/MQTTDefinitions.h | 19 +++ AquaMQTT/src/message/MainStatusMessage.cpp | 64 ++++++++++ AquaMQTT/src/task/MQTTTask.cpp | 117 +++++++++++++++++++ 5 files changed, 257 insertions(+), 2 deletions(-) diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index 9ab276a..6ba3681 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -4,6 +4,8 @@ #include #include +#include "MessageConstants.h" + namespace aquamqtt { namespace message @@ -37,6 +39,32 @@ class MainStatusMessage bool stateDefrost(); + uint8_t settingPwmFirst(); + + uint8_t settingPwmSecond(); + + uint8_t settingPwmThird(); + + uint8_t settingMinTTarget(); + + uint8_t settingLegionellaTTarget(); + + uint16_t settingWattageHeatingElement(); + + uint16_t settingBoilerCapacity(); + + MAINBrands settingBrand(); + + bool settingHasHeatExchanger(); + + bool settingHasCirculation(); + + bool settingHasPVInput(); + + bool settingHasCommunication(); + + bool settingHasAntiTrockenheizung(); + private: uint8_t* mData; }; diff --git a/AquaMQTT/include/message/MessageConstants.h b/AquaMQTT/include/message/MessageConstants.h index 3b49083..1f1c280 100644 --- a/AquaMQTT/include/message/MessageConstants.h +++ b/AquaMQTT/include/message/MessageConstants.h @@ -179,10 +179,11 @@ static const char* setupStr(HMISetup installation) } } -static void toHexStr(uint8_t* data, uint8_t data_len, char* buffer){ +static void toHexStr(uint8_t* data, uint8_t data_len, char* buffer) +{ const size_t num_bytes = data_len / sizeof(uint8_t); - //char hex_str[num_bytes * 2 + 1]; + // char hex_str[num_bytes * 2 + 1]; for (size_t i = 0; i < num_bytes; i++) { sprintf(&buffer[i * 2], "%02X", data[i]); @@ -190,6 +191,32 @@ static void toHexStr(uint8_t* data, uint8_t data_len, char* buffer){ buffer[num_bytes * 2] = '\0'; } +enum MAINBrands +{ + BR_ATLANTIC, + BR_NONAME, + BR_SAUTER, + BR_THERMOR, + BR_UNKNOWN +}; + +static const char* brandStr(MAINBrands brand) +{ + switch (brand) + { + case BR_ATLANTIC: + return reinterpret_cast(mqtt::ENUM_BRAND_ATLANTIC); + case BR_NONAME: + return reinterpret_cast(mqtt::ENUM_BRAND_NONAME); + case BR_SAUTER: + return reinterpret_cast(mqtt::ENUM_BRAND_SAUTER); + case BR_THERMOR: + return reinterpret_cast(mqtt::ENUM_BRAND_THERMOR); + default: + return reinterpret_cast(mqtt::ENUM_UNKNOWN); + } +} + } // namespace message } // namespace aquamqtt diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 663020c..285bb32 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -22,6 +22,11 @@ const char ENUM_UNKNOWN[] PROGMEM = { "UNKNOWN" }; const char ENUM_AQUAMQTT_MODE_LISTENER[] PROGMEM = { "LISTENER" }; const char ENUM_AQUAMQTT_MODE_MITM[] PROGMEM = { "MITM" }; +const char ENUM_BRAND_ATLANTIC[] PROGMEM = { "Atlantic" }; +const char ENUM_BRAND_THERMOR[] PROGMEM = { "Thermor" }; +const char ENUM_BRAND_SAUTER[] PROGMEM = { "Sauter" }; +const char ENUM_BRAND_NONAME[] PROGMEM = { "No Name" }; + const char ENUM_OPERATION_MODE_AUTO[] PROGMEM = { "AUTO" }; const char ENUM_OPERATION_MODE_ECO_OFF[] PROGMEM = { "MAN ECO OFF" }; const char ENUM_OPERATION_MODE_ECO_ON[] PROGMEM = { "MAN ECO ON" }; @@ -75,6 +80,20 @@ const char MAIN_STATE_HEAT_ELEMENT[] PROGMEM = { "stateElement" }; const char MAIN_STATE_EXT_BOILER[] PROGMEM = { "stateExtBoiler" }; const char MAIN_STATE_DEFROST[] PROGMEM = { "stateDefrost" }; +const char MAIN_SETTING_PWM_01[] PROGMEM = { "settingPWM_1" }; +const char MAIN_SETTING_PWM_02[] PROGMEM = { "settingPWM_2" }; +const char MAIN_SETTING_PWM_03[] PROGMEM = { "settingPWM_3" }; +const char MAIN_SETTING_MIN_TEMP_TARGET[] PROGMEM = { "settingMinTargetTemp" }; +const char MAIN_SETTING_MIN_TEMP_LEGIONELLA[] PROGMEM = { "settingMinLegionellaTemp" }; +const char MAIN_SETTING_PWR_HEATELEM[] PROGMEM = { "settingWattageElement" }; +const char MAIN_SETTING_BOILER_CAP[] PROGMEM = { "settingBoilerCapacity" }; +const char MAIN_SETTING_BOILER_BRAND[] PROGMEM = { "settingBoilerBrand" }; +const char MAIN_SETTING_HAS_HEAT_EXC[] PROGMEM = { "settingHasHeatExchanger" }; +const char MAIN_SETTING_HAS_CIRCULATION[] PROGMEM = { "settingHasCirculation" }; +const char MAIN_SETTING_HAS_PV_INPUT[] PROGMEM = { "settingHasPVInput" }; +const char MAIN_SETTING_HAS_EXT_COMM[] PROGMEM = { "settingHasExtCom" }; +const char MAIN_SETTING_HAS_ANTI_TRO[] PROGMEM = { "settingHasAntiTH" }; + const char HMI_HOT_WATER_TEMP_TARGET[] PROGMEM = { "waterTempTarget" }; const char HMI_OPERATION_MODE[] PROGMEM = { "operationMode" }; const char HMI_OPERATION_TYPE[] PROGMEM = { "operationType" }; diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index f107fb6..98dd270 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -45,5 +45,69 @@ bool MainStatusMessage::stateDefrost() { return mData[17] & 0x20; } +uint8_t MainStatusMessage::settingMinTTarget() +{ + return mData[20]; +} +uint8_t MainStatusMessage::settingPwmFirst() +{ + return mData[15]; +} +uint8_t MainStatusMessage::settingPwmSecond() +{ + return mData[16]; +} +uint8_t MainStatusMessage::settingPwmThird() +{ + return mData[14]; +} +uint8_t MainStatusMessage::settingLegionellaTTarget() +{ + return mData[21]; +} +uint16_t MainStatusMessage::settingWattageHeatingElement() +{ + return mData[32] * 100; +} +uint16_t MainStatusMessage::settingBoilerCapacity() +{ + return ((uint16_t) mData[34] << 8) | (uint16_t) mData[33]; +} +MAINBrands MainStatusMessage::settingBrand() +{ + switch (mData[35]) + { + case 65: + return MAINBrands::BR_ATLANTIC; + case 78: + return MAINBrands::BR_NONAME; + case 83: + return MAINBrands::BR_SAUTER; + case 84: + return MAINBrands::BR_THERMOR; + default: + return MAINBrands::BR_UNKNOWN; + } +} +bool MainStatusMessage::settingHasHeatExchanger() +{ + return mData[36] & 0x01; +} +bool MainStatusMessage::settingHasCirculation() +{ + return mData[36] & 0x02; +} +bool MainStatusMessage::settingHasPVInput() +{ + return mData[36] & 0x04; +} +bool MainStatusMessage::settingHasCommunication() +{ + return !(mData[36] & 0x08); +} +bool MainStatusMessage::settingHasAntiTrockenheizung() +{ + return !(mData[36] & 0x20); } +} // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index 59e9c5f..376c8dc 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -669,6 +669,123 @@ void MQTTTask::updateMainStatus() toHexStr(mTransferBuffer, MAIN_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); } + + itoa(message.settingPwmFirst(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_PWM_01); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingPwmSecond(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_PWM_02); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingPwmThird(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_PWM_03); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingMinTTarget(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_MIN_TEMP_TARGET); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingLegionellaTTarget(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_MIN_TEMP_LEGIONELLA); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + ultoa(message.settingWattageHeatingElement(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_PWR_HEATELEM); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + ultoa(message.settingBoilerCapacity(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_BOILER_CAP); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_BOILER_BRAND); + + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), brandStr(message.settingBrand())); + + itoa(message.settingHasHeatExchanger(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_HAS_HEAT_EXC); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingHasCirculation(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_HAS_CIRCULATION); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingHasPVInput(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_HAS_PV_INPUT); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingHasCommunication(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_HAS_EXT_COMM); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + itoa(message.settingHasAntiTrockenheizung(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + MAIN_SUBTOPIC, + MAIN_SETTING_HAS_ANTI_TRO); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); } #pragma clang diagnostic push From 1e1b6685d0e8e4211ac6f8998d1ac7d264d5c14a Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Sat, 18 May 2024 09:06:51 +0200 Subject: [PATCH 04/12] feat(mqtt): publish mqtt messages only on change --- AquaMQTT/include/message/HMIMessage.h | 36 + AquaMQTT/include/message/MainEnergyMessage.h | 28 +- AquaMQTT/include/message/MainStatusMessage.h | 51 +- AquaMQTT/include/message/MessageConstants.h | 17 + AquaMQTT/include/task/MQTTTask.h | 13 +- AquaMQTT/src/message/HMIMessage.cpp | 131 ++- AquaMQTT/src/message/MainEnergyMessage.cpp | 111 +++ AquaMQTT/src/message/MainStatusMessage.cpp | 171 +++- AquaMQTT/src/task/MQTTTask.cpp | 858 +++++++------------ 9 files changed, 838 insertions(+), 578 deletions(-) diff --git a/AquaMQTT/include/message/HMIMessage.h b/AquaMQTT/include/message/HMIMessage.h index b324861..2e392f9 100644 --- a/AquaMQTT/include/message/HMIMessage.h +++ b/AquaMQTT/include/message/HMIMessage.h @@ -84,7 +84,43 @@ class HMIMessage void setInstallationMode(HMIInstallation mode); + bool waterTempTargetChanged() const; + + bool operationTypeOrModeChanged() const; + + bool timeChanged() const; + + bool dateChanged() const; + + bool emergencyModeChanged() const; + + bool heatingElemOrSetupStateChanged() const; + + bool legionellaOrAirductChanged() const; + + bool testModeChanged() const; + + bool installationConfigChanged() const; + + bool timerModeOneChanged() const; + + bool timerModeTwoChanged() const; + + void compareWith(uint8_t* data); + private: + bool mTargetTempChanged; + bool mOperationModeChanged; + bool mLegionellaAirductChanged; + bool mEmergencyModeChanged; + bool mInstallConfigChanged; + bool mHeatingElemOrSetupStateChanged; + bool mTimerModeOneChanged; + bool mTimerModeTwoChanged; + bool mTimeChanged; + bool mDateChanged; + bool mTestModeChanged; + uint8_t* mData; }; diff --git a/AquaMQTT/include/message/MainEnergyMessage.h b/AquaMQTT/include/message/MainEnergyMessage.h index d99d817..a159dba 100644 --- a/AquaMQTT/include/message/MainEnergyMessage.h +++ b/AquaMQTT/include/message/MainEnergyMessage.h @@ -10,9 +10,7 @@ namespace message class MainEnergyMessage { public: - explicit MainEnergyMessage(uint8_t* data) : mData(data) - { - } + explicit MainEnergyMessage(uint8_t* data); ~MainEnergyMessage() = default; @@ -30,8 +28,32 @@ class MainEnergyMessage uint16_t powerOverall(); + void compareWith(uint8_t* data); + + bool totalHeatpumpHoursChanged() const; + + bool totalHeatingElemHoursChanged() const; + + bool totalHoursChanged() const; + + bool totalEnergyCounterChanged() const; + + bool powerHeatpumpChanged() const; + + bool powerHeatElementChanged() const; + + bool powerOverallChanged() const; + private: uint8_t* mData; + + bool mPowerHeatpumpChanged; + bool mPowerHeatelementChanged; + bool mPowerOverallChanged; + bool mTotalHeatpumpHoursChanged; + bool mTotalHeatElementHoursChanged; + bool mTotalHoursChanged; + bool mTotalEnergyChanged; }; } // namespace message diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index 6ba3681..ff2802f 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -13,9 +13,7 @@ namespace message class MainStatusMessage { public: - explicit MainStatusMessage(uint8_t* data) : mData(data) - { - } + explicit MainStatusMessage(uint8_t* data); ~MainStatusMessage() = default; @@ -65,8 +63,55 @@ class MainStatusMessage bool settingHasAntiTrockenheizung(); + void compareWith(uint8_t* data); + + bool hotWaterTempChanged() const; + + bool airTempChanged() const; + + bool evaporatorLowerAirTempChanged() const; + + bool evaporatorUpperAirTempChanged() const; + + bool fanSpeedChanged() const; + + bool statesChanged() const; + + bool settingPwmFirstChanged() const; + + bool settingPwmSecondChanged() const; + + bool settingPwmThirdChanged() const; + + bool settingMinTTargetChanged() const; + + bool settingLegionellaTTargetChanged() const; + + bool settingWattageHeatingElementChanged() const; + + bool settingBoilerCapacityChanged() const; + + bool settingBrandChanged() const; + + bool settingCapabilitiesChanged() const; + private: uint8_t* mData; + bool mHotWaterTempChanged; + bool mAirTempChanged; + bool mEvaporatorLowerChanged; + bool mEvaporatorUpperChanged; + bool mFanSpeedChanged; + bool mStatesChanged; + bool mSettingMinTChanged; + bool mSettingPwmFirstChanged; + bool mSettingPwmSecondChanged; + bool mSettingPwmThirdChanged; + bool mSettingAntiLegionellaTargetChanged; + bool mSettingWattageHeatElementChanged; + bool mSettingBoilerCapacityChanged; + bool mSettingBoilerBrandChanged; + bool mSettingCapabilitiesChanged; }; } // namespace message diff --git a/AquaMQTT/include/message/MessageConstants.h b/AquaMQTT/include/message/MessageConstants.h index 1f1c280..7b63bcc 100644 --- a/AquaMQTT/include/message/MessageConstants.h +++ b/AquaMQTT/include/message/MessageConstants.h @@ -217,6 +217,23 @@ static const char* brandStr(MAINBrands brand) } } +void static compareBuffers( + const uint8_t* buffer1, + const uint8_t* buffer2, + size_t length, + uint8_t* diffIndices, + size_t* numDiffs) +{ + *numDiffs = 0; + for (size_t i = 0; i < length; i++) + { + if (buffer1[i] != buffer2[i]) + { + diffIndices[(*numDiffs)++] = i; + } + } +} + } // namespace message } // namespace aquamqtt diff --git a/AquaMQTT/include/task/MQTTTask.h b/AquaMQTT/include/task/MQTTTask.h index 0845615..c5b73b4 100644 --- a/AquaMQTT/include/task/MQTTTask.h +++ b/AquaMQTT/include/task/MQTTTask.h @@ -17,7 +17,7 @@ class MQTTTask public: MQTTTask(); - virtual ~MQTTTask() = default; + virtual ~MQTTTask(); void spawn(); @@ -45,6 +45,17 @@ class MQTTTask WiFiClient mWiFiClient; MQTTClient mMQTTClient; TaskHandle_t mTaskHandle; + + uint8_t* mLastProcessedHMIMessage; + uint8_t* mLastProcessedEnergyMessage; + uint8_t* mLastProcessedMainMessage; + + // helper to avoid code duplication + void publishFloat(const char* subtopic, const char* topic, float value, bool retained = false); + void publishString(const char* subtopic, const char* topic, const char* value, bool retained = false); + void publishi(const char* subtopic, const char* topic, int value, bool retained = false); + void publishul(const char* subtopic, const char* topic, unsigned long value, bool retained = false); + void publishul(const char* subtopic_1, const char* subtopic_2, const char* topic, unsigned long value, bool retained = false); }; } // namespace aquamqtt diff --git a/AquaMQTT/src/message/HMIMessage.cpp b/AquaMQTT/src/message/HMIMessage.cpp index 94834ec..cbd6913 100644 --- a/AquaMQTT/src/message/HMIMessage.cpp +++ b/AquaMQTT/src/message/HMIMessage.cpp @@ -7,7 +7,19 @@ namespace aquamqtt namespace message { -HMIMessage::HMIMessage(uint8_t* data) : mData(data) +HMIMessage::HMIMessage(uint8_t* data) + : mData(data) + , mTargetTempChanged(false) + , mOperationModeChanged(false) + , mLegionellaAirductChanged(false) + , mEmergencyModeChanged(false) + , mInstallConfigChanged(false) + , mHeatingElemOrSetupStateChanged(false) + , mTimerModeOneChanged(false) + , mTimerModeTwoChanged(false) + , mTimeChanged(false) + , mDateChanged(false) + , mTestModeChanged(false) { } float HMIMessage::waterTempTarget() @@ -324,5 +336,122 @@ void HMIMessage::setAntiLegionellaModePerMonth(uint8_t value) { } +void HMIMessage::compareWith(uint8_t* data) +{ + if (data == nullptr) + { + mTargetTempChanged = true; + mOperationModeChanged = true; + mLegionellaAirductChanged = true; + mEmergencyModeChanged = true; + mInstallConfigChanged = true; + mHeatingElemOrSetupStateChanged = true; + mTimerModeOneChanged = true; + mTimerModeTwoChanged = true; + mTimeChanged = true; + mDateChanged = true; + mTestModeChanged = true; + return; + } + + uint8_t diffIndices[HMI_MESSAGE_LENGTH] = { 0 }; + size_t numDiffs = 0; + compareBuffers(mData, data, HMI_MESSAGE_LENGTH, diffIndices, &numDiffs); + + for (int i = 0; i < numDiffs; ++i) + { + auto indiceChanged = diffIndices[i]; + + switch (indiceChanged) + { + case 1: + case 2: + mTargetTempChanged = true; + break; + case 3: + mOperationModeChanged = true; + break; + case 5: + mLegionellaAirductChanged = true; + break; + case 6: + mEmergencyModeChanged = true; + break; + case 7: + mInstallConfigChanged = true; + break; + case 9: + mHeatingElemOrSetupStateChanged = true; + break; + case 10: + case 11: + mTimerModeOneChanged = true; + break; + case 12: + case 13: + mTimerModeTwoChanged = true; + break; + case 17: + case 20: + case 21: + mTimeChanged = true; + break; + case 18: + case 19: + mDateChanged = true; + break; + case 22: + mTestModeChanged = true; + break; + default: + break; + } + } +} +bool HMIMessage::waterTempTargetChanged() const +{ + return mTargetTempChanged; +} +bool HMIMessage::operationTypeOrModeChanged() const +{ + return mOperationModeChanged; +} +bool HMIMessage::timeChanged() const +{ + return mTimeChanged; +} +bool HMIMessage::dateChanged() const +{ + return mDateChanged; +} +bool HMIMessage::emergencyModeChanged() const +{ + return mEmergencyModeChanged; +} +bool HMIMessage::heatingElemOrSetupStateChanged() const +{ + return mHeatingElemOrSetupStateChanged; +} +bool HMIMessage::legionellaOrAirductChanged() const +{ + return mLegionellaAirductChanged; +} +bool HMIMessage::testModeChanged() const +{ + return mTestModeChanged; +} +bool HMIMessage::installationConfigChanged() const +{ + return mInstallConfigChanged; +} +bool HMIMessage::timerModeOneChanged() const +{ + return mTimerModeOneChanged; +} +bool HMIMessage::timerModeTwoChanged() const +{ + return mTimerModeTwoChanged; +} + } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/message/MainEnergyMessage.cpp b/AquaMQTT/src/message/MainEnergyMessage.cpp index 0fefde0..bd7b9d5 100644 --- a/AquaMQTT/src/message/MainEnergyMessage.cpp +++ b/AquaMQTT/src/message/MainEnergyMessage.cpp @@ -1,5 +1,7 @@ #include "message/MainEnergyMessage.h" +#include "message/MessageConstants.h" + namespace aquamqtt { namespace message @@ -38,5 +40,114 @@ uint16_t MainEnergyMessage::powerOverall() { return ((uint16_t) mData[8] << 8) | (uint16_t) mData[7]; } +void MainEnergyMessage::compareWith(uint8_t* data) +{ + if (data == nullptr) + { + mPowerHeatpumpChanged = true; + mPowerHeatelementChanged = true; + mPowerOverallChanged = true; + mTotalHeatpumpHoursChanged = true; + mTotalHeatElementHoursChanged = true; + mTotalHoursChanged = true; + mTotalEnergyChanged = true; + return; + } + + uint8_t diffIndices[ENERGY_MESSAGE_LENGTH] = { 0 }; + size_t numDiffs = 0; + compareBuffers(mData, data, ENERGY_MESSAGE_LENGTH, diffIndices, &numDiffs); + + for (int i = 0; i < numDiffs; ++i) + { + auto indiceChanged = diffIndices[i]; + + switch (indiceChanged) + { + case 1: + case 2: + mPowerHeatpumpChanged = true; + break; + case 3: + case 4: + mPowerHeatelementChanged = true; + break; + case 7: + case 8: + mPowerOverallChanged = true; + break; + case 11: + case 12: + case 13: + case 14: + mTotalHeatpumpHoursChanged = true; + break; + case 15: + case 16: + case 17: + case 18: + mTotalHeatElementHoursChanged = true; + break; + case 19: + case 20: + case 21: + case 22: + mTotalHoursChanged = true; + break; + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + mTotalEnergyChanged = true; + break; + default: + break; + } + } +} +MainEnergyMessage::MainEnergyMessage(uint8_t* data) + : mData(data) + , mPowerHeatpumpChanged(false) + , mPowerHeatelementChanged(false) + , mPowerOverallChanged(false) + , mTotalHeatpumpHoursChanged(false) + , mTotalHeatElementHoursChanged(false) + , mTotalHoursChanged(false) + , mTotalEnergyChanged(false) +{ +} +bool MainEnergyMessage::totalHeatpumpHoursChanged() const +{ + return mTotalHeatpumpHoursChanged; +} +bool MainEnergyMessage::totalHeatingElemHoursChanged() const +{ + return mTotalHeatElementHoursChanged; +} +bool MainEnergyMessage::totalHoursChanged() const +{ + return mTotalHoursChanged; +} +bool MainEnergyMessage::totalEnergyCounterChanged() const +{ + return mTotalEnergyChanged; +} +bool MainEnergyMessage::powerHeatpumpChanged() const +{ + return mPowerHeatpumpChanged; +} +bool MainEnergyMessage::powerHeatElementChanged() const +{ + return mPowerHeatelementChanged; +} +bool MainEnergyMessage::powerOverallChanged() const +{ + return mPowerOverallChanged; +} + } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index 98dd270..7e558cb 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -89,6 +89,8 @@ MAINBrands MainStatusMessage::settingBrand() return MAINBrands::BR_UNKNOWN; } } + +// TODO: rename this thing to capabilities bool MainStatusMessage::settingHasHeatExchanger() { return mData[36] & 0x01; @@ -107,7 +109,174 @@ bool MainStatusMessage::settingHasCommunication() } bool MainStatusMessage::settingHasAntiTrockenheizung() { - return !(mData[36] & 0x20); + return (mData[36] & 0x20); +} +void MainStatusMessage::compareWith(uint8_t* data) +{ + if (data == nullptr) + { + mHotWaterTempChanged = true; + mAirTempChanged = true; + mEvaporatorLowerChanged = true; + mEvaporatorUpperChanged = true; + mFanSpeedChanged = true; + mStatesChanged = true; + mSettingMinTChanged = true; + mSettingPwmFirstChanged = true; + mSettingPwmSecondChanged = true; + mSettingPwmThirdChanged = true; + mSettingAntiLegionellaTargetChanged = true; + mSettingWattageHeatElementChanged = true; + mSettingBoilerCapacityChanged = true; + mSettingBoilerBrandChanged = true; + mSettingCapabilitiesChanged = true; + return; + } + + uint8_t diffIndices[MAIN_MESSAGE_LENGTH] = { 0 }; + size_t numDiffs = 0; + compareBuffers(mData, data, MAIN_MESSAGE_LENGTH, diffIndices, &numDiffs); + + for (int i = 0; i < numDiffs; ++i) + { + auto indiceChanged = diffIndices[i]; + + switch (indiceChanged) + { + case 1: + case 2: + mHotWaterTempChanged = true; + break; + case 3: + case 4: + mAirTempChanged = true; + break; + case 5: + case 6: + mEvaporatorLowerChanged = true; + break; + case 7: + case 8: + mEvaporatorUpperChanged = true; + break; + case 14: + mSettingPwmThirdChanged = true; + break; + case 15: + mSettingPwmFirstChanged = true; + break; + case 16: + mSettingPwmSecondChanged = true; + break; + case 17: + mStatesChanged = true; + break; + case 18: + case 19: + mFanSpeedChanged = true; + break; + case 20: + mSettingMinTChanged = true; + break; + case 21: + mSettingAntiLegionellaTargetChanged = true; + break; + case 32: + mSettingWattageHeatElementChanged = true; + break; + case 33: + case 34: + mSettingBoilerCapacityChanged = true; + break; + case 35: + mSettingBoilerBrandChanged = true; + break; + case 36: + mSettingCapabilitiesChanged = true; + break; + default: + break; + } + } +} +MainStatusMessage::MainStatusMessage(uint8_t* data) + : mData(data) + , mHotWaterTempChanged(false) + , mAirTempChanged(false) + , mEvaporatorLowerChanged(false) + , mEvaporatorUpperChanged(false) + , mFanSpeedChanged(false) + , mStatesChanged(false) + , mSettingMinTChanged(false) + , mSettingPwmFirstChanged(false) + , mSettingPwmSecondChanged(false) + , mSettingPwmThirdChanged(false) + , mSettingAntiLegionellaTargetChanged(false) + , mSettingWattageHeatElementChanged(false) + , mSettingBoilerCapacityChanged(false) + , mSettingBoilerBrandChanged(false) + , mSettingCapabilitiesChanged(false) +{ +} +bool MainStatusMessage::hotWaterTempChanged() const +{ + return mHotWaterTempChanged; +} +bool MainStatusMessage::airTempChanged() const +{ + return mAirTempChanged; +} +bool MainStatusMessage::evaporatorLowerAirTempChanged() const +{ + return mEvaporatorLowerChanged; +} +bool MainStatusMessage::evaporatorUpperAirTempChanged() const +{ + return mEvaporatorUpperChanged; +} +bool MainStatusMessage::fanSpeedChanged() const +{ + return mFanSpeedChanged; +} +bool MainStatusMessage::statesChanged() const +{ + return mStatesChanged; +} +bool MainStatusMessage::settingPwmFirstChanged() const +{ + return mSettingPwmFirstChanged; +} +bool MainStatusMessage::settingPwmSecondChanged() const +{ + return mSettingPwmSecondChanged; +} +bool MainStatusMessage::settingPwmThirdChanged() const +{ + return mSettingPwmThirdChanged; +} +bool MainStatusMessage::settingMinTTargetChanged() const +{ + return mSettingMinTChanged; +} +bool MainStatusMessage::settingLegionellaTTargetChanged() const +{ + return mSettingAntiLegionellaTargetChanged; +} +bool MainStatusMessage::settingWattageHeatingElementChanged() const +{ + return mSettingWattageHeatElementChanged; +} +bool MainStatusMessage::settingBoilerCapacityChanged() const +{ + return mSettingBoilerCapacityChanged; +} +bool MainStatusMessage::settingBrandChanged() const +{ + return mSettingBoilerBrandChanged; +} +bool MainStatusMessage::settingCapabilitiesChanged() const +{ + return mSettingCapabilitiesChanged; } } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index 376c8dc..223e7af 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -20,9 +20,19 @@ MQTTTask::MQTTTask() , mTaskHandle(nullptr) , mTopicBuffer{ 0 } , mPayloadBuffer{ 0 } + , mLastProcessedHMIMessage(nullptr) + , mLastProcessedEnergyMessage(nullptr) + , mLastProcessedMainMessage(nullptr) { } +MQTTTask::~MQTTTask() +{ + delete mLastProcessedHMIMessage; + delete mLastProcessedEnergyMessage; + delete mLastProcessedMainMessage; +} + void MQTTTask::messageReceived(String& topic, String& payload) { Serial.println("incoming: " + topic + " - " + payload); @@ -192,7 +202,7 @@ void MQTTTask::messageReceived(String& topic, String& payload) void MQTTTask::spawn() { - xTaskCreatePinnedToCore(MQTTTask::innerTask, "mqttTaskLoop", 9000, this, 3, &mTaskHandle, 1); + xTaskCreatePinnedToCore(MQTTTask::innerTask, "mqttTaskLoop", 9000, this, 3, &mTaskHandle, 0); esp_task_wdt_add(mTaskHandle); DHWState::getInstance().setListener(mTaskHandle); HMIStateProxy::getInstance().setListener(mTaskHandle); @@ -288,6 +298,12 @@ void MQTTTask::loop() if (HMIStateProxy::getInstance().copyFrame(aquamqtt::message::HMI_MESSAGE_IDENTIFIER, mTransferBuffer)) { updateHMIStatus(); + + if (mLastProcessedHMIMessage == nullptr) + { + mLastProcessedHMIMessage = new uint8_t[aquamqtt::message::HMI_MESSAGE_LENGTH]; + } + memcpy(mLastProcessedHMIMessage, mTransferBuffer, aquamqtt::message::HMI_MESSAGE_LENGTH); } } @@ -296,6 +312,12 @@ void MQTTTask::loop() if (DHWState::getInstance().copyFrame(aquamqtt::message::MAIN_MESSAGE_IDENTIFIER, mTransferBuffer)) { updateMainStatus(); + + if (mLastProcessedMainMessage == nullptr) + { + mLastProcessedMainMessage = new uint8_t[aquamqtt::message::MAIN_MESSAGE_LENGTH]; + } + memcpy(mLastProcessedMainMessage, mTransferBuffer, aquamqtt::message::MAIN_MESSAGE_LENGTH); } } @@ -304,6 +326,12 @@ void MQTTTask::loop() if (DHWState::getInstance().copyFrame(aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER, mTransferBuffer)) { updateEnergyStats(); + + if (mLastProcessedEnergyMessage == nullptr) + { + mLastProcessedEnergyMessage = new uint8_t[aquamqtt::message::ENERGY_MESSAGE_LENGTH]; + } + memcpy(mLastProcessedEnergyMessage, mTransferBuffer, aquamqtt::message::ENERGY_MESSAGE_LENGTH); } } @@ -327,180 +355,39 @@ void MQTTTask::loop() #pragma clang diagnostic ignored "-Wformat" void MQTTTask::updateStats() { - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, + publishString( STATS_SUBTOPIC, - STATS_AQUAMQTT_MODE); - sprintf(reinterpret_cast(mPayloadBuffer), - "%S", + STATS_AQUAMQTT_MODE, config::OPERATION_MODE == config::EOperationMode::LISTENER ? ENUM_AQUAMQTT_MODE_LISTENER : ENUM_AQUAMQTT_MODE_MITM); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_AQUAMQTT_ADDR); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), WiFi.localIP().toString()); + publishString(STATS_SUBTOPIC, STATS_AQUAMQTT_ADDR, WiFi.localIP().toString().c_str()); - itoa(WiFi.RSSI(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_AQUAMQTT_RSSI); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishi(STATS_SUBTOPIC, STATS_AQUAMQTT_RSSI, WiFi.RSSI()); if (config::OPERATION_MODE == config::EOperationMode::LISTENER) { auto listenerStats = DHWState::getInstance().getFrameBufferStatistics(0); - - ultoa(listenerStats.msgHandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_MSG_HANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(listenerStats.msgUnhandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_MSG_UNHANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(listenerStats.msgCRCFail, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_MSG_CRC_NOK); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(listenerStats.droppedBytes, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - STATS_DROPPED_BYTES); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishul(STATS_SUBTOPIC, STATS_MSG_HANDLED, listenerStats.msgHandled); + publishul(STATS_SUBTOPIC, STATS_MSG_UNHANDLED, listenerStats.msgUnhandled); + publishul(STATS_SUBTOPIC, STATS_MSG_CRC_NOK, listenerStats.msgCRCFail); + publishul(STATS_SUBTOPIC, STATS_DROPPED_BYTES, listenerStats.droppedBytes); } else { auto hmiStats = DHWState::getInstance().getFrameBufferStatistics(1); - - ultoa(hmiStats.msgHandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - HMI_SUBTOPIC, - STATS_MSG_HANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(hmiStats.msgUnhandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - HMI_SUBTOPIC, - STATS_MSG_UNHANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(hmiStats.msgCRCFail, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - HMI_SUBTOPIC, - STATS_MSG_CRC_NOK); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(hmiStats.droppedBytes, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - HMI_SUBTOPIC, - STATS_DROPPED_BYTES); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(hmiStats.msgSent, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - HMI_SUBTOPIC, - STATS_MSG_SENT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishul(STATS_SUBTOPIC, HMI_SUBTOPIC, STATS_MSG_HANDLED, hmiStats.msgHandled); + publishul(STATS_SUBTOPIC, HMI_SUBTOPIC, STATS_MSG_UNHANDLED, hmiStats.msgUnhandled); + publishul(STATS_SUBTOPIC, HMI_SUBTOPIC, STATS_MSG_CRC_NOK, hmiStats.msgCRCFail); + publishul(STATS_SUBTOPIC, HMI_SUBTOPIC, STATS_DROPPED_BYTES, hmiStats.droppedBytes); + publishul(STATS_SUBTOPIC, HMI_SUBTOPIC, STATS_MSG_SENT, hmiStats.msgSent); auto mainStats = DHWState::getInstance().getFrameBufferStatistics(2); - - ultoa(mainStats.msgHandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - MAIN_SUBTOPIC, - STATS_MSG_HANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(mainStats.msgUnhandled, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - MAIN_SUBTOPIC, - STATS_MSG_UNHANDLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(mainStats.msgCRCFail, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - MAIN_SUBTOPIC, - STATS_MSG_CRC_NOK); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(mainStats.droppedBytes, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - MAIN_SUBTOPIC, - STATS_DROPPED_BYTES); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(mainStats.msgSent, reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - STATS_SUBTOPIC, - MAIN_SUBTOPIC, - STATS_MSG_SENT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishul(STATS_SUBTOPIC, MAIN_SUBTOPIC, STATS_MSG_HANDLED, mainStats.msgHandled); + publishul(STATS_SUBTOPIC, MAIN_SUBTOPIC, STATS_MSG_UNHANDLED, mainStats.msgUnhandled); + publishul(STATS_SUBTOPIC, MAIN_SUBTOPIC, STATS_MSG_CRC_NOK, mainStats.msgCRCFail); + publishul(STATS_SUBTOPIC, MAIN_SUBTOPIC, STATS_DROPPED_BYTES, mainStats.droppedBytes); + publishul(STATS_SUBTOPIC, MAIN_SUBTOPIC, STATS_MSG_SENT, mainStats.msgSent); auto overrides = HMIStateProxy::getInstance().getOverrides(); @@ -529,34 +416,18 @@ void MQTTTask::updateStats() STATS_ACTIVE_OVERRIDES); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, + publishString( STATS_SUBTOPIC, - STATS_AQUAMQTT_OVERRIDE_MODE); - - mMQTTClient.publish( - reinterpret_cast(mTopicBuffer), + STATS_AQUAMQTT_OVERRIDE_MODE, aquamqttOverrideStr(HMIStateProxy::getInstance().getOverrideMode())); - - itoa(HMIStateProxy::getInstance().isPVModeHeatPumpEnabled(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, + publishi( STATS_SUBTOPIC, - STATS_ENABLE_FLAG_PV_HEATPUMP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(HMIStateProxy::getInstance().isPVModeHeatElementEnabled(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, + STATS_ENABLE_FLAG_PV_HEATPUMP, + HMIStateProxy::getInstance().isPVModeHeatPumpEnabled()); + publishi( STATS_SUBTOPIC, - STATS_ENABLE_FLAG_PV_HEATELEMENT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + STATS_ENABLE_FLAG_PV_HEATELEMENT, + HMIStateProxy::getInstance().isPVModeHeatElementEnabled()); } } #pragma clang diagnostic pop @@ -566,96 +437,76 @@ void MQTTTask::updateStats() void MQTTTask::updateMainStatus() { message::MainStatusMessage message(mTransferBuffer); + message.compareWith(mLastProcessedMainMessage); - dtostrf(message.hotWaterTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_HOT_WATER_TEMP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - dtostrf(message.airTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SUPPLY_AIR_TEMP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - dtostrf(message.evaporatorLowerAirTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_EVAPORATOR_AIR_TEMP_LOWER); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - dtostrf(message.evaporatorUpperAirTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_EVAPORATOR_AIR_TEMP_UPPER); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(message.fanSpeed(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_FAN_SPEED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.stateHeatingElement(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_STATE_HEAT_ELEMENT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.stateHeatpump(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_STATE_HEATPUMP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.stateBoilerBackup(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_STATE_EXT_BOILER); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.stateFan(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_STATE_FAN); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.stateDefrost(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_STATE_DEFROST); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.hotWaterTempChanged()) + { + publishFloat(MAIN_SUBTOPIC, MAIN_HOT_WATER_TEMP, message.hotWaterTemp()); + } + if (message.airTempChanged()) + { + publishFloat(MAIN_SUBTOPIC, MAIN_SUPPLY_AIR_TEMP, message.airTemp()); + } + if (message.evaporatorLowerAirTempChanged()) + { + publishFloat(MAIN_SUBTOPIC, MAIN_EVAPORATOR_AIR_TEMP_LOWER, message.evaporatorLowerAirTemp()); + } + if (message.evaporatorUpperAirTempChanged()) + { + publishFloat(MAIN_SUBTOPIC, MAIN_EVAPORATOR_AIR_TEMP_UPPER, message.evaporatorUpperAirTemp()); + } + if (message.fanSpeedChanged()) + { + publishul(MAIN_SUBTOPIC, MAIN_FAN_SPEED, message.fanSpeed()); + } + if (message.statesChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_STATE_HEAT_ELEMENT, message.stateHeatingElement()); + publishi(MAIN_SUBTOPIC, MAIN_STATE_HEATPUMP, message.stateHeatpump()); + publishi(MAIN_SUBTOPIC, MAIN_STATE_EXT_BOILER, message.stateBoilerBackup()); + publishi(MAIN_SUBTOPIC, MAIN_STATE_FAN, message.stateFan()); + publishi(MAIN_SUBTOPIC, MAIN_STATE_DEFROST, message.stateDefrost()); + } + if (message.settingPwmFirstChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_PWM_01, message.settingPwmFirst()); + } + if (message.settingPwmSecondChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_PWM_02, message.settingPwmSecond()); + } + if (message.settingPwmThirdChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_PWM_03, message.settingPwmThird()); + } + if (message.settingMinTTargetChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_MIN_TEMP_TARGET, message.settingMinTTarget()); + } + if (message.settingLegionellaTTargetChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_MIN_TEMP_LEGIONELLA, message.settingLegionellaTTarget()); + } + if (message.settingWattageHeatingElementChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_PWR_HEATELEM, message.settingWattageHeatingElement()); + } + if (message.settingBoilerCapacityChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_BOILER_CAP, message.settingBoilerCapacity()); + } + if (message.settingBrandChanged()) + { + publishString(MAIN_SUBTOPIC, MAIN_SETTING_BOILER_BRAND, brandStr(message.settingBrand())); + } + if (message.settingCapabilitiesChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_HEAT_EXC, message.settingHasHeatExchanger()); + publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_CIRCULATION, message.settingHasCirculation()); + publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_PV_INPUT, message.settingHasPVInput()); + publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_EXT_COMM, message.settingHasCommunication()); + publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_ANTI_TRO, message.settingHasAntiTrockenheizung()); + } if (config::DEBUG_RAW_SERIAL_MESSAGES) { @@ -669,123 +520,6 @@ void MQTTTask::updateMainStatus() toHexStr(mTransferBuffer, MAIN_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); } - - itoa(message.settingPwmFirst(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_PWM_01); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingPwmSecond(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_PWM_02); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingPwmThird(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_PWM_03); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingMinTTarget(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_MIN_TEMP_TARGET); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingLegionellaTTarget(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_MIN_TEMP_LEGIONELLA); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(message.settingWattageHeatingElement(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_PWR_HEATELEM); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - ultoa(message.settingBoilerCapacity(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_BOILER_CAP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_BOILER_BRAND); - - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), brandStr(message.settingBrand())); - - itoa(message.settingHasHeatExchanger(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_HAS_HEAT_EXC); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingHasCirculation(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_HAS_CIRCULATION); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingHasPVInput(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_HAS_PV_INPUT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingHasCommunication(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_HAS_EXT_COMM); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.settingHasAntiTrockenheizung(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - MAIN_SUBTOPIC, - MAIN_SETTING_HAS_ANTI_TRO); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); } #pragma clang diagnostic push @@ -793,129 +527,102 @@ void MQTTTask::updateMainStatus() void MQTTTask::updateHMIStatus() { message::HMIMessage message(mTransferBuffer); + message.compareWith(mLastProcessedHMIMessage); - dtostrf(message.waterTempTarget(), 3, 1, reinterpret_cast(mPayloadBuffer)); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_HOT_WATER_TEMP_TARGET); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.waterTempTargetChanged()) + { + publishFloat(HMI_SUBTOPIC, HMI_HOT_WATER_TEMP_TARGET, message.waterTempTarget()); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_OPERATION_MODE); - mMQTTClient.publish( - reinterpret_cast(mTopicBuffer), - aquamqtt::message::operationModeStr(message.operationMode())); + if (message.operationTypeOrModeChanged()) + { + publishString(HMI_SUBTOPIC, HMI_OPERATION_MODE, operationModeStr(message.operationMode())); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_OPERATION_TYPE); - - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), operationTypeStr(message.getOperationType())); - - sprintf(reinterpret_cast(mPayloadBuffer), - "%02d:%02d:%02d", - message.timeHours(), - message.timeMinutes(), - message.timeSeconds()); - sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, HMI_SUBTOPIC, HMI_TIME); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - sprintf(reinterpret_cast(mPayloadBuffer), - "%d.%d.%d", - message.dateDay(), - message.dateMonth(), - message.dateYear()); - sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, HMI_SUBTOPIC, HMI_DATE); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - itoa(message.isEmergencyModeEnabled(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_EMERGENCY_MODE); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishString(HMI_SUBTOPIC, HMI_OPERATION_TYPE, operationTypeStr(message.getOperationType())); + } - itoa(message.isHeatingElementEnabled(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_HEATING_ELEMENT_ENABLED); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.timeChanged()) + { + sprintf(reinterpret_cast(mPayloadBuffer), + "%02d:%02d:%02d", + message.timeHours(), + message.timeMinutes(), + message.timeSeconds()); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + HMI_SUBTOPIC, + HMI_TIME); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } - itoa(message.antiLegionellaModePerMonth(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_LEGIONELLA); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.dateChanged()) + { + sprintf(reinterpret_cast(mPayloadBuffer), + "%d.%d.%d", + message.dateDay(), + message.dateMonth(), + message.dateYear()); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + HMI_SUBTOPIC, + HMI_DATE); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_AIR_DUCT_CONFIG); - mMQTTClient.publish( - reinterpret_cast(mTopicBuffer), - aquamqtt::message::airDuctConfigStr(message.airDuctConfig())); + if (message.emergencyModeChanged()) + { + publishi(HMI_SUBTOPIC, HMI_EMERGENCY_MODE, message.isEmergencyModeEnabled()); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_TEST_MODE); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), testModeStr(message.testMode())); + if (message.heatingElemOrSetupStateChanged()) + { + publishi(HMI_SUBTOPIC, HMI_HEATING_ELEMENT_ENABLED, message.isHeatingElementEnabled()); + publishString(HMI_SUBTOPIC, HMI_SETUP_STATE, setupStr(message.setupMode())); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_INSTALLATION_CONFIG); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), installationModeStr(message.installationMode())); + if (message.legionellaOrAirductChanged()) + { + publishi(HMI_SUBTOPIC, HMI_LEGIONELLA, message.antiLegionellaModePerMonth()); + publishString(HMI_SUBTOPIC, HMI_AIR_DUCT_CONFIG, aquamqtt::message::airDuctConfigStr(message.airDuctConfig())); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_SETUP_STATE); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), setupStr(message.setupMode())); + if (message.testModeChanged()) + { + publishString(HMI_SUBTOPIC, HMI_TEST_MODE, testModeStr(message.testMode())); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_TIMER_WINDOW_A); - message.timerWindowStr(true, reinterpret_cast(mPayloadBuffer)); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.installationConfigChanged()) + { + publishString(HMI_SUBTOPIC, HMI_INSTALLATION_CONFIG, installationModeStr(message.installationMode())); + } - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - HMI_SUBTOPIC, - HMI_TIMER_WINDOW_B); - message.timerWindowStr(false, reinterpret_cast(mPayloadBuffer)); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.timerModeOneChanged()) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + HMI_SUBTOPIC, + HMI_TIMER_WINDOW_A); + message.timerWindowStr(true, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } + + if (message.timerModeTwoChanged()) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + HMI_SUBTOPIC, + HMI_TIMER_WINDOW_B); + message.timerWindowStr(false, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } if (config::DEBUG_RAW_SERIAL_MESSAGES) { @@ -936,81 +643,49 @@ void MQTTTask::updateHMIStatus() void MQTTTask::updateEnergyStats() { message::MainEnergyMessage message(mTransferBuffer); + message.compareWith(mLastProcessedEnergyMessage); - ultoa(message.totalHeatpumpHours(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_TOTAL_HEATPUMP_HOURS); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), true, 0); - - ultoa(message.totalHeatingElemHours(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_TOTAL_HEATING_ELEM_HOURS); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), true, 0); - - ultoa(message.totalHours(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_TOTAL_HOURS); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), true, 0); - - ultoa(message.totalEnergyCounter(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_TOTAL_ENERGY_WH); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), true, 0); - - ultoa(message.powerHeatpump(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_POWER_HEATPUMP); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); - - if (strlen(optionalPublishTopicHeatPumpCurrentPower) != 0) + if (message.totalHeatpumpHoursChanged()) { - sprintf(reinterpret_cast(mTopicBuffer), "%s", optionalPublishTopicHeatPumpCurrentPower); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishul(ENERGY_SUBTOPIC, ENERGY_TOTAL_HEATPUMP_HOURS, message.totalHeatpumpHours(), true); + } + if (message.totalHeatingElemHoursChanged()) + { + publishul(ENERGY_SUBTOPIC, ENERGY_TOTAL_HEATING_ELEM_HOURS, message.totalHeatingElemHours(), true); + } + if (message.totalHoursChanged()) + { + publishul(ENERGY_SUBTOPIC, ENERGY_TOTAL_HOURS, message.totalHours(), true); + } + if (message.totalEnergyCounterChanged()) + { + publishul(ENERGY_SUBTOPIC, ENERGY_TOTAL_ENERGY_WH, message.totalEnergyCounter(), true); } - ultoa(message.powerHeatElement(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_POWER_HEAT_ELEMENT); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.powerHeatpumpChanged()) + { + publishul(ENERGY_SUBTOPIC, ENERGY_POWER_HEATPUMP, message.powerHeatpump()); + if (strlen(optionalPublishTopicHeatPumpCurrentPower) != 0) + { + sprintf(reinterpret_cast(mTopicBuffer), "%s", optionalPublishTopicHeatPumpCurrentPower); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } + } - if (strlen(optionalPublishTopicHeatElementCurrentPower) != 0) + if (message.powerHeatElementChanged()) { - sprintf(reinterpret_cast(mTopicBuffer), "%s", optionalPublishTopicHeatElementCurrentPower); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + publishul(ENERGY_SUBTOPIC, ENERGY_POWER_HEAT_ELEMENT, message.powerHeatElement()); + if (strlen(optionalPublishTopicHeatElementCurrentPower) != 0) + { + sprintf(reinterpret_cast(mTopicBuffer), "%s", optionalPublishTopicHeatElementCurrentPower); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } } - ultoa(message.powerOverall(), reinterpret_cast(mPayloadBuffer), 10); - sprintf(reinterpret_cast(mTopicBuffer), - "%s%S%S%S", - config::mqttPrefix, - BASE_TOPIC, - ENERGY_SUBTOPIC, - ENERGY_POWER_TOTAL); - mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + if (message.powerOverallChanged()) + { + publishul(ENERGY_SUBTOPIC, ENERGY_POWER_TOTAL, message.powerOverall()); + } if (config::DEBUG_RAW_SERIAL_MESSAGES) { @@ -1026,4 +701,49 @@ void MQTTTask::updateEnergyStats() } } +void MQTTTask::publishFloat(const char* subtopic, const char* topic, float value, bool retained) +{ + dtostrf(value, 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, subtopic, topic); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), retained, 0); +} + +void MQTTTask::publishString(const char* subtopic, const char* topic, const char* value, bool retained) +{ + sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, subtopic, topic); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), value, retained, 0); +} + +void MQTTTask::publishi(const char* subtopic, const char* topic, int value, bool retained) +{ + itoa(value, reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, subtopic, topic); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), retained, 0); +} + +void MQTTTask::publishul(const char* subtopic, const char* topic, unsigned long value, bool retained) +{ + ultoa(value, reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", config::mqttPrefix, BASE_TOPIC, subtopic, topic); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), retained, 0); +} + +void MQTTTask::publishul( + const char* subtopic_1, + const char* subtopic_2, + const char* topic, + unsigned long value, + bool retained) +{ + ultoa(value, reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + subtopic_1, + subtopic_2, + topic); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer), retained, 0); +} + } // namespace aquamqtt From 0fc0788d5c541abb478baaeb1f79fac1484780e1 Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Wed, 22 May 2024 21:48:54 +0200 Subject: [PATCH 05/12] fix(mitm): fixed dropped bytes in main channel --- AquaMQTT/src/task/ControllerTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AquaMQTT/src/task/ControllerTask.cpp b/AquaMQTT/src/task/ControllerTask.cpp index 11a3273..1089b7e 100644 --- a/AquaMQTT/src/task/ControllerTask.cpp +++ b/AquaMQTT/src/task/ControllerTask.cpp @@ -49,7 +49,7 @@ void ControllerTask::loop() { bool printSerialStats = (millis() - mLastStatisticsUpdate) >= 5000; - vTaskDelay(pdMS_TO_TICKS(50)); + vTaskDelay(pdMS_TO_TICKS(5)); while (Serial2.available()) { int valRead = Serial2.read(); From 6f12f023f9a45bca7592c52a722113492131ee94 Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Wed, 22 May 2024 21:54:50 +0200 Subject: [PATCH 06/12] chore(mqtt): rename topics --- AquaMQTT/include/message/MainStatusMessage.h | 10 +++++----- AquaMQTT/include/mqtt/MQTTDefinitions.h | 10 +++++----- AquaMQTT/src/message/MainStatusMessage.cpp | 11 +++++------ AquaMQTT/src/task/MQTTTask.cpp | 10 +++++----- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index ff2802f..1839208 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -53,15 +53,15 @@ class MainStatusMessage MAINBrands settingBrand(); - bool settingHasHeatExchanger(); + bool capabilityHasHeatExchanger(); - bool settingHasCirculation(); + bool capabilityHasCirculation(); - bool settingHasPVInput(); + bool capabilityHasPVInput(); - bool settingHasCommunication(); + bool capabilityHasCommunication(); - bool settingHasAntiTrockenheizung(); + bool capabilityHasAntiDryHeating(); void compareWith(uint8_t* data); diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 285bb32..8e1791d 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -88,11 +88,11 @@ const char MAIN_SETTING_MIN_TEMP_LEGIONELLA[] PROGMEM = { "settingMinLegionellaT const char MAIN_SETTING_PWR_HEATELEM[] PROGMEM = { "settingWattageElement" }; const char MAIN_SETTING_BOILER_CAP[] PROGMEM = { "settingBoilerCapacity" }; const char MAIN_SETTING_BOILER_BRAND[] PROGMEM = { "settingBoilerBrand" }; -const char MAIN_SETTING_HAS_HEAT_EXC[] PROGMEM = { "settingHasHeatExchanger" }; -const char MAIN_SETTING_HAS_CIRCULATION[] PROGMEM = { "settingHasCirculation" }; -const char MAIN_SETTING_HAS_PV_INPUT[] PROGMEM = { "settingHasPVInput" }; -const char MAIN_SETTING_HAS_EXT_COMM[] PROGMEM = { "settingHasExtCom" }; -const char MAIN_SETTING_HAS_ANTI_TRO[] PROGMEM = { "settingHasAntiTH" }; +const char MAIN_CAPABILITY_HEAT_EXC[] PROGMEM = { "capabilityHeatExchanger" }; +const char MAIN_CAPABILITY_CIRCULATION[] PROGMEM = { "capabilityCirculation" }; +const char MAIN_CAPABILITY_PV_INPUT[] PROGMEM = { "capabilityPVInput" }; +const char MAIN_CAPABILITY_EXT_COMM[] PROGMEM = { "capabilityCommunication" }; +const char MAIN_CAPABILITY_DRY_HEATING[] PROGMEM = { "capabilityAntiDryHeating" }; const char HMI_HOT_WATER_TEMP_TARGET[] PROGMEM = { "waterTempTarget" }; const char HMI_OPERATION_MODE[] PROGMEM = { "operationMode" }; diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index 7e558cb..a44bcc5 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -90,24 +90,23 @@ MAINBrands MainStatusMessage::settingBrand() } } -// TODO: rename this thing to capabilities -bool MainStatusMessage::settingHasHeatExchanger() +bool MainStatusMessage::capabilityHasHeatExchanger() { return mData[36] & 0x01; } -bool MainStatusMessage::settingHasCirculation() +bool MainStatusMessage::capabilityHasCirculation() { return mData[36] & 0x02; } -bool MainStatusMessage::settingHasPVInput() +bool MainStatusMessage::capabilityHasPVInput() { return mData[36] & 0x04; } -bool MainStatusMessage::settingHasCommunication() +bool MainStatusMessage::capabilityHasCommunication() { return !(mData[36] & 0x08); } -bool MainStatusMessage::settingHasAntiTrockenheizung() +bool MainStatusMessage::capabilityHasAntiDryHeating() { return (mData[36] & 0x20); } diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index 223e7af..d9b3634 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -501,11 +501,11 @@ void MQTTTask::updateMainStatus() } if (message.settingCapabilitiesChanged()) { - publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_HEAT_EXC, message.settingHasHeatExchanger()); - publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_CIRCULATION, message.settingHasCirculation()); - publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_PV_INPUT, message.settingHasPVInput()); - publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_EXT_COMM, message.settingHasCommunication()); - publishi(MAIN_SUBTOPIC, MAIN_SETTING_HAS_ANTI_TRO, message.settingHasAntiTrockenheizung()); + publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_HEAT_EXC, message.capabilityHasHeatExchanger()); + publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_CIRCULATION, message.capabilityHasCirculation()); + publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_PV_INPUT, message.capabilityHasPVInput()); + publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_EXT_COMM, message.capabilityHasCommunication()); + publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_DRY_HEATING, message.capabilityHasAntiDryHeating()); } if (config::DEBUG_RAW_SERIAL_MESSAGES) From 5ac70e3e78387553bf456974a40fabdf8c27a538 Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Wed, 22 May 2024 22:15:50 +0200 Subject: [PATCH 07/12] feat(parsing): parse pv input status and states --- AquaMQTT/include/message/HMIMessage.h | 6 ++-- AquaMQTT/include/message/MainStatusMessage.h | 5 +++ AquaMQTT/include/mqtt/MQTTDefinitions.h | 2 ++ AquaMQTT/src/message/HMIMessage.cpp | 36 ++++++++++++-------- AquaMQTT/src/message/MainStatusMessage.cpp | 15 ++++++++ AquaMQTT/src/task/MQTTTask.cpp | 8 ++++- 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/AquaMQTT/include/message/HMIMessage.h b/AquaMQTT/include/message/HMIMessage.h index 2e392f9..9a16d2c 100644 --- a/AquaMQTT/include/message/HMIMessage.h +++ b/AquaMQTT/include/message/HMIMessage.h @@ -36,6 +36,8 @@ class HMIMessage void enableHeatingElement(bool enabled); + bool isPVInputActivated(); + HMISetup setupMode(); uint8_t antiLegionellaModePerMonth(); @@ -94,7 +96,7 @@ class HMIMessage bool emergencyModeChanged() const; - bool heatingElemOrSetupStateChanged() const; + bool heatingElemOrSetupStateOrPVActiveChanged() const; bool legionellaOrAirductChanged() const; @@ -114,7 +116,7 @@ class HMIMessage bool mLegionellaAirductChanged; bool mEmergencyModeChanged; bool mInstallConfigChanged; - bool mHeatingElemOrSetupStateChanged; + bool mHeatingElemOrSetupStateOrPVActiveChanged; bool mTimerModeOneChanged; bool mTimerModeTwoChanged; bool mTimeChanged; diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index 1839208..e809011 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -37,6 +37,8 @@ class MainStatusMessage bool stateDefrost(); + bool statePV(); + uint8_t settingPwmFirst(); uint8_t settingPwmSecond(); @@ -77,6 +79,8 @@ class MainStatusMessage bool statesChanged() const; + bool statePVChanged() const; + bool settingPwmFirstChanged() const; bool settingPwmSecondChanged() const; @@ -112,6 +116,7 @@ class MainStatusMessage bool mSettingBoilerCapacityChanged; bool mSettingBoilerBrandChanged; bool mSettingCapabilitiesChanged; + bool mPVStateChanged; }; } // namespace message diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 8e1791d..8d17613 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -79,6 +79,7 @@ const char MAIN_STATE_HEATPUMP[] PROGMEM = { "stateHeatpump" }; const char MAIN_STATE_HEAT_ELEMENT[] PROGMEM = { "stateElement" }; const char MAIN_STATE_EXT_BOILER[] PROGMEM = { "stateExtBoiler" }; const char MAIN_STATE_DEFROST[] PROGMEM = { "stateDefrost" }; +const char MAIN_STATE_PV[] PROGMEM = { "statePV" }; const char MAIN_SETTING_PWM_01[] PROGMEM = { "settingPWM_1" }; const char MAIN_SETTING_PWM_02[] PROGMEM = { "settingPWM_2" }; @@ -109,6 +110,7 @@ const char HMI_AIR_DUCT_CONFIG[] PROGMEM = { "configAirduct" }; const char HMI_INSTALLATION_CONFIG[] PROGMEM = { "configInstallation" }; const char HMI_TEST_MODE[] PROGMEM = { "testModeStatus" }; const char HMI_SETUP_STATE[] PROGMEM = { "setupState" }; +const char HMM_PV_INPUT_ACTIVATED[] PROGMEM = { "pvInputActivated" }; const char ENERGY_TOTAL_HEATING_ELEM_HOURS[] PROGMEM = { "totalHeatingElemHours" }; const char ENERGY_TOTAL_HEATPUMP_HOURS[] PROGMEM = { "totalHeatpumpHours" }; diff --git a/AquaMQTT/src/message/HMIMessage.cpp b/AquaMQTT/src/message/HMIMessage.cpp index cbd6913..61ac363 100644 --- a/AquaMQTT/src/message/HMIMessage.cpp +++ b/AquaMQTT/src/message/HMIMessage.cpp @@ -14,7 +14,7 @@ HMIMessage::HMIMessage(uint8_t* data) , mLegionellaAirductChanged(false) , mEmergencyModeChanged(false) , mInstallConfigChanged(false) - , mHeatingElemOrSetupStateChanged(false) + , mHeatingElemOrSetupStateOrPVActiveChanged(false) , mTimerModeOneChanged(false) , mTimerModeTwoChanged(false) , mTimeChanged(false) @@ -85,6 +85,12 @@ bool HMIMessage::isHeatingElementEnabled() { return mData[9] & 0x04; } + +bool HMIMessage::isPVInputActivated() +{ + return mData[9] & 0x02; +} + HMISetup HMIMessage::setupMode() { if (mData[9] & 0x80) @@ -340,17 +346,17 @@ void HMIMessage::compareWith(uint8_t* data) { if (data == nullptr) { - mTargetTempChanged = true; - mOperationModeChanged = true; - mLegionellaAirductChanged = true; - mEmergencyModeChanged = true; - mInstallConfigChanged = true; - mHeatingElemOrSetupStateChanged = true; - mTimerModeOneChanged = true; - mTimerModeTwoChanged = true; - mTimeChanged = true; - mDateChanged = true; - mTestModeChanged = true; + mTargetTempChanged = true; + mOperationModeChanged = true; + mLegionellaAirductChanged = true; + mEmergencyModeChanged = true; + mInstallConfigChanged = true; + mHeatingElemOrSetupStateOrPVActiveChanged = true; + mTimerModeOneChanged = true; + mTimerModeTwoChanged = true; + mTimeChanged = true; + mDateChanged = true; + mTestModeChanged = true; return; } @@ -381,7 +387,7 @@ void HMIMessage::compareWith(uint8_t* data) mInstallConfigChanged = true; break; case 9: - mHeatingElemOrSetupStateChanged = true; + mHeatingElemOrSetupStateOrPVActiveChanged = true; break; case 10: case 11: @@ -428,9 +434,9 @@ bool HMIMessage::emergencyModeChanged() const { return mEmergencyModeChanged; } -bool HMIMessage::heatingElemOrSetupStateChanged() const +bool HMIMessage::heatingElemOrSetupStateOrPVActiveChanged() const { - return mHeatingElemOrSetupStateChanged; + return mHeatingElemOrSetupStateOrPVActiveChanged; } bool HMIMessage::legionellaOrAirductChanged() const { diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index a44bcc5..362e0fc 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -45,6 +45,12 @@ bool MainStatusMessage::stateDefrost() { return mData[17] & 0x20; } + +bool MainStatusMessage::statePV() +{ + return mData[22] & 0x20; +} + uint8_t MainStatusMessage::settingMinTTarget() { return mData[20]; @@ -129,6 +135,7 @@ void MainStatusMessage::compareWith(uint8_t* data) mSettingBoilerCapacityChanged = true; mSettingBoilerBrandChanged = true; mSettingCapabilitiesChanged = true; + mPVStateChanged = true; return; } @@ -180,6 +187,9 @@ void MainStatusMessage::compareWith(uint8_t* data) case 21: mSettingAntiLegionellaTargetChanged = true; break; + case 22: + mPVStateChanged = true; + break; case 32: mSettingWattageHeatElementChanged = true; break; @@ -215,6 +225,7 @@ MainStatusMessage::MainStatusMessage(uint8_t* data) , mSettingBoilerCapacityChanged(false) , mSettingBoilerBrandChanged(false) , mSettingCapabilitiesChanged(false) + , mPVStateChanged(false) { } bool MainStatusMessage::hotWaterTempChanged() const @@ -277,5 +288,9 @@ bool MainStatusMessage::settingCapabilitiesChanged() const { return mSettingCapabilitiesChanged; } +bool MainStatusMessage::statePVChanged() const +{ + return mPVStateChanged; +} } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index d9b3634..d9da3f2 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -467,6 +467,11 @@ void MQTTTask::updateMainStatus() publishi(MAIN_SUBTOPIC, MAIN_STATE_FAN, message.stateFan()); publishi(MAIN_SUBTOPIC, MAIN_STATE_DEFROST, message.stateDefrost()); } + + if(message.statePVChanged()){ + publishi(MAIN_SUBTOPIC, MAIN_STATE_PV, message.statePV()); + } + if (message.settingPwmFirstChanged()) { publishi(MAIN_SUBTOPIC, MAIN_SETTING_PWM_01, message.settingPwmFirst()); @@ -578,9 +583,10 @@ void MQTTTask::updateHMIStatus() publishi(HMI_SUBTOPIC, HMI_EMERGENCY_MODE, message.isEmergencyModeEnabled()); } - if (message.heatingElemOrSetupStateChanged()) + if (message.heatingElemOrSetupStateOrPVActiveChanged()) { publishi(HMI_SUBTOPIC, HMI_HEATING_ELEMENT_ENABLED, message.isHeatingElementEnabled()); + publishi(HMI_SUBTOPIC, HMM_PV_INPUT_ACTIVATED, message.isPVInputActivated()); publishString(HMI_SUBTOPIC, HMI_SETUP_STATE, setupStr(message.setupMode())); } From 778ed910559a84c5e3e80a412ed2ed81ad69cc3f Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Mon, 27 May 2024 22:25:40 +0200 Subject: [PATCH 08/12] fix(fan): represent fan speed in percent not rpm --- AquaMQTT/include/message/MainStatusMessage.h | 2 +- AquaMQTT/include/mqtt/MQTTDefinitions.h | 2 +- AquaMQTT/src/message/MainStatusMessage.cpp | 6 ++++-- AquaMQTT/src/task/MQTTTask.cpp | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index e809011..b556fbc 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -25,7 +25,7 @@ class MainStatusMessage float evaporatorUpperAirTemp(); - uint16_t fanSpeed(); + float fanSpeedPwm(); bool stateHeatingElement(); diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 8d17613..8a1d4ef 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -73,7 +73,7 @@ const char MAIN_HOT_WATER_TEMP[] PROGMEM = { "waterTemp" }; const char MAIN_SUPPLY_AIR_TEMP[] PROGMEM = { "supplyAirTemp" }; const char MAIN_EVAPORATOR_AIR_TEMP_UPPER[] PROGMEM = { "evaporatorAirTemp" }; const char MAIN_EVAPORATOR_AIR_TEMP_LOWER[] PROGMEM = { "evaporatorAirTempLower" }; -const char MAIN_FAN_SPEED[] PROGMEM = { "fanSpeed" }; +const char MAIN_FAN_PWM[] PROGMEM = { "fanPWM" }; const char MAIN_STATE_FAN[] PROGMEM = { "stateFan" }; const char MAIN_STATE_HEATPUMP[] PROGMEM = { "stateHeatpump" }; const char MAIN_STATE_HEAT_ELEMENT[] PROGMEM = { "stateElement" }; diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index 362e0fc..68be227 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -21,10 +21,12 @@ float MainStatusMessage::evaporatorUpperAirTemp() { return (float) (((short int) (mData[8] << 8) | mData[7]) / 10.0); } -uint16_t MainStatusMessage::fanSpeed() + +float MainStatusMessage::fanSpeedPwm() { - return ((uint16_t) mData[19] << 8) | (uint16_t) mData[18]; + return (float) (((short int) (mData[19] << 8) | mData[18]) / 10.0); } + bool MainStatusMessage::stateHeatingElement() { return mData[17] & 0x01; diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index d9da3f2..21939d7 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -457,7 +457,7 @@ void MQTTTask::updateMainStatus() } if (message.fanSpeedChanged()) { - publishul(MAIN_SUBTOPIC, MAIN_FAN_SPEED, message.fanSpeed()); + publishFloat(MAIN_SUBTOPIC, MAIN_FAN_PWM, message.fanSpeedPwm()); } if (message.statesChanged()) { From aaa89d1a63249c81a415562c131d28b07395674f Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Mon, 27 May 2024 22:30:43 +0200 Subject: [PATCH 09/12] feat(errors): parse and provide error updates --- AquaMQTT/include/buffer/FrameBuffer.h | 3 +- AquaMQTT/include/message/ErrorMessage.h | 56 +++ AquaMQTT/include/message/HMIMessage.h | 7 + AquaMQTT/include/message/MainStatusMessage.h | 11 + AquaMQTT/include/message/MessageConstants.h | 2 + AquaMQTT/include/mqtt/MQTTDefinitions.h | 4 + AquaMQTT/include/state/DHWState.h | 2 + AquaMQTT/include/task/MQTTTask.h | 1 + AquaMQTT/src/buffer/FrameBuffer.cpp | 10 +- AquaMQTT/src/message/ErrorMessage.cpp | 88 +++++ AquaMQTT/src/message/HMIMessage.cpp | 20 + AquaMQTT/src/message/MainStatusMessage.cpp | 18 + AquaMQTT/src/state/DHWState.cpp | 20 + AquaMQTT/src/task/ControllerTask.cpp | 2 +- AquaMQTT/src/task/HMITask.cpp | 2 +- AquaMQTT/src/task/ListenerTask.cpp | 2 +- AquaMQTT/src/task/MQTTTask.cpp | 156 +++++++- PROTOCOL.md | 369 ++++++++++++------- 18 files changed, 639 insertions(+), 134 deletions(-) create mode 100644 AquaMQTT/include/message/ErrorMessage.h create mode 100644 AquaMQTT/src/message/ErrorMessage.cpp diff --git a/AquaMQTT/include/buffer/FrameBuffer.h b/AquaMQTT/include/buffer/FrameBuffer.h index f5de5b3..8a26769 100644 --- a/AquaMQTT/include/buffer/FrameBuffer.h +++ b/AquaMQTT/include/buffer/FrameBuffer.h @@ -9,7 +9,7 @@ class FrameBuffer { public: - explicit FrameBuffer(bool handle194, bool handle193, bool handle67, std::string name); + explicit FrameBuffer(bool handle194, bool handle193, bool handle67, bool handle74, std::string name); ~FrameBuffer() = default; @@ -36,6 +36,7 @@ class FrameBuffer bool mHandle194; bool mHandle193; bool mHandle67; + bool mHandle74; std::string mName; uint64_t mDroppedCount; diff --git a/AquaMQTT/include/message/ErrorMessage.h b/AquaMQTT/include/message/ErrorMessage.h new file mode 100644 index 0000000..07ef10f --- /dev/null +++ b/AquaMQTT/include/message/ErrorMessage.h @@ -0,0 +1,56 @@ +#ifndef AQUAMQTT_ERRORMESSAGE_H +#define AQUAMQTT_ERRORMESSAGE_H + +#include + +#include "MessageConstants.h" + +namespace aquamqtt +{ +namespace message +{ +class ErrorMessage +{ +public: + explicit ErrorMessage(uint8_t* data); + + ~ErrorMessage() = default; + + uint8_t errorRequestId(); + + uint8_t errorCode(); + + float hotWaterTemp(); + + float airTemp(); + + float evaporatorLowerAirTemp(); + + float evaporatorUpperAirTemp(); + + float fanSpeedPwm(); + + uint16_t totalHeatpumpHours(); + + uint16_t totalHeatingElemHours(); + + uint8_t timeHours(); + + uint8_t timeMinutes(); + + uint16_t dateYear(); + + uint8_t dateMonth(); + + uint8_t dateDay(); + + bool isEmpty(); + +private: + uint8_t* mData; +}; + +} // namespace message +} // namespace aquamqtt + +#endif // AQUAMQTT_ERRORMESSAGE_H diff --git a/AquaMQTT/include/message/HMIMessage.h b/AquaMQTT/include/message/HMIMessage.h index 9a16d2c..95a0085 100644 --- a/AquaMQTT/include/message/HMIMessage.h +++ b/AquaMQTT/include/message/HMIMessage.h @@ -110,6 +110,12 @@ class HMIMessage void compareWith(uint8_t* data); + uint8_t errorRequestId() const; + + uint8_t errorNumberRequested() const; + + bool errorRequestChanged() const; + private: bool mTargetTempChanged; bool mOperationModeChanged; @@ -122,6 +128,7 @@ class HMIMessage bool mTimeChanged; bool mDateChanged; bool mTestModeChanged; + bool mErrorRequestChanged; uint8_t* mData; }; diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index b556fbc..9d38141 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -81,6 +81,8 @@ class MainStatusMessage bool statePVChanged() const; + uint8_t errorCode() const; + bool settingPwmFirstChanged() const; bool settingPwmSecondChanged() const; @@ -99,6 +101,14 @@ class MainStatusMessage bool settingCapabilitiesChanged() const; + bool errorCodeChanged() const; + + void changeUnknownStateA(bool b); + + void changeUnknownStateB(bool b); + + void hackError(); + private: uint8_t* mData; bool mHotWaterTempChanged; @@ -117,6 +127,7 @@ class MainStatusMessage bool mSettingBoilerBrandChanged; bool mSettingCapabilitiesChanged; bool mPVStateChanged; + bool mErrorCodeChanged; }; } // namespace message diff --git a/AquaMQTT/include/message/MessageConstants.h b/AquaMQTT/include/message/MessageConstants.h index 7b63bcc..dc6444c 100644 --- a/AquaMQTT/include/message/MessageConstants.h +++ b/AquaMQTT/include/message/MessageConstants.h @@ -17,6 +17,8 @@ constexpr uint8_t MAIN_MESSAGE_IDENTIFIER = 193; constexpr uint8_t MAIN_MESSAGE_LENGTH = 37; constexpr uint8_t ENERGY_MESSAGE_IDENTIFIER = 67; constexpr uint8_t ENERGY_MESSAGE_LENGTH = 31; +constexpr uint8_t ERROR_MESSAGE_IDENTIFIER = 74; +constexpr uint8_t ERROR_MESSAGE_LENGTH = 35; enum HMIOperationMode : int { diff --git a/AquaMQTT/include/mqtt/MQTTDefinitions.h b/AquaMQTT/include/mqtt/MQTTDefinitions.h index 8a1d4ef..00f22b4 100644 --- a/AquaMQTT/include/mqtt/MQTTDefinitions.h +++ b/AquaMQTT/include/mqtt/MQTTDefinitions.h @@ -14,6 +14,7 @@ const char CONTROL_TOPIC[] PROGMEM = { "aquamqtt/ctrl/#" }; const char HMI_SUBTOPIC[] PROGMEM = { "hmi/" }; const char MAIN_SUBTOPIC[] PROGMEM = { "main/" }; const char ENERGY_SUBTOPIC[] PROGMEM = { "energy/" }; +const char ERROR_SUBTOPIC[] PROGMEM = { "error/" }; const char STATS_SUBTOPIC[] PROGMEM = { "stats/" }; // Enum Types @@ -69,6 +70,7 @@ const char ENUM_AQUAMQTT_OVERRIDE_MODE_HE_ONLY[] PROGMEM = { "PV HE" }; const char ENUM_AQUAMQTT_OVERRIDE_MODE_PV_FULL[] PROGMEM = { "PV BOOST" }; // Subtopics +const char MAIN_ERROR_CODE[] PROGMEM = { "errorCode" }; const char MAIN_HOT_WATER_TEMP[] PROGMEM = { "waterTemp" }; const char MAIN_SUPPLY_AIR_TEMP[] PROGMEM = { "supplyAirTemp" }; const char MAIN_EVAPORATOR_AIR_TEMP_UPPER[] PROGMEM = { "evaporatorAirTemp" }; @@ -120,6 +122,8 @@ const char ENERGY_POWER_TOTAL[] PROGMEM = { "powerTotal" }; const char ENERGY_POWER_HEAT_ELEMENT[] PROGMEM = { "powerHeatingElem" }; const char ENERGY_POWER_HEATPUMP[] PROGMEM = { "powerHeatpump" }; +const char ERROR_ERROR_NUMBER[] PROGMEM = { "errorNumber" }; + const char STATS_AQUAMQTT_ADDR[] PROGMEM = { "ipAddress" }; const char STATS_AQUAMQTT_RSSI[] PROGMEM = { "rssiDb" }; const char STATS_AQUAMQTT_MODE[] PROGMEM = { "aquamqttMode" }; diff --git a/AquaMQTT/include/state/DHWState.h b/AquaMQTT/include/state/DHWState.h index 27f1382..e8c82f1 100644 --- a/AquaMQTT/include/state/DHWState.h +++ b/AquaMQTT/include/state/DHWState.h @@ -55,10 +55,12 @@ class DHWState bool mHasHmiMessage; bool mHasMainMessage; bool mHasEnergyMessage; + bool mHasErrorMessage; uint8_t mMessageHmi[aquamqtt::message::HMI_MESSAGE_LENGTH]; uint8_t mMessageMain[aquamqtt::message::MAIN_MESSAGE_LENGTH]; uint8_t mMessageEnergy[aquamqtt::message::ENERGY_MESSAGE_LENGTH]; + uint8_t mMessageError[aquamqtt::message::ERROR_MESSAGE_LENGTH]; BufferStatistics mHmiStats; BufferStatistics mMainStats; diff --git a/AquaMQTT/include/task/MQTTTask.h b/AquaMQTT/include/task/MQTTTask.h index c5b73b4..2cd66f7 100644 --- a/AquaMQTT/include/task/MQTTTask.h +++ b/AquaMQTT/include/task/MQTTTask.h @@ -35,6 +35,7 @@ class MQTTTask void updateMainStatus(); void updateHMIStatus(); void updateEnergyStats(); + void updateErrorStatus(); void updateStats(); private: diff --git a/AquaMQTT/src/buffer/FrameBuffer.cpp b/AquaMQTT/src/buffer/FrameBuffer.cpp index b784696..57df6cd 100644 --- a/AquaMQTT/src/buffer/FrameBuffer.cpp +++ b/AquaMQTT/src/buffer/FrameBuffer.cpp @@ -5,10 +5,11 @@ #define FRAME_ID_LEN_BYTES 1 #define CRC_LEN_BYTES 2 -FrameBuffer::FrameBuffer(bool handle194, bool handle193, bool handle67, std::string name) +FrameBuffer::FrameBuffer(bool handle194, bool handle193, bool handle67, bool handle74, std::string name) : mHandle194(handle194) , mHandle193(handle193) , mHandle67(handle67) + , mHandle74(handle74) , mName(std::move(name)) , mDroppedCount(0) , mCRCFailCount(0) @@ -85,7 +86,8 @@ int FrameBuffer::handleFrame() { if ((frameId == aquamqtt::message::HMI_MESSAGE_IDENTIFIER && mHandle194) || (frameId == aquamqtt::message::MAIN_MESSAGE_IDENTIFIER && mHandle193) - || (frameId == aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER && mHandle67)) + || (frameId == aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER && mHandle67) + || (frameId == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER && mHandle74)) { aquamqtt::DHWState::getInstance().storeFrame(frameId, payloadLength, mTransferBuffer); mHandledCount++; @@ -110,7 +112,9 @@ bool FrameBuffer::isSync() || (mBuffer[0] == aquamqtt::message::MAIN_MESSAGE_IDENTIFIER && mBuffer[1] == aquamqtt::message::MAIN_MESSAGE_LENGTH) || (mBuffer[0] == aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER - && mBuffer[1] == aquamqtt::message::ENERGY_MESSAGE_LENGTH)); + && mBuffer[1] == aquamqtt::message::ENERGY_MESSAGE_LENGTH) + || (mBuffer[0] == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER + && mBuffer[1] == aquamqtt::message::ERROR_MESSAGE_LENGTH)); } uint64_t FrameBuffer::getDroppedCount() const { diff --git a/AquaMQTT/src/message/ErrorMessage.cpp b/AquaMQTT/src/message/ErrorMessage.cpp new file mode 100644 index 0000000..ac0cfd1 --- /dev/null +++ b/AquaMQTT/src/message/ErrorMessage.cpp @@ -0,0 +1,88 @@ +#include "message/ErrorMessage.h" + +namespace aquamqtt +{ +namespace message +{ + +ErrorMessage::ErrorMessage(uint8_t* data) : mData(data) +{ +} + +bool ErrorMessage::isEmpty() +{ + for (int i = 0; i < ERROR_MESSAGE_LENGTH; ++i) + { + if (mData[i] != 0) + return false; + } + return true; +} + +uint8_t ErrorMessage::errorRequestId() +{ + return mData[1]; +} + +uint8_t ErrorMessage::errorCode() +{ + return mData[2]; +} + +float ErrorMessage::hotWaterTemp() +{ + return (float) (((short int) (mData[5] << 8) | mData[4]) / 10.0); +} + +float ErrorMessage::airTemp() +{ + return (float) (((short int) (mData[7] << 8) | mData[6]) / 10.0); +} + +float ErrorMessage::evaporatorLowerAirTemp() +{ + return (float) (((short int) (mData[9] << 8) | mData[8]) / 10.0); +} + +float ErrorMessage::evaporatorUpperAirTemp() +{ + return (float) (((short int) (mData[11] << 8) | mData[10]) / 10.0); +} + +float ErrorMessage::fanSpeedPwm() +{ + return mData[21]; +} + +uint16_t ErrorMessage::totalHeatpumpHours() +{ + return ((uint16_t) mData[28] << 8) | (uint16_t) mData[27]; +} + +uint16_t ErrorMessage::totalHeatingElemHours() +{ + return ((uint16_t) mData[26] << 8) | (uint16_t) mData[25]; +} + +uint8_t ErrorMessage::timeHours() +{ + return mData[32]; +} +uint8_t ErrorMessage::timeMinutes() +{ + return mData[31]; +} +uint16_t ErrorMessage::dateYear() +{ + return 2000 + (mData[30] / 2); +} +uint8_t ErrorMessage::dateMonth() +{ + return (mData[29] >> 5) + ((mData[30] % 2) * 8); +} +uint8_t ErrorMessage::dateDay() +{ + return mData[29] & 0x1F; +} +} // namespace message +} // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/message/HMIMessage.cpp b/AquaMQTT/src/message/HMIMessage.cpp index 61ac363..ca48bc3 100644 --- a/AquaMQTT/src/message/HMIMessage.cpp +++ b/AquaMQTT/src/message/HMIMessage.cpp @@ -20,6 +20,7 @@ HMIMessage::HMIMessage(uint8_t* data) , mTimeChanged(false) , mDateChanged(false) , mTestModeChanged(false) + , mErrorRequestChanged(false) { } float HMIMessage::waterTempTarget() @@ -342,6 +343,15 @@ void HMIMessage::setAntiLegionellaModePerMonth(uint8_t value) { } +uint8_t HMIMessage::errorRequestId() const +{ + return mData[29]; +} +uint8_t HMIMessage::errorNumberRequested() const +{ + return mData[28]; +} + void HMIMessage::compareWith(uint8_t* data) { if (data == nullptr) @@ -357,6 +367,7 @@ void HMIMessage::compareWith(uint8_t* data) mTimeChanged = true; mDateChanged = true; mTestModeChanged = true; + mErrorRequestChanged = true; return; } @@ -409,6 +420,10 @@ void HMIMessage::compareWith(uint8_t* data) case 22: mTestModeChanged = true; break; + case 27: + case 28: + mErrorRequestChanged = true; + break; default: break; } @@ -459,5 +474,10 @@ bool HMIMessage::timerModeTwoChanged() const return mTimerModeTwoChanged; } +bool HMIMessage::errorRequestChanged() const +{ + return mErrorRequestChanged; +} + } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/message/MainStatusMessage.cpp b/AquaMQTT/src/message/MainStatusMessage.cpp index 68be227..f7570e9 100644 --- a/AquaMQTT/src/message/MainStatusMessage.cpp +++ b/AquaMQTT/src/message/MainStatusMessage.cpp @@ -138,6 +138,7 @@ void MainStatusMessage::compareWith(uint8_t* data) mSettingBoilerBrandChanged = true; mSettingCapabilitiesChanged = true; mPVStateChanged = true; + mErrorCodeChanged = true; return; } @@ -192,6 +193,9 @@ void MainStatusMessage::compareWith(uint8_t* data) case 22: mPVStateChanged = true; break; + case 23: + mErrorCodeChanged = true; + break; case 32: mSettingWattageHeatElementChanged = true; break; @@ -228,6 +232,7 @@ MainStatusMessage::MainStatusMessage(uint8_t* data) , mSettingBoilerBrandChanged(false) , mSettingCapabilitiesChanged(false) , mPVStateChanged(false) + , mErrorCodeChanged(false) { } bool MainStatusMessage::hotWaterTempChanged() const @@ -294,5 +299,18 @@ bool MainStatusMessage::statePVChanged() const { return mPVStateChanged; } +bool MainStatusMessage::errorCodeChanged() const +{ + return mErrorCodeChanged; +} + +uint8_t MainStatusMessage::errorCode() const +{ + if (mData[23] == UINT8_MAX) + { + return 0; + } + return mData[23]; +} } // namespace message } // namespace aquamqtt \ No newline at end of file diff --git a/AquaMQTT/src/state/DHWState.cpp b/AquaMQTT/src/state/DHWState.cpp index e8efa58..dba8e22 100644 --- a/AquaMQTT/src/state/DHWState.cpp +++ b/AquaMQTT/src/state/DHWState.cpp @@ -14,9 +14,11 @@ DHWState::DHWState() , mHasEnergyMessage(false) , mHasMainMessage(false) , mHasHmiMessage(false) + , mHasErrorMessage(false) , mMessageHmi{ 0 } , mMessageMain{ 0 } , mMessageEnergy{ 0 } + , mMessageError{ 0 } , mHmiStats{ 0, 0, 0, 0, 0 } , mMainStats{ 0, 0, 0, 0, 0 } , mListenerStats{ 0, 0, 0, 0, 0 } @@ -76,6 +78,19 @@ void DHWState::storeFrame(uint8_t frameId, uint8_t payloadLength, uint8_t* paylo xTaskNotifyIndexed(mNotify, 0, (1UL << 6UL), eSetBits); } } + else if ( + frameId == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER + && payloadLength == aquamqtt::message::ERROR_MESSAGE_LENGTH + && memcmp(mMessageError, payload, payloadLength) != 0) + { + memcpy(mMessageError, payload, payloadLength); + mHasErrorMessage = true; + + if (mNotify != nullptr) + { + xTaskNotifyIndexed(mNotify, 0, (1UL << 5UL), eSetBits); + } + } xSemaphoreGive(mMutex); } @@ -149,6 +164,11 @@ bool DHWState::copyFrame(uint8_t frameId, uint8_t* buffer) memcpy(buffer, mMessageEnergy, aquamqtt::message::ENERGY_MESSAGE_LENGTH); handled = true; } + else if (frameId == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER && mHasErrorMessage) + { + memcpy(buffer, mMessageError, aquamqtt::message::ERROR_MESSAGE_LENGTH); + handled = true; + } xSemaphoreGive(mMutex); diff --git a/AquaMQTT/src/task/ControllerTask.cpp b/AquaMQTT/src/task/ControllerTask.cpp index 1089b7e..e49440e 100644 --- a/AquaMQTT/src/task/ControllerTask.cpp +++ b/AquaMQTT/src/task/ControllerTask.cpp @@ -9,7 +9,7 @@ namespace aquamqtt { ControllerTask::ControllerTask() - : mBuffer(false, true, true, "controller") + : mBuffer(false, true, true, true, "controller") , mFlagSeen193(false) , mFlagSeen67(false) , mLastStatisticsUpdate(0) diff --git a/AquaMQTT/src/task/HMITask.cpp b/AquaMQTT/src/task/HMITask.cpp index 654055c..5f059f3 100644 --- a/AquaMQTT/src/task/HMITask.cpp +++ b/AquaMQTT/src/task/HMITask.cpp @@ -9,7 +9,7 @@ namespace aquamqtt { HMITask::HMITask() - : mBuffer(true, false, false, "hmi") + : mBuffer(true, false, false, false, "hmi") , mLastStatisticsUpdate(0) , mTransferBuffer{ 0 } , mCRC() diff --git a/AquaMQTT/src/task/ListenerTask.cpp b/AquaMQTT/src/task/ListenerTask.cpp index 7d8f6ed..70a52c4 100644 --- a/AquaMQTT/src/task/ListenerTask.cpp +++ b/AquaMQTT/src/task/ListenerTask.cpp @@ -8,7 +8,7 @@ namespace aquamqtt { -ListenerTask::ListenerTask() : mBuffer(true, true, true, "listener"), mLastStatisticsUpdate(0){} +ListenerTask::ListenerTask() : mBuffer(true, true, true, true, "listener"), mLastStatisticsUpdate(0){} void ListenerTask::spawn() { diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index 21939d7..c3ca788 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -4,6 +4,7 @@ #include #include "config/Configuration.h" +#include "message/ErrorMessage.h" #include "mqtt/MQTTDefinitions.h" #include "state/HMIStateProxy.h" @@ -335,6 +336,14 @@ void MQTTTask::loop() } } + if ((notify & 1 << 5) != 0) + { + if (DHWState::getInstance().copyFrame(aquamqtt::message::ERROR_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + updateErrorStatus(); + } + } + if (statsUpdate) { updateStats(); @@ -468,7 +477,8 @@ void MQTTTask::updateMainStatus() publishi(MAIN_SUBTOPIC, MAIN_STATE_DEFROST, message.stateDefrost()); } - if(message.statePVChanged()){ + if (message.statePVChanged()) + { publishi(MAIN_SUBTOPIC, MAIN_STATE_PV, message.statePV()); } @@ -513,6 +523,11 @@ void MQTTTask::updateMainStatus() publishi(MAIN_SUBTOPIC, MAIN_CAPABILITY_DRY_HEATING, message.capabilityHasAntiDryHeating()); } + if (message.errorCodeChanged()) + { + publishi(MAIN_SUBTOPIC, MAIN_ERROR_CODE, message.errorCode()); + } + if (config::DEBUG_RAW_SERIAL_MESSAGES) { sprintf(reinterpret_cast(mTopicBuffer), @@ -630,6 +645,20 @@ void MQTTTask::updateHMIStatus() mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); } + // publish the error number to the request id + if (message.errorRequestChanged() && message.errorRequestId() != 0) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + ERROR_ERROR_NUMBER); + itoa(message.errorNumberRequested(), reinterpret_cast(mPayloadBuffer), 10); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } + if (config::DEBUG_RAW_SERIAL_MESSAGES) { sprintf(reinterpret_cast(mTopicBuffer), @@ -707,6 +736,131 @@ void MQTTTask::updateEnergyStats() } } +void MQTTTask::updateErrorStatus() +{ + message::ErrorMessage message(mTransferBuffer); + + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_ERROR_CODE); + itoa(message.errorCode(), reinterpret_cast(mPayloadBuffer), 10); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + if (!message.isEmpty()) + { + sprintf(reinterpret_cast(mPayloadBuffer), + "%d.%d.%d", + message.dateDay(), + message.dateMonth(), + message.dateYear()); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + HMI_DATE); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + sprintf(reinterpret_cast(mPayloadBuffer), "%02d:%02d", message.timeHours(), message.timeMinutes()); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + HMI_TIME); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + dtostrf(message.hotWaterTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_HOT_WATER_TEMP); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + dtostrf(message.airTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_SUPPLY_AIR_TEMP); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + dtostrf(message.evaporatorLowerAirTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_EVAPORATOR_AIR_TEMP_LOWER); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + dtostrf(message.evaporatorUpperAirTemp(), 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_EVAPORATOR_AIR_TEMP_UPPER); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + dtostrf(message.fanSpeedPwm(), 3, 1, reinterpret_cast(mPayloadBuffer)); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + MAIN_FAN_PWM); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + ultoa(message.totalHeatpumpHours(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + ENERGY_TOTAL_HEATPUMP_HOURS); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + + ultoa(message.totalHeatingElemHours(), reinterpret_cast(mPayloadBuffer), 10); + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%u/%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + message.errorRequestId(), + ENERGY_TOTAL_HEATING_ELEM_HOURS); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } + + if (config::DEBUG_RAW_SERIAL_MESSAGES) + { + sprintf(reinterpret_cast(mTopicBuffer), + "%s%S%S%S", + config::mqttPrefix, + BASE_TOPIC, + ERROR_SUBTOPIC, + DEBUG); + + toHexStr(mTransferBuffer, ERROR_MESSAGE_LENGTH, reinterpret_cast(mPayloadBuffer)); + mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer)); + } +} + void MQTTTask::publishFloat(const char* subtopic, const char* topic, float value, bool retained) { dtostrf(value, 3, 1, reinterpret_cast(mPayloadBuffer)); diff --git a/PROTOCOL.md b/PROTOCOL.md index 0589a43..82c99ce 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -1,6 +1,6 @@ -# Protocol +# Protocol -This document reflects the current state of knowledge: **It may contain errors and misassumptions**. +This document reflects the current state of knowledge: **It may contain errors and misassumptions**. ## General @@ -10,6 +10,7 @@ This document reflects the current state of knowledge: **It may contain errors a - Stop-Bits: 2 ### Wiring + The heatpumps HMI controller board is based upon a STM32F103RFT6 and provides a 8-pin (2x4 2,54mm) connector. The original setup needs 4 of those pins, which are connected to the main controller: @@ -23,7 +24,8 @@ The original setup needs 4 of those pins, which are connected to the main contro ## Overview -The protocol uses an unknown binary frame format, with each message consisting of an unique identifier, payload length, payload and checksum. +The protocol uses an unknown binary frame format, with each message consisting of an unique identifier, payload length, +payload and checksum. ### Message Format @@ -33,20 +35,24 @@ The protocol uses an unknown binary frame format, with each message consisting o - 2 bytes checksum (CRC-16) Example Messages: + ``` ID LEN ----------------------------------------------- PAYLOAD ------------------------------------------------------------------ CRC16.. 193 37 235 1 121 0 94 0 102 0 0 0 0 0 1 100 65 81 0 0 0 50 62 0 255 255 255 87 9 66 88 2 0 16 14 1 78 23 254 243 194 35 18 2 66 252 0 240 17 240 6 16 56 0 0 255 255 0 30 147 46 11 11 0 0 255 0 9 66 1 1 255 255 255 255 255 74 199 67 31 0 0 0 0 0 0 0 0 0 0 181 9 0 0 23 0 0 0 181 9 0 0 168 195 18 0 0 0 0 0 25 82 + 74 35 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 18 176 ``` ### Identifier -*There are currently no insights, why they've chosen 193, 194 and 67 and if there is an additional but yet unknown meaning in these identifiers* +*There are currently no insights, why they've chosen 193, 194, 67 and 74 and if there is an additional but yet unknown +meaning in these identifiers* ### Checksum -The two byte checksum value is created using standard CRC-16/CCITT-FALSE. The checksum is created for payload length and the actual payload itself: +The two byte checksum value is created using standard CRC-16/CCITT-FALSE. The checksum is created for payload length and +the actual payload itself: ``` Example Message 67 in decimal and hexadecimal representation: @@ -54,11 +60,15 @@ Example Message 67 in decimal and hexadecimal representation: DEC: 31 0 0 0 0 0 0 0 0 0 0 181 9 0 0 23 0 0 0 181 9 0 0 168 195 18 0 0 0 0 0 25 82 HEX: 1F 00 00 00 00 00 00 00 00 00 00 B5 09 00 00 17 00 00 00 B5 09 00 00 A8 C3 12 00 00 00 00 00 19 52 ``` -In this message we expect an CRC of `0x1952` (last two bytes). Using the CRC-16/CCITT-FALSE algorithm we are able to successfully recreate the CRC values: [See online crc result using crccalc.com](https://crccalc.com/?crc=1F00000000000000000000B509000017000000B5090000A8C3120000000000&method=crc16&datatype=hex&outtype=0) + +In this message we expect an CRC of `0x1952` (last two bytes). Using the CRC-16/CCITT-FALSE algorithm we are able to +successfully recreate the CRC +values: [See online crc result using crccalc.com](https://crccalc.com/?crc=1F00000000000000000000B509000017000000B5090000A8C3120000000000&method=crc16&datatype=hex&outtype=0) ### Messages -Messages are either send from the main or the hmi controller. The main controller is the master of the communication and is repeating a pattern of message identifiers. The message sequence during normal operation is: +Messages are either send from the main or the hmi controller. The main controller is the master of the communication and +is repeating a pattern of message identifiers. The message sequence during normal operation is: ``` 194 (HMI) @@ -70,6 +80,19 @@ Messages are either send from the main or the hmi controller. The main controlle (repeated) ``` +If an error message is requested by the HMI controller, the sequence contains another message with identifier 74. + +``` +194 (HMI) +56ms idle +67 (MAIN) +60ms idle +193 (MAIN) +54ms idle +74 (MAIN) +56ms idle +``` + In case we disconnect the HMI controller during operation, we will notice that the 194 frame is no longer complete: @@ -82,49 +105,51 @@ Therefore, message 194 is completed by the HMI controller, whereas 193 and 67 ar ### Send from the HMI controller -The main controller gives the HMI controller permission to send by emitting the 194 message type on the wire. The HMI controller completes the packet on the one-wire bus. +The main controller gives the HMI controller permission to send by emitting the 194 message type on the wire. The HMI +controller completes the packet on the one-wire bus. #### 194 -| Byte Number | Example | Purpose/Function | Other Information | -|-------------|---------|------------------|-------------------| -| 0 | 35 | Length Field | - | -| 1 - 2 | 18 2 | Target Temp. | See Temperature Table, 53°C | -| 3 | 66 | OperationMode | See Operation Mode Table | -| 4 | 252 | Command: Change PWM, Command: Change Anti-Trockenheizung | See Commands | -| 5 | 0 | Anti-Legionella Mode / AirDuct Mode | 0 == Off, 1 == 1perMonth, 2 == 2perMonth, 3 == 3perMonth, 4 == 4/perMonth, // 0 == AirDuct INT/INT, 16 == AirDuct EXT/INT, 32 == AirDuct EXT/EXT | -| 6 | 240 | Emergency-Mode, Command: Change Connectivity | "Emergency Mode Off == 240, Emergency Mode On == 241", "Disabled Connectivity: No == 240, Disabled Connectivity: Yes == 16" | -| 7 | 17 | InstallationConfig | WP-Only == 0, WP+ExtBoiler-Prio-WP == 1, Wp+ExtBoiler-Opt-WP == 17, Wp+ExtBoiler-Opt-ExtBoiler == 33 , Wp+ExtBoiler-Prio-ExtBoiler == 49 , WP + Solar == 50 | -| 8 | 240 | Command: Change Heat-Exchanger | See Commands | -| 9 | 4 | Heating-Element / SetupState | Heating-Element Automatic-Mode == 4, Heating-Element Disabled == 0, Setup Factory Settings == 164, Setup Airduct Set == 36, Setup Finished == 4 | -| 10 | 16 | Timer Mode: Window 1 Start | 16 = 04:00h, 12 = 03:00h | -| 11 | 56 | Timer Mode: Window 1 Length | 52 = 13h runtime, 56 = 14h runtime| -| 12 | 0 | Timer Mode: Window 2 Start | e.g. 52 = 13:00h - Value 0x00 0x00 is supported if Timer 2 is not set. | -| 13 | 0 | Timer Mode: Window 2 Length | e.g. 16 = 04h runtime. Max allowed length of both timers in sum is 14h runtime| -| 14 | 255 | Command: Change PV-Input, Change Circulation | - | -| 15 | 255 | Command: Change Min Target Temperature | Note: Range 40-50°C | -| 16 | 0 | ? | - | -| 17 | 25 | Current Time Seconds | - | -| 18 | 151 | Current Day, Current Month in Half-Year | See Formula below | -| 19 | 46 | Current Year, Half-Year | See Formula below | -| 20 | 11 | Current Time Minutes | - | -| 21 | 13 | Current Time Hour | - | -| 22 | 0 | TestMode Status | HMI Left TestMode == 0, HMI Entered TestMode == 1, Heatpump TestMode == 2, Heating-Element TestMode == 3, Fan-Slow TestMode == 4 , Fan-Fast TestMode == 5 , Defrost TestMode == 6, Heatpump + EXT Boiler TestMode == 8 -| 23 | 0 | ? | - | -| 24 | 255 | Command: Change PWM Value, Contains PWM Setpoint | - | -| 25 | 0 | ? | - | -| 26 | 9 | ? | - | -| 27 | 66 | ? | - | -| 28 | 0 | ? | - | -| 29 | 0 | ? | - | -| 30 | 255 | Command: Change Anti-Legionalla Temperature Target | See Commands. Note: Valid Range 62-70°C | -| 31 | 255 | Command: Change Installed Wattage Heat Element | See Commands. Note: Valid Range 10-30 (1000 - 3000W) | -| 32 | 255 | Command: Change Brand of Heat-Pump | See Commands. | -| 33 - 34 | 255 255 | Command: Change Boiler Capacity | See Commands. | +| Byte Number | Example | Purpose/Function | Other Information | +|-------------|---------|----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0 | 35 | Length Field | - | +| 1 - 2 | 18 2 | Target Temp. | See Temperature Table, 53°C | +| 3 | 66 | OperationMode | See Operation Mode Table | +| 4 | 252 | Command: Change PWM, Command: Change Anti-Trockenheizung | See Commands | +| 5 | 0 | Anti-Legionella Mode / AirDuct Mode | 0 == Off, 1 == 1perMonth, 2 == 2perMonth, 3 == 3perMonth, 4 == 4/perMonth, // 0 == AirDuct INT/INT, 16 == AirDuct EXT/INT, 32 == AirDuct EXT/EXT | +| 6 | 240 | Emergency-Mode, Command: Change Connectivity | "Emergency Mode Off == 240, Emergency Mode On == 241", "Disabled Connectivity: No == 240, Disabled Connectivity: Yes == 16" | +| 7 | 17 | InstallationConfig | WP-Only == 0, WP+ExtBoiler-Prio-WP == 1, Wp+ExtBoiler-Opt-WP == 17, Wp+ExtBoiler-Opt-ExtBoiler == 33 , Wp+ExtBoiler-Prio-ExtBoiler == 49 , WP + Solar == 50 | +| 8 | 240 | Command: Change Heat-Exchanger | See Commands | +| 9 | 4 | Heating-Element / SetupState / PV allowed | Heating-Element Automatic-Mode == 4, Heating-Element Disabled == 0, Setup Factory Settings == 164, Setup Airduct Set == 36, Setup Finished == 4 ... and PV allowed but not enabled = 4, PV allowed and enabled = 6 | +| 10 | 16 | Timer Mode: Window 1 Start | 16 = 04:00h, 12 = 03:00h | +| 11 | 56 | Timer Mode: Window 1 Length | 52 = 13h runtime, 56 = 14h runtime | +| 12 | 0 | Timer Mode: Window 2 Start | e.g. 52 = 13:00h - Value 0x00 0x00 is supported if Timer 2 is not set. | +| 13 | 0 | Timer Mode: Window 2 Length | e.g. 16 = 04h runtime. Max allowed length of both timers in sum is 14h runtime | +| 14 | 255 | Command: Change PV-Input, Change Circulation | - | +| 15 | 255 | Command: Change Min Target Temperature | Note: Range 40-50°C | +| 16 | 0 | Command: Clear Alarm (Error Beeps) | 0 == Noop, 4 == Clear Alarm / Errors | +| 17 | 25 | Current Time Seconds | - | +| 18 | 151 | Current Day, Current Month in Half-Year | See Formula below | +| 19 | 46 | Current Year, Half-Year | See Formula below | +| 20 | 11 | Current Time Minutes | - | +| 21 | 13 | Current Time Hour | - | +| 22 | 0 | TestMode Status | HMI Left TestMode == 0, HMI Entered TestMode == 1, Heatpump TestMode == 2, Heating-Element TestMode == 3, Fan-Slow TestMode == 4 , Fan-Fast TestMode == 5 , Defrost TestMode == 6, Heatpump + EXT Boiler TestMode == 8 | +| 23 | 0 | ? | - | +| 24 | 255 | Command: Change PWM Value, Contains PWM Setpoint | - | +| 25 | 0 | ? | - | +| 26 | 9 | ? | - | +| 27 | 66 | ? | - | +| 28 | 0 | Command: Request Error Message / Error Number | Error Number (1..x) | +| 29 | 0 | Command: Request Error Message / Request Id | Upcounting (1..255 | +| 30 | 255 | Command: Change Anti-Legionalla Temperature Target | See Commands. Note: Valid Range 62-70°C | +| 31 | 255 | Command: Change Installed Wattage Heat Element | See Commands. Note: Valid Range 10-30 (1000 - 3000W) | +| 32 | 255 | Command: Change Brand of Heat-Pump | See Commands. | +| 33 - 34 | 255 255 | Command: Change Boiler Capacity | See Commands. | ##### Byte No. 3: Operation Mode Findings... + ``` 2dec | 0000 0010: AlwaysOn + ECO Inactive 3dec | 0000 0011: AlwaysOn + Boost @@ -135,25 +160,25 @@ Findings... 68dec | 0100 0100: TimerMode + AUTO ``` - -| Bit Number | Purpose/Function | Other Information | -|------------|-------------------------|-------------------| -| 0 - 3 | Operation Mode | Interpreted as integer, 0 == Absence, 1 == Eco Active, 2 == Eco Inoactive, 3 == Boost, 4 == Auto | -| 4 | ? | | -| 5 | ? | | -| 6 | TimerMode enabled | | -| 7 | ? | | - +| Bit Number | Purpose/Function | Other Information | +|------------|-------------------|--------------------------------------------------------------------------------------------------| +| 0 - 3 | Operation Mode | Interpreted as integer, 0 == Absence, 1 == Eco Active, 2 == Eco Inoactive, 3 == Boost, 4 == Auto | +| 4 | ? | | +| 5 | ? | | +| 6 | TimerMode enabled | | +| 7 | ? | | ##### Temperature Values -Temperature values have an accurancy of a single decimal place. The two bytes are interpreted as an uint16 value and divided by 10: +Temperature values have an accuracy of a single decimal place. The two bytes are interpreted as an uint16 value and +divided by 10: `` float temperature = (float) (((int16_t) message[2] << 8) | message[1]) / 10.0; `` Example Table: + ``` 184 1 = 44,0°C 194 1 = 45,0°C @@ -194,15 +219,18 @@ Example Table (Byte 19): 46dec = 2023 (First Half) 47dec = 2023 (Second Half) ``` + #### Commands -Commands are executed by the HMI as soon as placeholder fields/values are replaced by command values. The HMI Controller awaits the change of the Main controller and then resets the placeholder fields to the previous placeholder value. +Commands are executed by the HMI as soon as placeholder fields/values are replaced by command values. The HMI Controller +awaits the change of the Main controller and then resets the placeholder fields to the previous placeholder value. ##### Change Capactiy - Affected Byte Positions: 33, 34 Examples: + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 270l: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 14 1 @@ -245,6 +273,7 @@ Thermor: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 ##### Enable/Disable Heat-Exchanger - Affected Byte Positions: 8 + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 Yes: 35 18 2 65 252 0 240 32 16 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 @@ -254,6 +283,7 @@ No: 35 18 2 65 252 0 240 32 0 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 ##### Enable/Disable Communication - Affected Byte Positions: 6 + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 Not Disabled: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 @@ -263,6 +293,7 @@ Disabled: 35 18 2 65 252 0 16 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 ##### Enable/Disable Circulation - Affected Byte Positions: 14 + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 No: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 240 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 @@ -272,14 +303,13 @@ Yes: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 241 255 0 x x x x x 0 0 255 0 9 ##### Enable/Disable PV-Input - Affected Byte Positions: 14 - + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 No: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 16 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 Yes: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 17 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 ``` - ##### Change Min Target Temperature - Affected Byte Positions: 16 @@ -293,7 +323,6 @@ Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 ##### Enable/Disable Anti-Trockenheizung - - Affected Byte Positions: 4 ``` @@ -308,6 +337,7 @@ Disabled: 35 18 2 65 132 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 - Range: 62-70°C Examples: + ``` Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 62°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 62 255 255 255 255 @@ -328,48 +358,75 @@ Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 3000W: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 30 255 255 255 ``` +##### Clear Alarm / Clear Error + +If an error occurred which is recoverable, the hmi offers the functionality to "stop the alarm". This will resume the +operation of the heatpump and clears the error. + +- Affected Byte Position: 16 + +``` +Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +Clear Alarm: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 4 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 +``` + +##### Request Error Message + +- Affected Byte Positions: 27 - 28 +- Triggers the main controller to emit a single message 74 (error message) +- RequestId is incremented for each request, starting from zero, most likely with wrap around. +- The error number addresses the list of errors, the most recent error is id==1 +- If an error number is addressed which is not existent, the main controller will respond with an empty error message ( + zeroed) + +``` +Noop: 35 18 2 65 252 0 240 32 240 6 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 0 0 255 255 255 255 255 +Request Error No 1 with Request Id: 76 35 18 2 65 252 0 240 32 240 6 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 1 76 255 255 255 255 255 +Request Error No 2 with Request Id: 77 35 18 2 65 252 0 240 32 240 6 60 16 16 24 255 255 0 x x x x x 0 0 255 0 9 66 2 77 255 255 255 255 255 +``` + ### Send by the main controller #### 193 -| Byte Number | Example | Purpose/Function | Other Information | -|-------------|---------|------------------|-------------------| -| 0 | 37 | Length Field | - | -| 1 - 2 | 235 1 | Hot Water Temp. | Note: Range Allowed 18 - 62°C | -| 3 - 4 | 121 0 | Input Air Temp | Note: Range Allowed 10 - 27°C | -| 5 - 6 | 94 0 | Lower Evaporator Temp | Note: Range Allowed -2 - 41°C | -| 7 - 8 | 102 0 | Upper Evaporator Temp | Note: Range Allowed -3 - 39°C | -| 9 | 0 | ? | - | -| 10 | 0 | ? | - | -| 11 | 0 | ? | - | -| 12 | 0 | ? | - | -| 13 | 1 | ? | - | -| 14 | 100 | Setting: PWM Level 3 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller*| -| 15 | 65 | Setting: PWM Level 1 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | -| 16 | 81 | Setting: PWM Level 2 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | -| 17 | 8 or 0 | Picture Bitmask | See table below | -| 18 - 19 | 0 0 | Fan-Speed | Either 0 (off) or 650 (lowspeed) or 810 (highspeed) Maybe rpm? Does this reflect the PWM settings?| -| 20 | 44 | Setting: Min T Target | *Adjustable from HMI controller* | -| 21 | 62 | Setting: Anti-Legionella T Target | *Adjustable from HMI controller* | -| 22 | 0 | ? | - | -| 23 | 255 | Errors? | In Error State this contains Error Code, eg. 7 | -| 24 | 255 | Errors? | In Error State this contains 0 | -| 25 | 255 | Errors? | In Error State this contains 0 | -| 26 | 87 | ? | - | -| 27 | 9 | ? | - | -| 28 | 66 | ? | - | -| 29 | 88 | ? | - | -| 30 | 2 | ? | - | -| 31 | 0 | ? | - | -| 32 | 16 | Setting: Wattage Heat-Element | Note: Multiplied by 100 -> 1600W, *Adjustable from HMI controller* | -| 33 - 34 | 14 1 | Setting: Boiler Capacity (l) | '14 1' == 270l, '200 0' == 200l, *Adjustable from HMI controller* | -| 35 | 78 | Setting: Brand | 65 == Atlantic, 78 == NoName, 83 == Sauter, 84 == Thermor, *Adjustable from HMI controller* | -| 36 | 21 | Setting: Bitflags | See Table below, *Adjustable from HMI controller*| - +| Byte Number | Example | Purpose/Function | Other Information | +|-------------|---------|-----------------------------------|----------------------------------------------------------------------------------------------------| +| 0 | 37 | Length Field | - | +| 1 - 2 | 235 1 | Hot Water Temp. | Note: Range Allowed 18 - 62°C | +| 3 - 4 | 121 0 | Input Air Temp | Note: Range Allowed 10 - 27°C | +| 5 - 6 | 94 0 | Lower Evaporator Temp | Note: Range Allowed -2 - 41°C | +| 7 - 8 | 102 0 | Upper Evaporator Temp | Note: Range Allowed -3 - 39°C | +| 9 | 0 | ? | - | +| 10 | 0 | ? | - | +| 11 | 0 | ? | - | +| 12 | 0 | ? | - | +| 13 | 1 | ? | - | +| 14 | 100 | Setting: PWM Level 3 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | +| 15 | 65 | Setting: PWM Level 1 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | +| 16 | 81 | Setting: PWM Level 2 | Note: Range Allowed in HMI 50-100%, *Adjustable from HMI controller* | +| 17 | 8 or 0 | Picture Bitmask | See table below | +| 18 - 19 | 0 0 | Fan-Speed | Either 0 (off) or 650 (lowspeed) or 810 (highspeed) Maybe rpm? Does this reflect the PWM settings? | +| 20 | 44 | Setting: Min T Target | *Adjustable from HMI controller* | +| 21 | 62 | Setting: Anti-Legionella T Target | *Adjustable from HMI controller* | +| 22 | 0 | State: PV-Input | 16 == PV Enabled and Active! | +| 23 | 255 | Error-Code | In Error State this contains Error Code, eg. 7 | +| 24 | 255 | Error-Code | In Error State this contains 0 | +| 25 | 255 | Error-Code | In Error State this contains 0 | +| 26 | 87 | ? | - | +| 27 | 9 | ? | - | +| 28 | 66 | ? | - | +| 29 | 88 | ? | - | +| 30 | 2 | ? | - | +| 31 | 0 | ? | - | +| 32 | 16 | Setting: Wattage Heat-Element | Note: Multiplied by 100 -> 1600W, *Adjustable from HMI controller* | +| 33 - 34 | 14 1 | Setting: Boiler Capacity (l) | '14 1' == 270l, '200 0' == 200l, *Adjustable from HMI controller* | +| 35 | 78 | Setting: Brand | 65 == Atlantic, 78 == NoName, 83 == Sauter, 84 == Thermor, *Adjustable from HMI controller* | +| 36 | 21 | Setting: Bitflags | See Table below, *Adjustable from HMI controller* | ##### Byte No 17: Picture Bitmask Findings... + ``` 0dec | 0000 0000: Nothing Shown on HMI 8dec | 0000 1000: Fan is turned on (observed via testmode) @@ -386,20 +443,21 @@ Findings... 192dec | 1100 0000: Unknown, Observed while triggering Error 7 ``` -| Bit Number | Purpose/Function | Other Information | -|------------|-------------------------|-------------------| -| 0 | Heating Element On/Off | | -| 1 | Heatpump On/Off | | -| 2 | Boiler Backup On/Off | | -| 3 | Fan On/Off. | | -| 4 | ? | | -| 5 | Defrost On/Off | | -| 6 | ? | | -| 7 | ? | | +| Bit Number | Purpose/Function | Other Information | +|------------|------------------------|-------------------| +| 0 | Heating Element On/Off | | +| 1 | Heatpump On/Off | | +| 2 | Boiler Backup On/Off | | +| 3 | Fan On/Off. | | +| 4 | ? | | +| 5 | Defrost On/Off | | +| 6 | ? | | +| 7 | ? | | ##### Byte No 36: Settings Bitmask Findings... + ``` 5dec | 0000 0101: Communication Enabled, PV enabled, No Circulation, Anti-Trockenheizung 17dec | 0001 0001: Communication Enabled, PV disabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung @@ -409,31 +467,90 @@ Findings... 29dec | 0001 1101: Communication Disabled, PV enabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung ``` -| Bit Number | Purpose/Function | Other Information | -|------------|-------------------------------|-------------------| -| 0 | Heat-Exchanger Available | | -| 1 | Circulation Enabled | | -| 2 | PV Input Enabled | If flag is set, pv input is enabled | -| 3 | Communication Disabled | If flag is set, io-homecontrol/cozytouch is disabled| -| 4 | Anti-Trockenheizung Disabled | If flag is set, there is no anti-trockenheizung | -| 5 | ? / Unused | | -| 6 | ? / Unused | | -| 7 | ? / Unused | | +| Bit Number | Purpose/Function | Other Information | +|------------|---------------------------|------------------------------------------------------| +| 0 | Heat-Exchanger Available | | +| 1 | Circulation Enabled | | +| 2 | PV Input Enabled | If flag is set, pv input is enabled | +| 3 | Communication Disabled | If flag is set, io-homecontrol/cozytouch is disabled | +| 4 | Anti-Dry-Heating Disabled | If flag is set, there is no Anti-Dry-Heating | +| 5 | ? / Unused | | +| 6 | ? / Unused | | +| 7 | ? / Unused | | #### 67 -| Byte Number | Example | Purpose/Function | Other Information | -|-------------|---------------------|------------------|-------------------| -| 0 | 31 | Length Field | - | -| 1 - 2 | 221 1 | Power Consumption Heatpump | - | -| 3 - 4 | 0 0 | Power Consumption Heating Element | - | -| 5 | 0 | ? | - | -| 6 | 0 | ? | - | -| 7 - 8 | 221 1 | Power Consumption Total (Both) | - | -| 9 | 54 | Unknown Value A, First Byte | Is Counting upwards till 255 255, then reseting to 0 0. | -| 10 | 215 | Unknown Value A, Second Byte| Seems value is not rising when heatpump is idle. Heating element has no influence | -| 11 - 14 | 231 9 0 0 | Total Operation Hours (Heatpump) | - | -| 15 - 18 | 24 0 0 0 | Total Operation Hours (Heating Element) | - | -| 19 - 22 | 231 9 0 0 | Total Operation Hours (Both) | - | -| 23 - 30 | 127 37 19 0 0 0 0 0 | Total Energy Counter (Wh) | - | - +| Byte Number | Example | Purpose/Function | Other Information | +|-------------|---------------------|-----------------------------------------|-------------------| +| 0 | 31 | Length Field | - | +| 1 - 2 | 221 1 | Power Consumption Heatpump | - | +| 3 - 4 | 0 0 | Power Consumption Heating Element | - | +| 5 | 0 | ? | - | +| 6 | 0 | ? | - | +| 7 - 8 | 221 1 | Power Consumption Total (Both) | - | +| 9 | 54 | ? | - | +| 10 | 215 | ? | - | +| 11 - 14 | 231 9 0 0 | Total Operation Hours (Heatpump) | - | +| 15 - 18 | 24 0 0 0 | Total Operation Hours (Heating Element) | - | +| 19 - 22 | 231 9 0 0 | Total Operation Hours (Both) | - | +| 23 - 30 | 127 37 19 0 0 0 0 0 | Total Energy Counter (Wh) | - | + +#### 74 Error Message + +Error messages are emitted by the main controller if the HMI is requesting error messages. + +- Any change to byte 27 and byte 28 of the HMI (194) message will trigger a new error message. +- The main controller is emitting empty error messages if HMI is requesting placeholder values (errorNumber==0, + requestId==0) or if an invalid errorNumber has been requested. +- Entering Secret Diagnosis Menu: HMI will fetch all existing errors + +To be clarified: + +- Most likely the HMI controller will request an error message if the error bitflag has been set. +- Most likely the MAIN controller will never emit an error message, without an HMI controller asking for it. +- Check if errorId == 0 contains an valid error during an error state of if is reserved to be empty. + + + +| Byte Number | Example | Purpose/Function | Other Information | +|-------------|---------|----------------------|---------------------------------------| +| 0 | 35 | Length Field | - | +| 1 | 3 | Request-Id | Matches original request from HMI | +| 2 | 7 | Error-Code | - | +| 3 | 0 | ? | - | +| 4 - 5 | 62 2 | Water-Temperature | - | +| 6 - 7 | 213 0 | Air-Temperature | - | +| 8 - 9 | 212 0 | Upper Evaporator | - | +| 10 - 11 | 206 0 | Lower Evaporator | - | +| 12 | 0 | ? | - | +| 13 | 0 | ? | - | +| 14 | 0 | ? | - | +| 15 | 0 | ? | - | +| 16 | 0 | State Flags | 6 == HeatPump Active + Fan (?) | +| 17 | 0 | ? | - | +| 18 | 0 | ? | - | +| 19 | 0 | ? | - | +| 20 | 0 | ? | - | +| 21 | 0 | Fan-Speed PWM | - | +| 22 | 0 | ? | - | +| 23 | 0 | ? | - | +| 24 | 0 | ? | - | +| 25 | 252 | Runtime Heat-Element | - | +| 26 | 0 | Runtime Heat-Element | - | +| 27 | 146 | Runtime Heat-Pump | - | +| 28 | 19 | Runtime Heat-Pump | - | +| 29 | 185 | Date | - | +| 30 | 48 | Date | - | +| 31 | 29 | Time: Minutes | - | +| 32 | 21 | Time: Hours | - | +| 33 | 1 | Operation Mode | 0 == Absence, 1 == Eco/Manuell EC ... | +| 34 | 4 | ? | - | \ No newline at end of file From ea61601b4186c428ab1ffa31dfc90b60600890ec Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Mon, 27 May 2024 22:32:16 +0200 Subject: [PATCH 10/12] doc(mqtt): update mqtt topics --- MQTT.md | 175 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 104 insertions(+), 71 deletions(-) diff --git a/MQTT.md b/MQTT.md index 6223cb1..e7df84c 100644 --- a/MQTT.md +++ b/MQTT.md @@ -1,9 +1,8 @@ # MQTT - ## Overall -The mqtt prefix is configurable within `AquaMQTT/include/config/ExampleConfiguration.h`: +The mqtt prefix is configurable within `AquaMQTT/include/config/ExampleConfiguration.h`: ```c++ constexpr char mqttPrefix[] = "prefix/"; @@ -15,88 +14,122 @@ Using the prefix, the `$root` topic is created, which is `$prefix/aquamqtt/` and ### AquaMQTT / Statistics -| Value | MQTT Topic | Format | Unit | Other Information | -|----------------------------------|---------------------------------------|--------|------|--------------------------------------------------| -| Last Will | `$root/stats/lwlState` | Enum | | ONLINE, OFFLINE -- retained| -| OperationMode | `$root/stats/aquamqttMode` | Enum | | LISTENER, MITM | -| Active Overrides | `$root/stats/activeOverrides` | json | | Active Overrides are flagged either with 1 (overriden) or 0 (not overriden) Example Payload: `{ "operationMode": 0, "operationType": 0, "waterTempTarget": 0, "heatingElementEnabled": 0, "emergencyModeEnabled": 0, "configInstallation": 0 , "time/date": 1 }` | -| Override Modes | `$root/stats/overrideMode` | Enum | | `STANDARD`, `PV HP`, `PV HE` or `PV BOOST`. See [README-PV.md](/README-PV.md) for additional information. | -| Flag PV heat pump | `$root/stats/flagPVModeHeatPump` | bool | | Status of the pv heat pump flag. See [README-PV.md](/README-PV.md) for additional information. | -| Flag PV heat element | `$root/stats/flagPVModeHeatElement` | bool | | Status of the pv heat element flag. See [README-PV.md](/README-PV.md) for additional information. | -| IP Address | `$root/stats/ipAddress` | string | | e.g. 192.168.188.62 | -| RSSI | `$root/stats/rssiDb` | int | dB | | -| Messages OK | `$root/stats/$channel/msgHandled` | uint64 | | | -| Messages IGNORED | `$root/stats/$channel/msgUnhandled` | uint64 | | | -| Messages CRC NOK | `$root/stats/$channel/msgCRCNOK` | uint64 | | | -| Dropped Bytes | `$root/stats/$channel/droppedBytes` | uint64 | | | - +| Value | MQTT Topic | Format | Unit | Other Information | +|----------------------|-------------------------------------|--------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Last Will | `$root/stats/lwlState` | Enum | | ONLINE, OFFLINE -- retained | +| OperationMode | `$root/stats/aquamqttMode` | Enum | | LISTENER, MITM | +| Active Overrides | `$root/stats/activeOverrides` | json | | Active Overrides are flagged either with 1 (overridden) or 0 (not overridden) Example Payload: `{ "operationMode": 0, "operationType": 0, "waterTempTarget": 0, "heatingElementEnabled": 0, "emergencyModeEnabled": 0, "configInstallation": 0 , "time/date": 1 }` | +| Override Modes | `$root/stats/overrideMode` | Enum | | `STANDARD`, `PV HP`, `PV HE` or `PV BOOST`. See [README-PV.md](/README-PV.md) for additional information. | +| Flag PV heat pump | `$root/stats/flagPVModeHeatPump` | bool | | Status of the pv heat pump flag. See [README-PV.md](/README-PV.md) for additional information. | +| Flag PV heat element | `$root/stats/flagPVModeHeatElement` | bool | | Status of the pv heat element flag. See [README-PV.md](/README-PV.md) for additional information. | +| IP Address | `$root/stats/ipAddress` | string | | e.g. 192.168.188.62 | +| RSSI | `$root/stats/rssiDb` | int | dB | | +| Messages OK | `$root/stats/$channel/msgHandled` | uint64 | | | +| Messages IGNORED | `$root/stats/$channel/msgUnhandled` | uint64 | | | +| Messages CRC NOK | `$root/stats/$channel/msgCRCNOK` | uint64 | | | +| Dropped Bytes | `$root/stats/$channel/droppedBytes` | uint64 | | | `$channel` is either `hmi` and `main` or `listener` depending on the AquaMQTT operation mode. - ### HMI Message -| Value | MQTT Topic | Format | Unit | Other Information | -|----------------------------------|--------------------------------------|--------|------|--------------------------------------------------| -| Target Water Temperature | `$root/hmi/waterTempTarget` | float | °C | | -| Operation Mode | `$root/hmi/operationMode` | Enum | | AUTO, MAN ECO ON, MAN ECO OFF, BOOST, ABSENCE | -| Operation Type | `$root/hmi/operationType` | Enum | | TIME WINDOW, ALWAYS ON | -| Operation Time Window A | `$root/hmi/timerWindowA` | string | | e.g. 22:00-06:00 | -| Operation Time Window B | `$root/hmi/timerWindowB` | string | | e.g. 04:00-08:00 | -| Current Time | `$root/hmi/time` | string | | e.g 09:11:59 | -| Current Date | `$root/hmi/date` | string | | e.g 07.09.2023 | -| Emergeny Mode Enabled | `$root/hmi/emergencyMode` | bool | | | -| Heating Element Enabled | `$root/hmi/heatingElementEnabled` | bool | | | -| Anti-Legionella Mode | `$root/hmi/antiLegionellaPerMonth` | uint8 | | 0 (off), 1 (once per month), etc | -| Configuration AirDuct | `$root/hmi/configAirduct` | Enum | | INT/INT, INT/EXT, EXT/EXT | -| Configuration Installation | `$root/hmi/configInstallation` | Enum | | | -| HMI SetupState | `$root/hmi/setupState` | Enum | | OK, PARTIAL RESET, FACTORY RESET | -| Raw Message (Debug Mode Only) | `$root/hmi/debug` | string | | | +| Value | MQTT Topic | Format | Unit | Other Information | +|-------------------------------|------------------------------------|--------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Water Temperature | `$root/hmi/waterTempTarget` | float | °C | | +| Operation Mode | `$root/hmi/operationMode` | Enum | | AUTO, MAN ECO ON, MAN ECO OFF, BOOST, ABSENCE | +| Operation Type | `$root/hmi/operationType` | Enum | | TIME WINDOW, ALWAYS ON | +| Operation Time Window A | `$root/hmi/timerWindowA` | string | | e.g. 22:00-06:00 | +| Operation Time Window B | `$root/hmi/timerWindowB` | string | | e.g. 04:00-08:00 | +| Current Time | `$root/hmi/time` | string | | e.g 09:11:59 | +| Current Date | `$root/hmi/date` | string | | e.g 07.09.2023 | +| Emergeny Mode Enabled | `$root/hmi/emergencyMode` | bool | | | +| Heating Element Enabled | `$root/hmi/heatingElementEnabled` | bool | | | +| Anti-Legionella Mode | `$root/hmi/antiLegionellaPerMonth` | uint8 | | 0 (off), 1 (once per month), etc | +| Configuration AirDuct | `$root/hmi/configAirduct` | Enum | | INT/INT, INT/EXT, EXT/EXT | +| Configuration Installation | `$root/hmi/configInstallation` | Enum | | HEAT PUMP ONLY, BOILER BACKUP / HEAT PUMP PRIORITY, BOILER BACKUP / HEAT PUMP OPTIMIZED, BOILER BACKUP / BOILER OPTIMIZED, BOILER BACKUP / BOILER PRIORITY, HEAT PUMP AND SOLAR BACKUP | +| HMI SetupState | `$root/hmi/setupState` | Enum | | OK, PARTIAL RESET, FACTORY RESET | +| PV Input Activated | `$root/hmi/pvInputActivated` | bool | | | +| Raw Message (Debug Mode Only) | `$root/hmi/debug` | string | | | ### Main Message -| Value | MQTT Topic | Format | Unit | Other Information | -|----------------------------------|-------------------------------------|--------|------|--------------------------------------------------| -| Current Water Temperature | `$root/main/waterTemp` | float | °C | | -| Supply Air Temperature | `$root/main/supplyAirTemp` | float | °C | | -| Lower Evaporator Air Temperature | `$root/main/evaporatorAirTempLower` | float | °C | | -| Upper Evaporator Air Temperature | `$root/main/evaporatorAirTemp` | float | °C | | -| Fan Speed | `$root/main/fanSpeed` | uint16 | rpm | | -| State: Fan On/Off | `$root/main/stateFan` | bool | | | -| State: Heatpump On/Off | `$root/main/stateHeatpump` | bool | | | -| State: Heating Element On/Off | `$root/main/stateElement` | bool | | | -| State: Boiler Backup On/Off | `$root/main/stateExtBoiler` | bool | | | -| State: Defrost On/Off Date | `$root/main/stateDefrost` | bool | | | -| Raw Message (Debug Mode Only) | `$root/main/debug` | string | | | +| Value | MQTT Topic | Format | Unit | Other Information | +|------------------------------------|---------------------------------------|--------|------|---------------------------------------------------------------------| +| Present Error Code | `$root/main/errorCode` | uint8 | | 0 if there is no present error, else the error-code | +| Current Water Temperature | `$root/main/waterTemp` | float | °C | | +| Supply Air Temperature | `$root/main/supplyAirTemp` | float | °C | | +| Lower Evaporator Air Temperature | `$root/main/evaporatorAirTempLower` | float | °C | | +| Upper Evaporator Air Temperature | `$root/main/evaporatorAirTemp` | float | °C | | +| ~~Fan Speed~~ | ~~`$root/main/fanSpeed`~~ | uint16 | rpm | Removed since the message contained the pwm level and no rpm value. | +| Fan-Level PWM | `$root/main/fanPWM` | float | % | | +| State: Fan On/Off | `$root/main/stateFan` | bool | | | +| State: Heatpump On/Off | `$root/main/stateHeatpump` | bool | | | +| State: Heating Element On/Off | `$root/main/stateElement` | bool | | | +| State: Boiler Backup On/Off | `$root/main/stateExtBoiler` | bool | | | +| State: Defrost On/Off Date | `$root/main/stateDefrost` | bool | | | +| State: PV Input Signal Detected | `$root/main/statePV` | bool | | | +| Setting: 1st Fan-Level PWM | `$root/main/settingPWM_1` | uint8 | % | | +| Setting: 2nd Fan-Level PWM | `$root/main/settingPWM_2` | uint8 | % | | +| Setting: 3rd Fan-Level PWM | `$root/main/settingPWM_3` | uint8 | % | | +| Setting: Min. Target Temp | `$root/main/settingMinTargetTemp` | uint8 | °C | | +| Setting: Min. Anti-Legionella Temp | `$root/main/settingMinLegionellaTemp` | uint8 | °C | | +| Setting: Wattage Heat Element | `$root/main/settingWattageElement` | uint16 | W | | +| Setting: Boiler Capacity | `$root/main/settingBoilerCapacity` | uint16 | l | | +| Setting: Boiler Brand | `$root/main/settingBoilerBrand` | Enum | | Atlantic, Thermor, Sauter, No Name | +| Capability: Has Heat Exchanger | `$root/main/capabilityHeatExchanger` | bool | | | +| Capability: Has Circulation | `$root/main/capabilityCirculation` | bool | | | +| Capability: Has PV-Input | `$root/main/capabilityPVInput` | bool | | | +| Capability: Has Communication | `$root/main/capabilityCommunication` | bool | | | +| Capability: Has Anti-Dry-Heating | `$root/main/capabilityAntiDryHeating` | bool | | | +| Raw Message (Debug Mode Only) | `$root/main/debug` | string | | | ### Energy Message - -| Value | MQTT Topic | Format | Unit | Other Information | -|----------------------------------|---------------------------------------|--------|------|--------------------------------------------------| -| Total Heatpump Hours | `$root/energy/totalHeatpumpHours` | uint32 | h | retained | -| Total Heating Element Hours | `$root/energy/totalHeatingElemHours` | uint32 | h | retained | -| Total Hours | `$root/energy/totalHours` | uint32 | h | retained | -| Total Energy | `$root/energy/totalEnergyWh` | uint64 | Wh | | -| Current Power Heatpump | `$root/energy/powerHeatpump` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | -| Current Power Heating Element | `$root/energy/powerHeatingElem` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | -| Current Power Total | `$root/energy/powerTotal` | uint16 | W | | -| Raw Message (Debug Mode Only) | `$root/energy/debug` | string | | | +| Value | MQTT Topic | Format | Unit | Other Information | +|-------------------------------|--------------------------------------|--------|------|------------------------------------------------------------------------------------------------------------| +| Total Heatpump Hours | `$root/energy/totalHeatpumpHours` | uint32 | h | retained | +| Total Heating Element Hours | `$root/energy/totalHeatingElemHours` | uint32 | h | retained | +| Total Hours | `$root/energy/totalHours` | uint32 | h | retained | +| Total Energy | `$root/energy/totalEnergyWh` | uint64 | Wh | | +| Current Power Heatpump | `$root/energy/powerHeatpump` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | +| Current Power Heating Element | `$root/energy/powerHeatingElem` | uint16 | W | Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | +| Current Power Total | `$root/energy/powerTotal` | uint16 | W | | +| Raw Message (Debug Mode Only) | `$root/energy/debug` | string | | | + +### Error Messages + +Collected Error Messages are provided to mqtt, the latest/recent occurred error is always error number == 1. The values +within the message reflect the state when the error has been created. + +| Value | MQTT Topic | Format | Unit | Other Information | +|----------------------------------|-------------------------------------------------|--------|------|-------------------| +| Error Number | `$root/error/%requestId/errorNumber` | uint8 | | | +| Error Code | `$root/error/%requestId/errorCode` | uint8 | | | +| Date | `$root/error/%requestId/date` | string | | e.g. 25.5.2024 | +| Time (hh:ss) | `$root/error/%requestId/time` | string | | e.g. 21:29 | +| Water Temperature | `$root/error/%requestId/waterTemp` | float | | | +| Lower Evaporator Air Temperature | `$root/error/%requestId/evaporatorAirTempLower` | float | | | +| Upper Evaporator Air Temperature | `$root/error/%requestId/evaporatorAirTemp` | float | | | +| Fan-Level PWM | `$root/error/%requestId/fanPWM` | uint8 | % | | +| Total Heatpump Hours | `$root/error/%requestId/totalHeatpumpHours` | uint16 | | | +| Total Heating Element Hours | `$root/error/%requestId/totalHeatingElemHours` | uint16 | | | +| Raw Message (Debug Mode Only) | `$root/error/debug` | string | | | ## Subscribe Topics -Using this topics you may override the HMI Controller in AquaMQTT OperationMode Man-In-The-Middle. Currently these are the only ones implemented: - -| Value / Action | MQTT Topic | Format | Unit | Example Payload | Other Information | -|----------------------------------|----------------------------------|--------|------|-----------------|-------------------------------------------------| -| Target Water Temperature | `$root/ctrl/waterTempTarget` | float | °C | "`55.0`" | Overrides the water temperature target: Allowed range is from 20°C to 62°C. | -| Operation Mode | `$root/ctrl/operationMode` | Enum | | "`BOOST`" | Overrides the operation mode, may affect the waterTempTarget. For example BOOST and ABSENCE will automatically set the waterTempTarget accordingly. | -| Operation Type | `$root/ctrl/operationType` | Enum | | "`ALWAYS ON`" | Overrides the operation type | -| Installation Config | `$root/ctrl/configInstallation` | Enum | | "`HEAT PUMP ONLY`", "`BOILER BACKUP / HEAT PUMP PRIORITY"`", "`...`" | Overrides the installation config | -| Enable or disable heating element | `$root/ctrl/heatingElementEnabled` | bool | | "`1`" | Allow the DHW heat pump to use the heating element if needed. Sanity: It is not possible to disable the heating element in case emergency mode is enabled.| -| Enable or disable emergency mode | `$root/ctrl/emergencyModeEnabled` | bool | | "`0`" | Forces the DHW heat pump to use only the heating element. Sanity: it is not possible to enable emergency mode if heating element has been disabled. | -| Set PV Mode Heat Pump Flag | `$root/ctrl/flagPVModeHeatPump` | bool| | "`1`" | See [README-PV.md](/README-PV.md) additional information. Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | -| Set PV Mode Heat Element Flag | `$root/ctrl/flagPVModeHeatElement` | bool| | "`1`" | See [README-PV.md](/README-PV.md) for additional information. Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | -| Reset Overrides | `$root/ctrl/reset` | Void | | | Removes all previous set overrides. | +Using this topics you may override the HMI Controller in AquaMQTT OperationMode Man-In-The-Middle. Currently these are +the only ones implemented. If you need more overrides, feel free to raise an issue or even PR. + +| Value / Action | MQTT Topic | Format | Unit | Example Payload | Other Information | +|-----------------------------------|------------------------------------|--------|------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Water Temperature | `$root/ctrl/waterTempTarget` | float | °C | "`55.0`" | Overrides the water temperature target: Allowed range is from 20°C to 62°C. | +| Operation Mode | `$root/ctrl/operationMode` | Enum | | "`BOOST`" | Overrides the operation mode, may affect the waterTempTarget. For example BOOST and ABSENCE will automatically set the waterTempTarget accordingly. | +| Operation Type | `$root/ctrl/operationType` | Enum | | "`ALWAYS ON`" | Overrides the operation type | +| Installation Config | `$root/ctrl/configInstallation` | Enum | | "`HEAT PUMP ONLY`", "`BOILER BACKUP / HEAT PUMP PRIORITY"`", "`...`" | Overrides the installation config | +| Enable or disable heating element | `$root/ctrl/heatingElementEnabled` | bool | | "`1`" | Allow the DHW heat pump to use the heating element if needed. Sanity: It is not possible to disable the heating element in case emergency mode is enabled. | +| Enable or disable emergency mode | `$root/ctrl/emergencyModeEnabled` | bool | | "`0`" | Forces the DHW heat pump to use only the heating element. Sanity: it is not possible to enable emergency mode if heating element has been disabled. | +| Set PV Mode Heat Pump Flag | `$root/ctrl/flagPVModeHeatPump` | bool | | "`1`" | See [README-PV.md](/README-PV.md) additional information. Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | +| Set PV Mode Heat Element Flag | `$root/ctrl/flagPVModeHeatElement` | bool | | "`1`" | See [README-PV.md](/README-PV.md) for additional information. Note: It is possible to define an additional custom mqtt topic for this attribute within `Configuration.h` | +| Reset Overrides | `$root/ctrl/reset` | Void | | | Removes all previous set overrides. | **Note:** Calling a `ctrl` topic with an empty payload `""` will reset individual override. \ No newline at end of file From a3a8c25eab3c6419cc4e195c8144d1e39d59dbbb Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Tue, 28 May 2024 21:11:25 +0200 Subject: [PATCH 11/12] feat(mitm): transfer errors in mitm mode --- AquaMQTT/include/config/Configuration.h | 11 +- AquaMQTT/include/message/MainStatusMessage.h | 6 - AquaMQTT/include/message/MessageConstants.h | 2 + AquaMQTT/include/task/ControllerTask.h | 25 ++- AquaMQTT/include/task/HMITask.h | 21 ++ AquaMQTT/include/task/MQTTTask.h | 14 +- AquaMQTT/src/main.cpp | 6 +- AquaMQTT/src/message/ErrorMessage.cpp | 3 +- AquaMQTT/src/task/ControllerTask.cpp | 104 ++++++---- AquaMQTT/src/task/HMITask.cpp | 202 ++++++++++++++----- AquaMQTT/src/task/MQTTTask.cpp | 48 +++-- 11 files changed, 321 insertions(+), 121 deletions(-) diff --git a/AquaMQTT/include/config/Configuration.h b/AquaMQTT/include/config/Configuration.h index b5324d4..1e4134e 100644 --- a/AquaMQTT/include/config/Configuration.h +++ b/AquaMQTT/include/config/Configuration.h @@ -50,11 +50,20 @@ constexpr bool OVERRIDE_TIME_AND_DATE_IN_MITM = true; */ constexpr bool DEBUG_RAW_SERIAL_MESSAGES = false; +/** + * Change the time interval where all known attributes are published to the MQTT broker. + */ +constexpr uint32_t MQTT_FULL_UPDATE_MS = 1000*60*30; + +/** + * Change the fixed time interval where the attributes published to the stats topic are updated. + */ +constexpr uint16_t MQTT_STATS_UPDATE_MS = 5000; + /** * Self explanatory internal settings: most probably you don't want to change them. */ constexpr uint8_t WATCHDOG_TIMEOUT_S = 60; -constexpr uint16_t MQTT_STATS_UPDATE_MS = 5000; constexpr uint8_t MQTT_MAX_TOPIC_SIZE = 80; constexpr uint8_t MQTT_MAX_PAYLOAD_SIZE = 255; diff --git a/AquaMQTT/include/message/MainStatusMessage.h b/AquaMQTT/include/message/MainStatusMessage.h index 9d38141..c78a31a 100644 --- a/AquaMQTT/include/message/MainStatusMessage.h +++ b/AquaMQTT/include/message/MainStatusMessage.h @@ -103,12 +103,6 @@ class MainStatusMessage bool errorCodeChanged() const; - void changeUnknownStateA(bool b); - - void changeUnknownStateB(bool b); - - void hackError(); - private: uint8_t* mData; bool mHotWaterTempChanged; diff --git a/AquaMQTT/include/message/MessageConstants.h b/AquaMQTT/include/message/MessageConstants.h index dc6444c..9815c07 100644 --- a/AquaMQTT/include/message/MessageConstants.h +++ b/AquaMQTT/include/message/MessageConstants.h @@ -11,6 +11,8 @@ namespace message { constexpr uint8_t HEATPUMP_MAX_FRAME_LENGTH = 40; +constexpr uint8_t MESSAGE_PERIOD_MS = 100; + constexpr uint8_t HMI_MESSAGE_IDENTIFIER = 194; constexpr uint8_t HMI_MESSAGE_LENGTH = 35; constexpr uint8_t MAIN_MESSAGE_IDENTIFIER = 193; diff --git a/AquaMQTT/include/task/ControllerTask.h b/AquaMQTT/include/task/ControllerTask.h index b3d28e9..d9fba8e 100644 --- a/AquaMQTT/include/task/ControllerTask.h +++ b/AquaMQTT/include/task/ControllerTask.h @@ -7,6 +7,14 @@ namespace aquamqtt { +enum class ControllerTaskState +{ + AWAITING_67, + AWAITING_193, + CHECK_FOR_HMI_TRIGGER, + AWAITING_74, +}; + class ControllerTask { public: @@ -23,14 +31,17 @@ class ControllerTask void loop(); + void sendMessage194(); + + static void flushReadBuffer(); + private: - bool mFlagSeen67; - bool mFlagSeen193; - FrameBuffer mBuffer; - unsigned long mLastStatisticsUpdate; - uint8_t mTransferBuffer[message::HEATPUMP_MAX_FRAME_LENGTH]; - FastCRC16 mCRC; - uint64_t mMessagesSent; + FrameBuffer mBuffer; + unsigned long mLastStatisticsUpdate; + uint8_t mTransferBuffer[message::HEATPUMP_MAX_FRAME_LENGTH]; + FastCRC16 mCRC; + uint64_t mMessagesSent; + ControllerTaskState mState; }; } // namespace aquamqtt diff --git a/AquaMQTT/include/task/HMITask.h b/AquaMQTT/include/task/HMITask.h index 2d0e433..05970f2 100644 --- a/AquaMQTT/include/task/HMITask.h +++ b/AquaMQTT/include/task/HMITask.h @@ -7,6 +7,18 @@ namespace aquamqtt { +enum class HMITaskState +{ + REQUESTING_194, + SLEEP_194, + SENDING_67, + SLEEP_67, + SENDING_193, + SLEEP_193, + SENDING_74, + SLEEP_74, +}; + class HMITask { public: @@ -23,12 +35,21 @@ class HMITask void loop(); + static void flushReadBuffer(); + + void sendMessage67(); + void sendMessage193(); + void sendMessage74(); + private: FrameBuffer mBuffer; unsigned long mLastStatisticsUpdate; + unsigned long mLastMessageSent; + uint8_t mLastEmittedRequestId; uint8_t mTransferBuffer[message::HEATPUMP_MAX_FRAME_LENGTH]; FastCRC16 mCRC; uint64_t mMessagesSent; + HMITaskState mState; }; } // namespace aquamqtt diff --git a/AquaMQTT/include/task/MQTTTask.h b/AquaMQTT/include/task/MQTTTask.h index 2cd66f7..1efe2b3 100644 --- a/AquaMQTT/include/task/MQTTTask.h +++ b/AquaMQTT/include/task/MQTTTask.h @@ -32,9 +32,9 @@ class MQTTTask static void messageReceived(String& topic, String& payload); - void updateMainStatus(); - void updateHMIStatus(); - void updateEnergyStats(); + void updateMainStatus(bool triggerFullUpdate); + void updateHMIStatus(bool triggerFullUpdate); + void updateEnergyStats(bool triggerFullUpdate); void updateErrorStatus(); void updateStats(); @@ -42,6 +42,7 @@ class MQTTTask uint8_t mTransferBuffer[message::HEATPUMP_MAX_FRAME_LENGTH]; uint8_t mTopicBuffer[config::MQTT_MAX_TOPIC_SIZE]; uint8_t mPayloadBuffer[config::MQTT_MAX_PAYLOAD_SIZE]; + unsigned long mLastStatsUpdate; unsigned long mLastFullUpdate; WiFiClient mWiFiClient; MQTTClient mMQTTClient; @@ -56,7 +57,12 @@ class MQTTTask void publishString(const char* subtopic, const char* topic, const char* value, bool retained = false); void publishi(const char* subtopic, const char* topic, int value, bool retained = false); void publishul(const char* subtopic, const char* topic, unsigned long value, bool retained = false); - void publishul(const char* subtopic_1, const char* subtopic_2, const char* topic, unsigned long value, bool retained = false); + void publishul( + const char* subtopic_1, + const char* subtopic_2, + const char* topic, + unsigned long value, + bool retained = false); }; } // namespace aquamqtt diff --git a/AquaMQTT/src/main.cpp b/AquaMQTT/src/main.cpp index 8f6876d..5fd0dec 100644 --- a/AquaMQTT/src/main.cpp +++ b/AquaMQTT/src/main.cpp @@ -72,17 +72,17 @@ void setup() // if listener mode is set in configuration, just read the DHW traffic from a single One-Wire USART instance if (OPERATION_MODE == LISTENER) { - // reads 194, 193 and 67 message and notifies the mqtt task + // reads 194, 193, 67 and 74 message and notifies the mqtt task listenerTask.spawn(); } // if man-in-the-middle mode is set in configuration, there are two physical One-Wire USART instances // and AquaMQTT forwards (modified) messages from one to another else { - // reads 194 message from the hmi controller, writes 193 and 67 to the hmi controller + // reads 194 message from the hmi controller, writes 193, 67 and 74 to the hmi controller hmiTask.spawn(); - // reads 193 and 67 from the main controller, writes 194 to the main controller + // reads 193, 67 and 74 from the main controller, writes 194 to the main controller controllerTask.spawn(); } diff --git a/AquaMQTT/src/message/ErrorMessage.cpp b/AquaMQTT/src/message/ErrorMessage.cpp index ac0cfd1..8fbd2c5 100644 --- a/AquaMQTT/src/message/ErrorMessage.cpp +++ b/AquaMQTT/src/message/ErrorMessage.cpp @@ -11,7 +11,8 @@ ErrorMessage::ErrorMessage(uint8_t* data) : mData(data) bool ErrorMessage::isEmpty() { - for (int i = 0; i < ERROR_MESSAGE_LENGTH; ++i) + // i == 1, since we skip the first byte which is length field and always != 0 + for (int i = 1; i < ERROR_MESSAGE_LENGTH; ++i) { if (mData[i] != 0) return false; diff --git a/AquaMQTT/src/task/ControllerTask.cpp b/AquaMQTT/src/task/ControllerTask.cpp index e49440e..a333370 100644 --- a/AquaMQTT/src/task/ControllerTask.cpp +++ b/AquaMQTT/src/task/ControllerTask.cpp @@ -10,12 +10,13 @@ namespace aquamqtt ControllerTask::ControllerTask() : mBuffer(false, true, true, true, "controller") - , mFlagSeen193(false) - , mFlagSeen67(false) , mLastStatisticsUpdate(0) , mTransferBuffer{ 0 } , mCRC() - , mMessagesSent(0){} + , mMessagesSent(0) + , mState(ControllerTaskState::AWAITING_67) +{ +} void ControllerTask::spawn() { @@ -40,7 +41,7 @@ void ControllerTask::spawn() } } -void ControllerTask::setup() // NOLINT(*-convert-member-functions-to-static) +void ControllerTask::setup() // NOLINT(*-convert-member-functions-to-static) { Serial2.begin(9550, SERIAL_8N2, aquamqtt::config::GPIO_MAIN_RX, aquamqtt::config::GPIO_MAIN_TX); } @@ -54,51 +55,57 @@ void ControllerTask::loop() { int valRead = Serial2.read(); - if (mFlagSeen67 && mFlagSeen193) + switch (mState) { - if (valRead == aquamqtt::message::HMI_MESSAGE_IDENTIFIER) + case ControllerTaskState::AWAITING_67: + if (mBuffer.pushByte(valRead) == aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER) + { + mState = ControllerTaskState::AWAITING_193; + } + break; + case ControllerTaskState::AWAITING_193: { - mFlagSeen67 = false; - mFlagSeen193 = false; - - if (HMIStateProxy::getInstance().copyFrame(aquamqtt::message::HMI_MESSAGE_IDENTIFIER, mTransferBuffer)) + int processedMessageId = mBuffer.pushByte(valRead); + if (processedMessageId == aquamqtt::message::MAIN_MESSAGE_IDENTIFIER) + { + mState = ControllerTaskState::CHECK_FOR_HMI_TRIGGER; + } + else if (processedMessageId != 0) { - uint16_t crc = mCRC.ccitt(mTransferBuffer, aquamqtt::message::HMI_MESSAGE_LENGTH); - Serial2.write(mTransferBuffer, aquamqtt::message::HMI_MESSAGE_LENGTH); - Serial2.write((uint8_t) (crc >> 8)); - Serial2.write((uint8_t) (crc & 0xFF)); - Serial2.flush(); - mMessagesSent++; + mState = ControllerTaskState::AWAITING_67; } - else + break; + } + case ControllerTaskState::CHECK_FOR_HMI_TRIGGER: + if (valRead == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER) { - Serial.println("[main] no hmi message yet, cannot forward"); + mBuffer.pushByte(valRead); + mState = ControllerTaskState::AWAITING_74; + continue; } - // flush read buffer - while (Serial2.available()) + if (valRead == aquamqtt::message::HMI_MESSAGE_IDENTIFIER) { - Serial2.read(); + sendMessage194(); + flushReadBuffer(); } - continue; - } - else + + mState = ControllerTaskState::AWAITING_67; + break; + case ControllerTaskState::AWAITING_74: { - mFlagSeen67 = false; - mFlagSeen193 = false; + int processedMessageId = mBuffer.pushByte(valRead); + if (processedMessageId == aquamqtt::message::ERROR_MESSAGE_IDENTIFIER) + { + mState = ControllerTaskState::CHECK_FOR_HMI_TRIGGER; + } + else if (processedMessageId != 0) + { + mState = ControllerTaskState::AWAITING_67; + } + break; } } - - int message = mBuffer.pushByte(valRead); - if (message == aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER) - { - mFlagSeen67 = true; - mFlagSeen193 = false; - } - else if (message == aquamqtt::message::MAIN_MESSAGE_IDENTIFIER) - { - mFlagSeen193 = true; - } } DHWState::getInstance().updateFrameBufferStatistics( @@ -128,5 +135,28 @@ void ControllerTask::loop() mLastStatisticsUpdate = millis(); } } +void ControllerTask::flushReadBuffer() +{ + while (Serial2.available()) + { + Serial2.read(); + } +} +void ControllerTask::sendMessage194() +{ + if (HMIStateProxy::getInstance().copyFrame(message::HMI_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + uint16_t crc = mCRC.ccitt(mTransferBuffer, message::HMI_MESSAGE_LENGTH); + Serial2.write(mTransferBuffer, message::HMI_MESSAGE_LENGTH); + Serial2.write((uint8_t) (crc >> 8)); + Serial2.write((uint8_t) (crc & 0xFF)); + Serial2.flush(); + mMessagesSent++; + } + else + { + Serial.println("[main] no hmi message yet, cannot forward"); + } +} } // namespace aquamqtt diff --git a/AquaMQTT/src/task/HMITask.cpp b/AquaMQTT/src/task/HMITask.cpp index 5f059f3..89791ff 100644 --- a/AquaMQTT/src/task/HMITask.cpp +++ b/AquaMQTT/src/task/HMITask.cpp @@ -3,6 +3,8 @@ #include #include "config/Configuration.h" +#include "message/ErrorMessage.h" +#include "message/MessageConstants.h" #include "state/HMIStateProxy.h" namespace aquamqtt @@ -10,9 +12,12 @@ namespace aquamqtt HMITask::HMITask() : mBuffer(true, false, false, false, "hmi") + , mLastEmittedRequestId(UINT8_MAX) , mLastStatisticsUpdate(0) + , mLastMessageSent(0) , mTransferBuffer{ 0 } , mCRC() + , mState(HMITaskState::REQUESTING_194) , mMessagesSent(0) { } @@ -46,60 +51,82 @@ void HMITask::setup() // NOLINT(*-convert-member-functions-to-static) void HMITask::loop() { - bool printSerialStats = (millis() - mLastStatisticsUpdate) >= 5000; - - // request 194 from hmi - Serial1.write(aquamqtt::message::HMI_MESSAGE_IDENTIFIER); - Serial1.flush(); - vTaskDelay(pdMS_TO_TICKS(60)); + bool printSerialStats = (millis() - mLastStatisticsUpdate) >= 5000; + bool performStateChange = (millis() - mLastMessageSent) >= aquamqtt::message::MESSAGE_PERIOD_MS; while (Serial1.available()) { mBuffer.pushByte(Serial1.read()); } - // as soon as we have a valid message from the main controller, forward it - if (DHWState::getInstance().copyFrame(aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER, mTransferBuffer)) - { - uint16_t crc = mCRC.ccitt(mTransferBuffer, aquamqtt::message::ENERGY_MESSAGE_LENGTH); - Serial1.write(aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER); - Serial1.write(mTransferBuffer, aquamqtt::message::ENERGY_MESSAGE_LENGTH); - Serial1.write((uint8_t) (crc >> 8)); - Serial1.write((uint8_t) (crc & 0xFF)); - Serial1.flush(); - mMessagesSent++; - } - else - { - Serial.println("[hmi] no energy message yet, cannot forward"); - } - - vTaskDelay(pdMS_TO_TICKS(100)); - - // as soon as we have a valid message from the main controller, forward it - if (DHWState::getInstance().copyFrame(aquamqtt::message::MAIN_MESSAGE_IDENTIFIER, mTransferBuffer)) - { - uint16_t crc = mCRC.ccitt(mTransferBuffer, aquamqtt::message::MAIN_MESSAGE_LENGTH); - Serial1.write(aquamqtt::message::MAIN_MESSAGE_IDENTIFIER); - Serial1.write(mTransferBuffer, aquamqtt::message::MAIN_MESSAGE_LENGTH); - Serial1.write((uint8_t) (crc >> 8)); - Serial1.write((uint8_t) (crc & 0xFF)); - Serial1.flush(); - mMessagesSent++; - } - else - { - Serial.println("[hmi] no main message yet, cannot forward"); - } - - // flush read buffer - while (Serial1.available()) + switch (mState) { - Serial1.read(); + case HMITaskState::REQUESTING_194: + Serial1.write(aquamqtt::message::HMI_MESSAGE_IDENTIFIER); + Serial1.flush(); + mState = HMITaskState::SLEEP_194; + mLastMessageSent = millis(); + break; + case HMITaskState::SLEEP_194: + if (performStateChange) + { + mState = HMITaskState::SENDING_67; + } + else + { + vTaskDelay(pdMS_TO_TICKS(5)); + } + break; + case HMITaskState::SENDING_67: + sendMessage67(); + flushReadBuffer(); + mState = HMITaskState::SLEEP_67; + mLastMessageSent = millis(); + break; + case HMITaskState::SLEEP_67: + if (performStateChange) + { + mState = HMITaskState::SENDING_193; + } + else + { + vTaskDelay(pdMS_TO_TICKS(5)); + } + break; + case HMITaskState::SENDING_193: + sendMessage193(); + flushReadBuffer(); + mState = HMITaskState::SLEEP_193; + mLastMessageSent = millis(); + break; + case HMITaskState::SLEEP_193: + if (performStateChange) + { + mState = HMITaskState::SENDING_74; + } + else + { + vTaskDelay(pdMS_TO_TICKS(5)); + } + break; + case HMITaskState::SENDING_74: + sendMessage74(); + flushReadBuffer(); + mState = HMITaskState::SLEEP_74; + mLastMessageSent = millis(); + break; + case HMITaskState::SLEEP_74: + if (performStateChange) + { + mState = HMITaskState::REQUESTING_194; + } + else + { + vTaskDelay(pdMS_TO_TICKS(5)); + } + break; } - vTaskDelay(pdMS_TO_TICKS(200)); - DHWState::getInstance().updateFrameBufferStatistics( 1, BufferStatistics{ mBuffer.getHandledCount(), @@ -110,7 +137,6 @@ void HMITask::loop() if (printSerialStats) { - Serial.print("[hmi]: handled="); Serial.print(mBuffer.getHandledCount()); Serial.print(", unhandled="); @@ -130,5 +156,89 @@ void HMITask::loop() mLastStatisticsUpdate = millis(); } } +void HMITask::flushReadBuffer() +{ + while (Serial1.available()) + { + Serial1.read(); + } +} +void HMITask::sendMessage193() +{ + if (DHWState::getInstance().copyFrame(message::MAIN_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + uint16_t crc = mCRC.ccitt(mTransferBuffer, message::MAIN_MESSAGE_LENGTH); + Serial1.write(message::MAIN_MESSAGE_IDENTIFIER); + Serial1.write(mTransferBuffer, message::MAIN_MESSAGE_LENGTH); + Serial1.write((uint8_t) (crc >> 8)); + Serial1.write((uint8_t) (crc & 0xFF)); + Serial1.flush(); + mMessagesSent++; + } + else + { + Serial.println("[hmi] no main message yet, cannot forward"); + } +} +void HMITask::sendMessage67() +{ + if (DHWState::getInstance().copyFrame(message::ENERGY_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + uint16_t crc = mCRC.ccitt(mTransferBuffer, message::ENERGY_MESSAGE_LENGTH); + Serial1.write(message::ENERGY_MESSAGE_IDENTIFIER); + Serial1.write(mTransferBuffer, message::ENERGY_MESSAGE_LENGTH); + Serial1.write((uint8_t) (crc >> 8)); + Serial1.write((uint8_t) (crc & 0xFF)); + Serial1.flush(); + mMessagesSent++; + } + else + { + Serial.println("[hmi] no energy message yet, cannot forward"); + } +} + +void HMITask::sendMessage74() +{ + // check if the HMI is requesting an error message + uint8_t requestId = UINT8_MAX; + { + if (DHWState::getInstance().copyFrame(aquamqtt::message::HMI_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + aquamqtt::message::HMIMessage hmiMessage(mTransferBuffer); + requestId = hmiMessage.errorRequestId(); + } + } + + // Check if we already emitted that error message, because we emit error messages just once + if (mLastEmittedRequestId == requestId) + { + // already emitted, no change + return; + } + + // check if we have the requested error message in cache + uint8_t availableRequestId = 0; + { + if (DHWState::getInstance().copyFrame(aquamqtt::message::ERROR_MESSAGE_IDENTIFIER, mTransferBuffer)) + { + aquamqtt::message::ErrorMessage errorMessage(mTransferBuffer); + availableRequestId = errorMessage.errorRequestId(); + } + } + + // emit the error message + if (requestId == availableRequestId) + { + uint16_t crc = mCRC.ccitt(mTransferBuffer, aquamqtt::message::ERROR_MESSAGE_LENGTH); + Serial1.write(aquamqtt::message::ERROR_MESSAGE_IDENTIFIER); + Serial1.write(mTransferBuffer, aquamqtt::message::ERROR_MESSAGE_LENGTH); + Serial1.write((uint8_t) (crc >> 8)); + Serial1.write((uint8_t) (crc & 0xFF)); + Serial1.flush(); + mMessagesSent++; + mLastEmittedRequestId = requestId; + } +} } // namespace aquamqtt diff --git a/AquaMQTT/src/task/MQTTTask.cpp b/AquaMQTT/src/task/MQTTTask.cpp index c3ca788..bc08db9 100644 --- a/AquaMQTT/src/task/MQTTTask.cpp +++ b/AquaMQTT/src/task/MQTTTask.cpp @@ -15,7 +15,8 @@ using namespace mqtt; using namespace message; MQTTTask::MQTTTask() - : mLastFullUpdate(0) + : mLastStatsUpdate(0) + , mLastFullUpdate(0) , mMQTTClient(256) , mTransferBuffer{ 0 } , mTaskHandle(nullptr) @@ -290,15 +291,17 @@ void MQTTTask::loop() auto mqttCycle = pdMS_TO_TICKS(5); - bool statsUpdate = (millis() - mLastFullUpdate) >= config::MQTT_STATS_UPDATE_MS; + bool fullUpdate = (millis() - mLastFullUpdate) >= config::MQTT_FULL_UPDATE_MS; + + bool statsUpdate = (millis() - mLastStatsUpdate) >= config::MQTT_STATS_UPDATE_MS; auto notify = ulTaskNotifyTake(pdTRUE, mqttCycle); - if ((notify & 1 << 8) != 0) + if ((notify & 1 << 8) != 0 || fullUpdate) { if (HMIStateProxy::getInstance().copyFrame(aquamqtt::message::HMI_MESSAGE_IDENTIFIER, mTransferBuffer)) { - updateHMIStatus(); + updateHMIStatus(fullUpdate); if (mLastProcessedHMIMessage == nullptr) { @@ -308,11 +311,11 @@ void MQTTTask::loop() } } - if ((notify & 1 << 7) != 0) + if ((notify & 1 << 7) != 0 || fullUpdate) { if (DHWState::getInstance().copyFrame(aquamqtt::message::MAIN_MESSAGE_IDENTIFIER, mTransferBuffer)) { - updateMainStatus(); + updateMainStatus(fullUpdate); if (mLastProcessedMainMessage == nullptr) { @@ -322,11 +325,11 @@ void MQTTTask::loop() } } - if ((notify & 1 << 6) != 0) + if ((notify & 1 << 6) != 0 || fullUpdate) { if (DHWState::getInstance().copyFrame(aquamqtt::message::ENERGY_MESSAGE_IDENTIFIER, mTransferBuffer)) { - updateEnergyStats(); + updateEnergyStats(fullUpdate); if (mLastProcessedEnergyMessage == nullptr) { @@ -352,11 +355,16 @@ void MQTTTask::loop() if (statsUpdate) { Serial.println("[mqtt] stat update"); - mLastFullUpdate = millis(); + mLastStatsUpdate = millis(); Serial.print("[mqtt]: stack size (words)"); Serial.println(uxTaskGetStackHighWaterMark(nullptr)); } + if (fullUpdate) + { + mLastFullUpdate = millis(); + } + mMQTTClient.loop(); } @@ -415,7 +423,10 @@ void MQTTTask::updateStats() HMI_INSTALLATION_CONFIG, overrides.installationMode ? "1" : "0", HMI_TIME_AND_DATE, - aquamqtt::config::OVERRIDE_TIME_AND_DATE_IN_MITM ? "1" : "0"); + (aquamqtt::config::OVERRIDE_TIME_AND_DATE_IN_MITM + && aquamqtt::config::OPERATION_MODE != aquamqtt::config::EOperationMode::LISTENER) + ? "1" + : "0"); sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%S", @@ -443,10 +454,10 @@ void MQTTTask::updateStats() #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat" -void MQTTTask::updateMainStatus() +void MQTTTask::updateMainStatus(bool fullUpdate) { message::MainStatusMessage message(mTransferBuffer); - message.compareWith(mLastProcessedMainMessage); + message.compareWith(fullUpdate ? nullptr : mLastProcessedMainMessage); if (message.hotWaterTempChanged()) { @@ -544,10 +555,10 @@ void MQTTTask::updateMainStatus() #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat" -void MQTTTask::updateHMIStatus() +void MQTTTask::updateHMIStatus(bool fullUpdate) { message::HMIMessage message(mTransferBuffer); - message.compareWith(mLastProcessedHMIMessage); + message.compareWith(fullUpdate ? nullptr : mLastProcessedHMIMessage); if (message.waterTempTargetChanged()) { @@ -675,10 +686,10 @@ void MQTTTask::updateHMIStatus() #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat" -void MQTTTask::updateEnergyStats() +void MQTTTask::updateEnergyStats(bool fullUpdate) { message::MainEnergyMessage message(mTransferBuffer); - message.compareWith(mLastProcessedEnergyMessage); + message.compareWith(fullUpdate ? nullptr : mLastProcessedEnergyMessage); if (message.totalHeatpumpHoursChanged()) { @@ -740,6 +751,11 @@ void MQTTTask::updateErrorStatus() { message::ErrorMessage message(mTransferBuffer); + if (message.isEmpty()) + { + return; + } + sprintf(reinterpret_cast(mTopicBuffer), "%s%S%S%u/%S", config::mqttPrefix, From c844d51cc992719bb609d204b00dc9a0bac0643a Mon Sep 17 00:00:00 2001 From: Thomas Popp Date: Tue, 28 May 2024 22:04:47 +0200 Subject: [PATCH 12/12] chore(doc): update latest protocol findings --- AquaMQTT/include/config/Configuration.h | 2 +- MQTT.md | 2 +- PROTOCOL.md | 25 +++++++++++-------------- aquamqtt.yaml | 8 ++++---- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/AquaMQTT/include/config/Configuration.h b/AquaMQTT/include/config/Configuration.h index 1e4134e..61a4a01 100644 --- a/AquaMQTT/include/config/Configuration.h +++ b/AquaMQTT/include/config/Configuration.h @@ -51,7 +51,7 @@ constexpr bool OVERRIDE_TIME_AND_DATE_IN_MITM = true; constexpr bool DEBUG_RAW_SERIAL_MESSAGES = false; /** - * Change the time interval where all known attributes are published to the MQTT broker. + * Change the time interval where all known attributes are re-published to the MQTT broker. */ constexpr uint32_t MQTT_FULL_UPDATE_MS = 1000*60*30; diff --git a/MQTT.md b/MQTT.md index e7df84c..221ca98 100644 --- a/MQTT.md +++ b/MQTT.md @@ -118,7 +118,7 @@ within the message reflect the state when the error has been created. ## Subscribe Topics Using this topics you may override the HMI Controller in AquaMQTT OperationMode Man-In-The-Middle. Currently these are -the only ones implemented. If you need more overrides, feel free to raise an issue or even PR. +the only ones implemented. If you need more overrides, feel free to raise an issue or even provide an PR. | Value / Action | MQTT Topic | Format | Unit | Example Payload | Other Information | |-----------------------------------|------------------------------------|--------|------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| diff --git a/PROTOCOL.md b/PROTOCOL.md index 82c99ce..6cd35ed 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -115,7 +115,7 @@ controller completes the packet on the one-wire bus. | 0 | 35 | Length Field | - | | 1 - 2 | 18 2 | Target Temp. | See Temperature Table, 53°C | | 3 | 66 | OperationMode | See Operation Mode Table | -| 4 | 252 | Command: Change PWM, Command: Change Anti-Trockenheizung | See Commands | +| 4 | 252 | Command: Change PWM, Command: Change Anti-Dry-Heating | See Commands | | 5 | 0 | Anti-Legionella Mode / AirDuct Mode | 0 == Off, 1 == 1perMonth, 2 == 2perMonth, 3 == 3perMonth, 4 == 4/perMonth, // 0 == AirDuct INT/INT, 16 == AirDuct EXT/INT, 32 == AirDuct EXT/EXT | | 6 | 240 | Emergency-Mode, Command: Change Connectivity | "Emergency Mode Off == 240, Emergency Mode On == 241", "Disabled Connectivity: No == 240, Disabled Connectivity: Yes == 16" | | 7 | 17 | InstallationConfig | WP-Only == 0, WP+ExtBoiler-Prio-WP == 1, Wp+ExtBoiler-Opt-WP == 17, Wp+ExtBoiler-Opt-ExtBoiler == 33 , Wp+ExtBoiler-Prio-ExtBoiler == 49 , WP + Solar == 50 | @@ -321,7 +321,7 @@ Noop: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 255 0 x x x x x 0 0 255 0 40°C: 35 18 2 65 252 0 240 32 240 4 60 16 16 24 255 40 0 x x x x x 0 0 255 0 9 66 1 1 255 255 255 255 255 ``` -##### Enable/Disable Anti-Trockenheizung +##### Enable/Disable Anti-Dry-Heating - Affected Byte Positions: 4 @@ -459,12 +459,12 @@ Findings... Findings... ``` -5dec | 0000 0101: Communication Enabled, PV enabled, No Circulation, Anti-Trockenheizung -17dec | 0001 0001: Communication Enabled, PV disabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung -20dec | 0001 0100: Communication Enabled, PV enabled, Heat Exchanger not available, No Zirculation, No Anti-Trockenheizung -21dec | 0001 0101: Communication Enabled, PV enabled, No Circulation, No Anti-Trockenheizung -23dec | 0001 0111: Communication Enabled, PV enabled, With Circulation Enabled, No Anti-Trockenheizung -29dec | 0001 1101: Communication Disabled, PV enabled, Heat-Exchanger available, No Zirculation, No Anti-Trockenheizung +5dec | 0000 0101: Communication Enabled, PV enabled, No Circulation, Anti-Dry-Heating +17dec | 0001 0001: Communication Enabled, PV disabled, Heat-Exchanger available, No Zirculation, No Anti-Dry-Heating +20dec | 0001 0100: Communication Enabled, PV enabled, Heat Exchanger not available, No Zirculation, No Anti-Dry-Heating +21dec | 0001 0101: Communication Enabled, PV enabled, No Circulation, No Anti-Dry-Heating +23dec | 0001 0111: Communication Enabled, PV enabled, With Circulation Enabled, No Anti-Dry-Heating +29dec | 0001 1101: Communication Disabled, PV enabled, Heat-Exchanger available, No Circulation, No Anti-Dry-Heating ``` | Bit Number | Purpose/Function | Other Information | @@ -503,12 +503,9 @@ Error messages are emitted by the main controller if the HMI is requesting error - The main controller is emitting empty error messages if HMI is requesting placeholder values (errorNumber==0, requestId==0) or if an invalid errorNumber has been requested. - Entering Secret Diagnosis Menu: HMI will fetch all existing errors - -To be clarified: - -- Most likely the HMI controller will request an error message if the error bitflag has been set. -- Most likely the MAIN controller will never emit an error message, without an HMI controller asking for it. -- Check if errorId == 0 contains an valid error during an error state of if is reserved to be empty. +- The HMI controller will request an error message if the error bitflag (within main message, byte 17) has been set. +- The MAIN controller will never emit an error message, without an HMI controller asking for it. +- ErrorID == 0 is reserved and always contains an empty error message if requested.