@@ -169,6 +169,18 @@ static const channel_type_entry_t CHANNEL_TYPE_TABLE[] = {
169169
170170#define CHANNEL_TYPE_TABLE_SIZE (sizeof(CHANNEL_TYPE_TABLE) / sizeof(CHANNEL_TYPE_TABLE[0]))
171171
172+ // Gas Heater Status Field Bitmasks
173+ static const int GAS_HEATER_BITMASK_HEATER_ON = 0x01 ;
174+ static const int GAS_HEATER_BITMASK_WATER_FLOW = 0x02 ;
175+ static const int GAS_HEATER_BITMASK_GAS_VALVE = 0x04 ;
176+ static const int GAS_HEATER_BITMASK_BURNER_ALIGHT = 0x08 ;
177+ static const int GAS_HEATER_BITMASK_LOCKED_OUT = 0x10 ;
178+ static const int GAS_HEATER_BITMASK_GENERAL_SERVICE_REQUIRED = 0x20 ;
179+ static const int GAS_HEATER_BITMASK_IGNITION_SERVICE_REQUIRED = 0x40 ;
180+ static const int GAS_HEATER_BITMASK_COOLING_AVAILABLE = 0x80 ;
181+ // The lower 5 bits are for general heater functions
182+ static const int GAS_HEATER_BITMASK_FUNCTIONAL_STATUS = 0x1F ;
183+
172184/**
173185 * Get channel type name from type code
174186 * @param type_code Channel type code (0x00-0x12, 0xFD, 0xFE)
@@ -267,6 +279,24 @@ const char *LIGHT_ZONE_NAME_TABLE[] = {
267279 "Waterfall 3" , // 0x05
268280};
269281
282+ // Gas heater status names (indexed by gas_heater_status_t)
283+ const char * HEATER_STATUS_NAMES [] = {
284+ "Off" , // HEATER_OFF
285+ "No Flow" , // HEATER_ON_NO_FLOW
286+ "Igniting" , // HEATER_IGNITING
287+ "Heating" , // HEATER_HEATING
288+ "Setpoint Reached" , // HEATER_SETPOINT_REACHED
289+ "Cooldown" , // HEATER_COOLDOWN
290+ "Locked Out" , // HEATER_LOCKED_OUT
291+ };
292+
293+ // Gas heater burner state names (indexed by gas_heater_burner_state_t)
294+ const char * BURNER_STATE_NAMES [] = {
295+ "Off" , // BURNER_OFF
296+ "Igniting" , // BURNER_IGNITING
297+ "Alight" , // BURNER_ALIGHT
298+ };
299+
270300// Day of week names
271301const char * DAY_OF_WEEK_NAMES [] = {
272302 "Monday" , // 0
@@ -897,8 +927,98 @@ static bool handle_gas_heater_status(
897927 message_decoder_context_t * ctx )
898928{
899929 if (payload_len < 4 ) return false;
900- ESP_LOGI (TAG , "%s Gas heater status - [%02X %02X %02X %02X]" ,
901- addr_info , payload [0 ], payload [1 ], payload [2 ], payload [3 ]);
930+
931+
932+ const uint8_t raw_status = payload [1 ];
933+ const uint8_t functional_status = raw_status & GAS_HEATER_BITMASK_FUNCTIONAL_STATUS ;
934+ gas_heater_status_t heater_status ;
935+
936+ switch (functional_status ) {
937+ case 0x00 :
938+ heater_status = HEATER_OFF ;
939+ break ;
940+ case 0x01 :
941+ heater_status = HEATER_ON_NO_FLOW ;
942+ break ;
943+ case 0x02 :
944+ heater_status = HEATER_OFF ;
945+ break ;
946+ case 0x03 :
947+ heater_status = HEATER_SETPOINT_REACHED ;
948+ break ;
949+ case 0x07 :
950+ heater_status = HEATER_IGNITING ;
951+ break ;
952+ case 0x0F :
953+ heater_status = HEATER_HEATING ;
954+ break ;
955+ case 0x12 :
956+ heater_status = HEATER_COOLDOWN ;
957+ break ;
958+ case 0x13 :
959+ heater_status = HEATER_LOCKED_OUT ;
960+ break ;
961+ default :
962+ ESP_LOGW (TAG , "Invalid gas heater status:%d" , functional_status );
963+ return false;
964+ }
965+
966+ const bool heater_on = (raw_status & GAS_HEATER_BITMASK_HEATER_ON ) != 0 ;
967+ const bool water_flow_detected = (raw_status & GAS_HEATER_BITMASK_WATER_FLOW ) != 0 ;
968+ const bool gas_valve_open = (raw_status & GAS_HEATER_BITMASK_GAS_VALVE ) != 0 ;
969+ const bool burner_alight = (raw_status & GAS_HEATER_BITMASK_BURNER_ALIGHT ) != 0 ;
970+ const bool locked_out = (raw_status & GAS_HEATER_BITMASK_LOCKED_OUT ) != 0 ;
971+ const bool general_service_required = (raw_status & GAS_HEATER_BITMASK_GENERAL_SERVICE_REQUIRED ) != 0 ;
972+ const bool ignition_service_required = (raw_status & GAS_HEATER_BITMASK_IGNITION_SERVICE_REQUIRED ) != 0 ;
973+ const bool cooling_available = (raw_status & GAS_HEATER_BITMASK_COOLING_AVAILABLE ) != 0 ;
974+
975+ gas_heater_burner_state_t burner_state ;
976+ if (!gas_valve_open && !burner_alight ) {
977+ burner_state = BURNER_OFF ;
978+ } else if (gas_valve_open && !burner_alight ) {
979+ burner_state = BURNER_IGNITING ;
980+ } else if (gas_valve_open && burner_alight ) {
981+ burner_state = BURNER_ALIGHT ;
982+ } else {
983+ // This should never happen given the valid status filter above
984+ ESP_LOGW (TAG , "Invalid gas burner state: gas_valve=%d, alight=%d" , gas_valve_open , burner_alight );
985+ return false;
986+ }
987+
988+ ESP_LOGI (TAG , "%s Gas heater raw status - [%02X %02X %02X %02X], decoded status - \"%s\"" ,
989+ addr_info , payload [0 ], payload [1 ], payload [2 ], payload [3 ], HEATER_STATUS_NAMES [heater_status ]);
990+ if (general_service_required || ignition_service_required ) {
991+ ESP_LOGI (TAG , "%s Gas heater service required - general %s, ignition %s" ,
992+ addr_info , general_service_required ? "true" : "false" , ignition_service_required ? "true" : "false" );
993+ }
994+
995+ // Update state and publish
996+ pool_state_t snapshot ;
997+ if (!ctx -> state_mutex || xSemaphoreTake (ctx -> state_mutex , pdMS_TO_TICKS (MUTEX_TIMEOUT_MS )) != pdTRUE ) {
998+ ESP_LOGW (TAG , "Failed to acquire mutex for heater" );
999+ return true;
1000+ }
1001+ pool_heater_t * const heater_state = & (ctx -> pool_state -> heaters [0 ]);
1002+ heater_state -> valid = true;
1003+ heater_state -> on = heater_on ;
1004+
1005+ heater_state -> gas_heater_valid = true;
1006+ heater_state -> water_flow_detected = water_flow_detected ;
1007+ heater_state -> locked_out = locked_out ;
1008+ heater_state -> burner_state = burner_state ;
1009+ heater_state -> general_service_required = general_service_required ;
1010+ heater_state -> ignition_service_required = ignition_service_required ;
1011+ heater_state -> cooling_available = cooling_available ;
1012+ heater_state -> status = heater_status ;
1013+
1014+ ctx -> pool_state -> last_update_ms = xTaskGetTickCount () * portTICK_PERIOD_MS ;
1015+ snapshot = * ctx -> pool_state ;
1016+ xSemaphoreGive (ctx -> state_mutex );
1017+
1018+ if (ctx -> enable_mqtt ) {
1019+ mqtt_publish_gas_heater (& snapshot , 0 );
1020+ }
1021+
9021022 return true;
9031023}
9041024
@@ -3008,7 +3128,7 @@ static bool dispatch_message(
30083128 return handle_heater (data , len , payload , payload_len , addr_info , ctx );
30093129 }
30103130
3011- // Genus Heater (0x0070) messages
3131+ // Heater temperature setting messages
30123132 if (match_pattern (data , len , MSG_TYPE_GENUS_HEATER_TEMP_SETTING ) ||
30133133 match_pattern (data , len , MSG_TYPE_HINRG_HEATER_TEMP_SETTING ) ||
30143134 match_pattern (data , len , MSG_TYPE_ICI_HEATER_TEMP_SETTING )) {
0 commit comments