Skip to content

Commit 56b3bf6

Browse files
nanomadclaude
andcommitted
feat: add Home Assistant Event entity for command errors
Closes #272. When a command to the vehicle fails, a JSON event is now published to the command/error topic so Home Assistant users can build automations around command failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8edf815 commit 56b3bf6

4 files changed

Lines changed: 59 additions & 3 deletions

File tree

src/handlers/vehicle_command.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass
44
import logging
5-
from typing import TYPE_CHECKING, Final
5+
from typing import TYPE_CHECKING, Any, Final
66

77
from saic_ismart_client_ng.exceptions import SaicApiException, SaicLogoutException
88

@@ -69,6 +69,15 @@ async def handle_mqtt_command(self, *, topic: str, payload: str) -> None:
6969
handler=handler, payload=payload, analyzed_topic=analyzed_topic
7070
)
7171

72+
def __publish_command_error(self, command: str, detail: str) -> None:
73+
error_topic = self.vehicle_state.get_topic(mqtt_topics.COMMAND_ERROR)
74+
event_payload: dict[str, Any] = {
75+
"event_type": "command_error",
76+
"command": command,
77+
"detail": detail,
78+
}
79+
self.publisher.publish_json(error_topic, event_payload)
80+
7281
async def __execute_mqtt_command_handler(
7382
self,
7483
*,
@@ -91,6 +100,7 @@ async def __execute_mqtt_command_handler(
91100
self.publisher.clear_topic(topic_no_global)
92101
except MqttGatewayException as e:
93102
self.publisher.publish_str(result_topic, f"Failed: {e.message}")
103+
self.__publish_command_error(topic, e.message)
94104
LOG.exception(e.message, exc_info=e)
95105
except SaicLogoutException:
96106
LOG.warning(
@@ -99,9 +109,11 @@ async def __execute_mqtt_command_handler(
99109
try:
100110
await self.relogin_handler.force_login()
101111
except Exception as login_err:
112+
detail = f"relogin failed ({login_err})"
102113
self.publisher.publish_str(
103-
result_topic, f"Failed: relogin failed ({login_err})"
114+
result_topic, f"Failed: {detail}"
104115
)
116+
self.__publish_command_error(topic, detail)
105117
LOG.error("Immediate relogin failed", exc_info=login_err)
106118
return
107119
try:
@@ -115,17 +127,21 @@ async def __execute_mqtt_command_handler(
115127
if execution_result.clear_command:
116128
self.publisher.clear_topic(topic_no_global)
117129
except Exception as retry_err:
130+
detail = str(retry_err)
118131
self.publisher.publish_str(
119-
result_topic, f"Failed: {retry_err}"
132+
result_topic, f"Failed: {detail}"
120133
)
134+
self.__publish_command_error(topic, detail)
121135
LOG.error(
122136
"Command retry after relogin failed", exc_info=retry_err
123137
)
124138
except SaicApiException as se:
125139
self.publisher.publish_str(result_topic, f"Failed: {se.message}")
140+
self.__publish_command_error(topic, se.message)
126141
LOG.exception(se.message, exc_info=se)
127142
except Exception as se:
128143
self.publisher.publish_str(result_topic, "Failed unexpectedly")
144+
self.__publish_command_error(topic, str(se))
129145
LOG.exception(
130146
"handle_mqtt_command failed with an unexpected exception", exc_info=se
131147
)

src/integrations/home_assistant/base.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,34 @@ def _publish_lock(
230230
"lock", name, payload, custom_availability
231231
)
232232

233+
def _publish_event(
234+
self,
235+
topic: str,
236+
name: str,
237+
event_types: list[str],
238+
*,
239+
enabled: bool = True,
240+
entity_category: str | None = None,
241+
device_class: str | None = None,
242+
icon: str | None = None,
243+
custom_availability: HaCustomAvailabilityConfig | None = None,
244+
) -> str:
245+
payload: dict[str, Any] = {
246+
"state_topic": self._get_state_topic(topic),
247+
"event_types": event_types,
248+
"enabled_by_default": enabled,
249+
}
250+
if entity_category is not None:
251+
payload["entity_category"] = entity_category
252+
if device_class is not None:
253+
payload["device_class"] = device_class
254+
if icon is not None:
255+
payload["icon"] = icon
256+
257+
return self._publish_ha_discovery_message(
258+
"event", name, payload, custom_availability
259+
)
260+
233261
def _publish_sensor(
234262
self,
235263
topic: str,

src/integrations/home_assistant/discovery.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ def __publish_ha_discovery_messages_real(self) -> None:
327327
)
328328
self.__publish_lights_sensors()
329329

330+
# Command error event
331+
self._publish_event(
332+
mqtt_topics.COMMAND_ERROR,
333+
"Command error",
334+
["command_error"],
335+
entity_category="diagnostic",
336+
icon="mdi:alert-circle",
337+
)
338+
330339
LOG.debug("Completed publishing Home Assistant discovery messages")
331340

332341
def __publish_drivetrain_charging_sensors(self) -> None:

src/mqtt_topics.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,7 @@
179179
TYRES_REAR_LEFT_PRESSURE = TYRES + "/rearLeftPressure"
180180
TYRES_REAR_RIGHT_PRESSURE = TYRES + "/rearRightPressure"
181181

182+
COMMAND = "command"
183+
COMMAND_ERROR = COMMAND + "/error"
184+
182185
VEHICLES = "vehicles"

0 commit comments

Comments
 (0)