Skip to content

Commit 78087cb

Browse files
authored
Merge pull request #42 from lawther/feat/heater-status
2 parents 6fca2e9 + 7fc0132 commit 78087cb

13 files changed

Lines changed: 633 additions & 16 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Note about CMD 0x05 observed with payload 0x00. Added to PROTOCOL.md and new sample trace.
2323
- Note about CMD 0x12 observed with payload 0x01 0x00. Added to PROTOCOL.md and new sample trace.
2424
- Parsing for CMD 0x12 updated so `0x01 0x00` no longer reported as unexpected.
25-
25+
- Full decoding of CMD 0x12 status byte for Gas Heaters (HiNRG `0x0072` and ICI (`0x0074`))
2626
### Removed
2727
### Fixed
2828
### Deprecated

PROTOCOL.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,17 +463,17 @@ Data fields:
463463

464464
---
465465

466-
#### ICI Gas Heater (`0x0074`) ✅
466+
#### Gas Heaters: HiNRG (`0x0072`) & ICI (`0x0074`) ✅
467467

468468
Pattern: `02 00 74 FF FF 80 00 12 10 16`
469469

470470
Examples:
471471

472472
```
473473
02 00 74 FF FF 80 00 12 10 16 00 00 00 00 00 03 Idle / off
474-
02 00 74 FF FF 80 00 12 10 16 00 01 00 00 01 03 On and Lighting
474+
02 00 74 FF FF 80 00 12 10 16 00 01 00 00 01 03 Heater On, no water flow yet
475475
02 00 74 FF FF 80 00 12 10 16 00 03 00 00 03 03 At Setpoint (on but not heating)
476-
02 00 74 FF FF 80 00 12 10 16 00 07 00 00 07 03 (transitional?)
476+
02 00 74 FF FF 80 00 12 10 16 00 07 00 00 07 03 Igniting
477477
02 00 74 FF FF 80 00 12 10 16 00 0F 00 00 0F 03 Heater Lit and Running
478478
^^ Status byte
479479
```
@@ -486,13 +486,30 @@ Data fields:
486486

487487
Observed status values (payload[1]):
488488

489-
| Value | Meaning |
490-
|--------|---------|
491-
| `0x00` | Idle / off |
492-
| `0x01` | On and Lighting (attempting ignition) |
493-
| `0x03` | At Setpoint — on but not heating |
494-
| `0x07` | Transitional? (observed briefly between `0x01` and `0x0F`) |
495-
| `0x0F` | Heater Lit and Running |
489+
| Value | Bits 7–5 Diagnostics | Bit 4 <br> Locked Out | Bit 3 <br> Flame | Bit 2 <br> Gas Valve | Bit 1 <br> Pressure / Flow | Bit 0 <br> Heater On | Meaning |
490+
|--------|---------|-------|-------|-------|-------|-------|---------|
491+
| `0x00` | X | 0 | 0 | 0 | 0 | 0 | System Idle (Heater Off, No Water Flow)|
492+
| `0x01` | X | 0 | 0 | 0 | 0 | 1 | Heater On / No Flow (temporary state if heater is turned on while pump is off)|
493+
| `0x02` | X | 0 | 0 | 0 | 1 | 0 | Heater Off / Water Flow (Normal state when heater is off and pump is running) |
494+
| `0x03` | X | 0 | 0 | 0 | 1 | 1 | Setpoint Reached |
495+
| `0x07` | X | 0 | 0 | 1 | 1 | 1 | Igniting |
496+
| `0x0F` | X | 0 | 1 | 1 | 1 | 1 | Heating |
497+
| `0x12` | X | 1 | 0 | 0 | 1 | 0 | Cooling Down (Heater Off, Pump Forced On)|
498+
| `0x13` | X | 1 | 0 | 0 | 1 | 1 | Locked Out (Heater On, Pump Forced On) |
499+
500+
Some notes:
501+
502+
- 'Heater On' is required for 'Gas Valve' to open.
503+
- 'Pressure / Flow' is required for 'Gas Valve' to open.
504+
- 'Gas Valve' is required for 'Flame'.
505+
- 'Pressure / Flow' is required for 'Locked Out'.
506+
- 'Locked Out' implies 'Gas Valve' is closed.
507+
508+
Diagnostic Bits
509+
- Bit 5: General Service Required
510+
- Bit 6: Ignition Service Required
511+
- Bit 7: Cooling Available (Heatpump installed?)
512+
It is unclear whether the diagnostic bits can be set at the same time as the functional status bits.
496513

497514
Payload[1] is the only byte that varies; bytes 10, 12, and 13 are always `0x00`. The data checksum (byte 14) equals payload[1] since all other payload bytes are zero.
498515

dependencies.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dependencies:
2222
idf:
2323
source:
2424
type: idf
25-
version: 5.5.1
25+
version: 5.5.4
2626
direct_dependencies:
2727
- espressif/led_strip
2828
- espressif/mdns

main/message_decoder.c

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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
271301
const 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

Comments
 (0)