Skip to content

Commit 02c8f7b

Browse files
committed
POINTTAPI: add additional appliance status pairs and translations
- Add requested Bosch/Buderus display+code combinations to appliance status mapping - Add new state keys for low fan speed, gas-shutdown flame detection, safety-time faults, and KIM recognition - Extend base strings and all locale translations (en/de/fr/it/nl/pl/sk) - Expand pair-coverage unit test with newly requested combinations
1 parent c655ac1 commit 02c8f7b

10 files changed

Lines changed: 435 additions & 104 deletions

File tree

custom_components/bosch/pointtapi_entities.py

Lines changed: 117 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -76,38 +76,56 @@
7676
("-H", 200): "heating_operation",
7777
("=H", 201): "hot_water_operation",
7878
("0A", 202): "anti_cycle_delay",
79+
("0A", 305): "dhw_post_heating_lockout",
7980
("0H", 203): "standby_no_heat_demand",
8081
("0Y", 204): "supply_temp_above_setpoint",
82+
("0Y", 359): "dhw_sensor_temp_too_high",
8183
("", 207): "system_pressure_too_low",
8284
("A", 208): "flue_gas_test_heat_demand",
8385
("-A", 208): "flue_gas_test_heat_demand",
8486
("1C", 210): "flue_gas_thermostat_or_air_pressure_active",
8587
("2E", 212): "safety_supply_temp_rising_too_fast",
8688
("2P", 212): "safety_supply_temp_rising_too_fast",
89+
("2P", 341): "heating_gradient_limitation",
90+
("2P", 342): "dhw_gradient_limitation",
8791
("2U", 213): "supply_return_temp_difference_too_high",
92+
("3L", 214): "fan_stopped_during_safety_time",
8893
("3Y", 214): "fan_stopped_during_safety_time",
8994
("3Y", 215): "fan_speed_too_high",
95+
("3P", 216): "fan_speed_too_low",
9096
("3Y", 216): "fan_malfunction",
91-
("3C", 217): "air_pressure_monitor_fault",
97+
("3A", 264): "fan_stopped_during_operation",
98+
("3F", 273): "safety_shutdown_after_24h_continuous_operation",
99+
("3C", 217): "fan_malfunction",
92100
("4A", 218): "supply_temp_too_high",
93101
("4F", 219): "safety_sensor_temp_too_high",
94102
("4U", 220): "supply_sensor_short_circuit",
103+
("4U", 222): "supply_sensor_short_circuit",
104+
("4U", 350): "supply_sensor_short_circuit",
95105
("4Y", 221): "supply_sensor_disconnected",
106+
("4Y", 223): "supply_sensor_disconnected",
107+
("4Y", 351): "supply_sensor_disconnected",
96108
("4C", 222): "safety_sensor_short_circuit",
97109
("4C", 223): "safety_sensor_disconnected",
98110
("4C", 224): "safety_limiter_active",
99111
("4L", 225): "communication_or_internal_signal_fault",
100112
("4L", 226): "internal_monitoring_fault",
101113
("6A", 227): "no_flame_after_ignition",
102114
("6C", 228): "unexpected_flame_signal",
115+
("6C", 306): "flame_detected_after_gas_shutdown",
103116
("6L", 229): "ionization_signal_lost",
104117
("6L", 230): "invalid_ionization_signal",
105118
("7C", 231): "mains_voltage_fault_or_power_loss",
106119
("8Y", 232): "external_cutoff_switch_active",
107120
("8Y", 233): "external_controller_fault",
121+
("7L", 261): "first_safety_time_fault",
122+
("7L", 280): "restart_attempt_time_fault",
108123
("9A", 234): "gas_valve_communication_fault",
124+
("9L", 234): "gas_valve_communication_fault",
109125
("9A", 235): "gas_valve_not_recognized",
110126
("9A", 238): "internal_electronics_fault",
127+
("9L", 238): "internal_electronics_fault",
128+
("9P", 239): "kim_not_recognized",
111129
("9U", 239): "equipment_fault_relay_error",
112130
("9U", 240): "device_internal_fault",
113131
("CU", 240): "return_sensor_short_circuit",
@@ -120,6 +138,7 @@
120138
("EC", 257): "regulation_system_internal_fault",
121139
("EC", 258): "regulation_system_internal_fault",
122140
("EC", 259): "regulation_system_internal_fault",
141+
("EL", 259): "regulation_system_internal_fault",
123142
("EA", 260): "ignition_or_flame_fault",
124143
("EA", 261): "flame_fault",
125144
("0E", 265): "heat_demand_below_min_power",
@@ -132,7 +151,10 @@
132151
("0Y", 276): "supply_temp_above_setpoint",
133152
("0U", 280): "fan_starting",
134153
("0U", 281): "startup_delay",
154+
("2Y", 281): "pump_no_pressure_difference",
135155
("2Y", 282): "pump_or_flow_insufficient",
156+
("2E", 357): "purge_function_active",
157+
("2H", 358): "pump_or_three_way_valve_anti_seizure",
136158
("0C", 283): "burner_starting",
137159
("0L", 284): "gas_valve_opening",
138160
("0L", 285): "gas_valve_open_burner_startup",
@@ -147,77 +169,24 @@
147169
("EL", 299): "internal_system_fault",
148170
}
149171

150-
_APPLIANCE_STATUS_BY_CAUSE: dict[int, str] = {
151-
200: "heating_operation",
152-
201: "hot_water_operation",
153-
202: "anti_cycle_delay",
154-
203: "standby_no_heat_demand",
155-
204: "supply_temp_above_setpoint",
156-
207: "system_pressure_too_low",
157-
208: "flue_gas_test_heat_demand",
158-
210: "flue_gas_thermostat_or_air_pressure_active",
159-
212: "safety_supply_temp_rising_too_fast",
160-
213: "supply_return_temp_difference_too_high",
161-
214: "fan_stopped_during_safety_time",
162-
215: "fan_speed_too_high",
163-
216: "fan_malfunction",
164-
217: "air_pressure_monitor_fault",
165-
218: "supply_temp_too_high",
166-
219: "safety_sensor_temp_too_high",
167-
220: "supply_sensor_short_circuit",
168-
221: "supply_sensor_disconnected",
169-
222: "safety_sensor_short_circuit",
170-
223: "safety_sensor_disconnected",
171-
224: "safety_limiter_active",
172-
225: "communication_or_internal_signal_fault",
173-
226: "internal_monitoring_fault",
174-
227: "no_flame_after_ignition",
175-
228: "unexpected_flame_signal",
176-
229: "ionization_signal_lost",
177-
230: "invalid_ionization_signal",
178-
231: "mains_voltage_fault_or_power_loss",
179-
232: "external_cutoff_switch_active",
180-
233: "external_controller_fault",
181-
234: "gas_valve_communication_fault",
182-
235: "gas_valve_not_recognized",
183-
238: "internal_electronics_fault",
184-
239: "equipment_fault_relay_error",
185-
240: "device_internal_fault",
186-
241: "return_sensor_short_circuit",
187-
242: "return_sensor_disconnected",
188-
243: "internal_communication_fault",
189-
244: "sensor_or_electronics_fault",
190-
245: "communication_fault",
191-
246: "internal_controller_fault",
192-
257: "regulation_system_internal_fault",
193-
258: "regulation_system_internal_fault",
194-
259: "regulation_system_internal_fault",
195-
260: "ignition_or_flame_fault",
196-
261: "flame_fault",
197-
265: "heat_demand_below_min_power",
198-
268: "regulation_system_test",
199-
270: "boiler_starting",
200-
271: "boiler_startup_preventilation",
201-
272: "burner_operation_monitoring",
202-
273: "flame_monitoring",
203-
274: "burner_startup_phase",
204-
276: "supply_temp_above_setpoint",
205-
280: "fan_starting",
206-
281: "startup_delay",
207-
282: "pump_or_flow_insufficient",
208-
283: "burner_starting",
209-
284: "gas_valve_opening",
210-
285: "gas_valve_open_burner_startup",
211-
290: "main_controller_fault",
212-
291: "internal_electronics_fault",
213-
292: "regulation_system_fault",
214-
293: "internal_fault",
215-
294: "electronic_fault",
216-
296: "internal_controller_fault",
217-
297: "internal_fault",
218-
298: "electronic_fault",
219-
299: "internal_system_fault",
220-
}
172+
def _build_appliance_status_by_cause(
173+
by_code: dict[tuple[str, int], str],
174+
) -> dict[int, str]:
175+
"""Build a cause-only fallback map from the pair table.
176+
177+
Keep the first seen mapping for a cause code to preserve the intended
178+
primary meaning when multiple display codes share the same cause.
179+
"""
180+
181+
by_cause: dict[int, str] = {}
182+
for (_display, cause), status in by_code.items():
183+
by_cause.setdefault(cause, status)
184+
return by_cause
185+
186+
187+
_APPLIANCE_STATUS_BY_CAUSE: dict[int, str] = _build_appliance_status_by_cause(
188+
_APPLIANCE_STATUS_BY_CODE
189+
)
221190

222191

223192
def _val(data: dict[str, Any], path: str, key: str = VALUE_KEY) -> Any:
@@ -876,15 +845,72 @@ def _appliance_cause_code(data: dict[str, Any]) -> int | None:
876845
return None
877846

878847

848+
def _appliance_error_flag(data: dict[str, Any], path: str) -> bool | None:
849+
"""Parse appliance error flags that may be bool, numeric or string values."""
850+
raw = _val(data, path)
851+
if isinstance(raw, bool):
852+
return raw
853+
if isinstance(raw, (int, float)):
854+
return raw != 0
855+
if isinstance(raw, str):
856+
value = raw.strip().lower()
857+
if value in {"true", "1", "on", "yes"}:
858+
return True
859+
if value in {"false", "0", "off", "no"}:
860+
return False
861+
return None
862+
863+
864+
def _appliance_optional_code(data: dict[str, Any], path: str) -> int | None:
865+
"""Parse optional diagnostic codes (service/blocking/locking) when numeric."""
866+
raw = _val(data, path)
867+
if raw is None or isinstance(raw, bool):
868+
return None
869+
if isinstance(raw, (int, float)):
870+
return int(raw)
871+
if isinstance(raw, str):
872+
value = raw.strip().lower()
873+
if value in {"", "true", "false", "on", "off", "yes", "no"}:
874+
return None
875+
try:
876+
return int(float(value))
877+
except ValueError:
878+
return None
879+
return None
880+
881+
879882
def _appliance_status_state(data: dict[str, Any]) -> str | None:
880883
display = _appliance_display_code(data)
881884
cause = _appliance_cause_code(data)
882-
if display is None and cause is None:
883-
return None
885+
blocking = _appliance_error_flag(data, "/system/appliance/blockingError")
886+
locking = _appliance_error_flag(data, "/system/appliance/lockingError")
887+
blocking_code = _appliance_optional_code(data, "/system/appliance/blockingError")
888+
locking_code = _appliance_optional_code(data, "/system/appliance/lockingError")
889+
890+
# Single mapping table, context-aware lookup order:
891+
# locking pair -> blocking pair -> service pair.
892+
if locking is True:
893+
if display is not None and locking_code is not None:
894+
status = _APPLIANCE_STATUS_BY_CODE.get((display, locking_code))
895+
if status is not None:
896+
return status
897+
return "locking_fault_code_active"
898+
899+
if blocking is True:
900+
if display is not None and blocking_code is not None:
901+
status = _APPLIANCE_STATUS_BY_CODE.get((display, blocking_code))
902+
if status is not None:
903+
return status
904+
return "blocking_fault_code_active"
905+
884906
if display is not None and cause is not None:
885907
status = _APPLIANCE_STATUS_BY_CODE.get((display, cause))
886908
if status is not None:
887909
return status
910+
911+
if display is None and cause is None and blocking is None and locking is None:
912+
return None
913+
888914
if cause is not None:
889915
status = _APPLIANCE_STATUS_BY_CAUSE.get(cause)
890916
if status is not None:
@@ -897,17 +923,32 @@ def _appliance_status_state(data: dict[str, Any]) -> str | None:
897923
def _appliance_status_attributes(data: dict[str, Any]) -> dict[str, Any] | None:
898924
display = _appliance_display_code(data)
899925
cause = _appliance_cause_code(data)
900-
if display is None and cause is None:
926+
blocking = _appliance_error_flag(data, "/system/appliance/blockingError")
927+
locking = _appliance_error_flag(data, "/system/appliance/lockingError")
928+
blocking_code = _appliance_optional_code(data, "/system/appliance/blockingError")
929+
locking_code = _appliance_optional_code(data, "/system/appliance/lockingError")
930+
if display is None and cause is None and blocking is None and locking is None:
901931
return None
902-
return {
932+
attrs: dict[str, Any] = {
903933
"display_code": display,
904934
"cause_code": cause,
905935
}
936+
if blocking is not None:
937+
attrs["blocking_error"] = blocking
938+
if blocking_code is not None:
939+
attrs["blocking_code"] = blocking_code
940+
if locking is not None:
941+
attrs["locking_error"] = locking
942+
if locking_code is not None:
943+
attrs["locking_code"] = locking_code
944+
return attrs
906945

907946

908947
def _appliance_status_available(data: dict[str, Any]) -> bool:
909948
return _val(data, "/system/appliance/displayCode") is not None or _val(
910949
data, "/system/appliance/causeCode"
950+
) is not None or _val(data, "/system/appliance/blockingError") is not None or _val(
951+
data, "/system/appliance/lockingError"
911952
) is not None
912953

913954

custom_components/bosch/strings.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@
4747
"name": "Gas total this hour"
4848
},
4949
"blocking_error": {
50-
"name": "Blocking error",
50+
"name": "Blocking fault code",
5151
"state": {
5252
"false": "No error",
5353
"true": "Error"
5454
}
5555
},
5656
"locking_error": {
57-
"name": "Locking error",
57+
"name": "Locking fault code",
5858
"state": {
5959
"false": "No error",
6060
"true": "Error"
@@ -71,22 +71,30 @@
7171
"name": "Display code"
7272
},
7373
"cause_code": {
74-
"name": "Cause code"
74+
"name": "Service code"
7575
},
7676
"appliance_status": {
7777
"name": "Boiler status",
7878
"state": {
7979
"unknown": "Unknown",
80+
"locking_fault_code_active": "Locking fault code active",
81+
"blocking_fault_code_active": "Blocking fault code active",
8082
"standby_no_heat_demand": "Standby - no heat demand",
8183
"heating_operation": "Heating operation",
8284
"hot_water_operation": "Hot water operation",
8385
"anti_cycle_delay": "Anti-cycling delay",
86+
"dhw_post_heating_lockout": "DHW post-heating lockout active",
8487
"system_pressure_too_low": "System pressure too low",
8588
"flue_gas_test_heat_demand": "Heat demand due to flue gas test",
8689
"flue_gas_thermostat_or_air_pressure_active": "Flue gas thermostat or air pressure controller active",
8790
"safety_supply_temp_rising_too_fast": "Safety/supply temperature rising too fast",
91+
"heating_gradient_limitation": "Heating gradient limitation",
92+
"dhw_gradient_limitation": "DHW gradient limitation",
8893
"supply_return_temp_difference_too_high": "Supply/return temperature difference too high",
94+
"pump_no_pressure_difference": "Circulation pump does not generate pressure difference",
8995
"fan_stopped_during_safety_time": "Fan stopped during safety time",
96+
"fan_stopped_during_operation": "Fan stopped during operation",
97+
"fan_speed_too_low": "Fan speed too low",
9098
"fan_speed_too_high": "Fan speed too high",
9199
"fan_malfunction": "Fan malfunction",
92100
"air_pressure_monitor_fault": "Air pressure monitor fault",
@@ -101,13 +109,17 @@
101109
"internal_monitoring_fault": "Internal monitoring fault",
102110
"no_flame_after_ignition": "No flame detected after ignition",
103111
"unexpected_flame_signal": "Flame signal present when no flame expected",
112+
"flame_detected_after_gas_shutdown": "Flame detected after gas shutdown",
104113
"ionization_signal_lost": "Ionization signal lost during operation",
105114
"invalid_ionization_signal": "Invalid ionization signal",
106115
"mains_voltage_fault_or_power_loss": "Mains voltage fault or power outage",
116+
"first_safety_time_fault": "First safety-time timeout fault",
117+
"restart_attempt_time_fault": "Restart-attempt timeout fault",
107118
"external_cutoff_switch_active": "External cutoff switch active",
108119
"external_controller_fault": "External controller fault",
109120
"gas_valve_communication_fault": "Communication fault with gas valve",
110121
"gas_valve_not_recognized": "Gas valve not recognized",
122+
"kim_not_recognized": "KIM not recognized",
111123
"internal_electronics_fault": "Internal electronics fault",
112124
"equipment_fault_relay_error": "Equipment fault relay error",
113125
"device_internal_fault": "Internal device fault",
@@ -124,17 +136,21 @@
124136
"boiler_startup_preventilation": "Boiler startup/pre-ventilation",
125137
"burner_operation_monitoring": "Burner operation monitoring",
126138
"flame_monitoring": "Flame monitoring",
139+
"safety_shutdown_after_24h_continuous_operation": "Safety shutdown after 24h continuous burner/fan operation",
127140
"burner_startup_phase": "Burner startup phase",
128141
"fan_starting": "Fan starting",
129142
"startup_delay": "Startup delay",
130143
"pump_or_flow_insufficient": "Pump/circulation flow insufficient",
144+
"purge_function_active": "Purge function active",
145+
"pump_or_three_way_valve_anti_seizure": "Pump/3-way valve anti-seizure active",
131146
"internal_error_service_required": "Internal error - service required",
132147
"burner_starting": "Burner starting",
133148
"heat_demand_below_min_power": "Heat demand below minimum power",
134149
"gas_valve_opening": "Gas valve opening",
135150
"gas_valve_open_burner_startup": "Gas valve open / burner startup",
136151
"boiler_starting": "Boiler starting",
137152
"supply_temp_above_setpoint": "Supply temperature above setpoint",
153+
"dhw_sensor_temp_too_high": "DHW sensor temperature too high",
138154
"main_controller_fault": "Main controller fault",
139155
"regulation_system_fault": "Regulation system fault",
140156
"internal_fault": "Internal fault",

0 commit comments

Comments
 (0)