diff --git a/victron_ble/devices/base.py b/victron_ble/devices/base.py index 1b58e2e..a6dec6f 100644 --- a/victron_ble/devices/base.py +++ b/victron_ble/devices/base.py @@ -2,7 +2,7 @@ import struct from dataclasses import dataclass from enum import Enum -from typing import Any, Dict, Type +from typing import Any, Dict, Type, TypeVar from Crypto.Cipher import AES from Crypto.Util import Counter @@ -10,6 +10,19 @@ from victron_ble.exceptions import AdvertisementKeyMismatchError +E = TypeVar("E", bound=Enum) + + +def parse_enum(enum_class: Type[E], value: int) -> E: + """ + Safely convert an int to an enum, returning the UNKNOWN member if the value is not valid. + The enum_class must have an UNKNOWN member defined. + """ + try: + return enum_class(value) + except ValueError: + return getattr(enum_class, "UNKNOWN") + # Sourced from VE.Direct docs class OperationMode(Enum): @@ -171,6 +184,7 @@ class ChargerError(Enum): class OffReason(Enum): + UNKNOWN = -1 NO_REASON = 0x00000000 NO_INPUT_POWER = 0x00000001 SWITCHED_OFF_SWITCH = 0x00000002 @@ -186,6 +200,7 @@ class OffReason(Enum): class AlarmReason(Enum): + UNKNOWN = -1 NO_ALARM = 0 LOW_VOLTAGE = 1 HIGH_VOLTAGE = 2 diff --git a/victron_ble/devices/battery_monitor.py b/victron_ble/devices/battery_monitor.py index 6c34ea6..f91cd08 100644 --- a/victron_ble/devices/battery_monitor.py +++ b/victron_ble/devices/battery_monitor.py @@ -7,6 +7,7 @@ Device, DeviceData, kelvin_to_celsius, + parse_enum, ) @@ -107,7 +108,7 @@ def parse_decrypted(self, decrypted: bytes) -> dict: parsed = { "remaining_mins": remaining_mins if remaining_mins != 0xFFFF else None, "voltage": voltage / 100 if voltage != 0x7FFF else None, - "alarm": AlarmReason(alarm), + "alarm": parse_enum(AlarmReason, alarm), "aux_mode": AuxMode(aux_mode), "current": current / 1000 if current != 0x3FFFFF else None, "consumed_ah": -consumed_ah / 10 if consumed_ah != 0xFFFFF else None, diff --git a/victron_ble/devices/dc_energy_meter.py b/victron_ble/devices/dc_energy_meter.py index abd2c09..24bb5b1 100644 --- a/victron_ble/devices/dc_energy_meter.py +++ b/victron_ble/devices/dc_energy_meter.py @@ -7,6 +7,7 @@ Device, DeviceData, kelvin_to_celsius, + parse_enum, ) from victron_ble.devices.battery_monitor import AuxMode @@ -54,7 +55,8 @@ def get_alarm(self) -> Optional[AlarmReason]: """ Return an enum indicating the current alarm reason or None otherwise """ - return AlarmReason(self._data["alarm"]) if self._data["alarm"] > 0 else None + alarm = self._data["alarm"] + return parse_enum(AlarmReason, alarm) if alarm > 0 else None def get_aux_mode(self) -> AuxMode: """ diff --git a/victron_ble/devices/dcdc_converter.py b/victron_ble/devices/dcdc_converter.py index 82d9db8..a79c251 100644 --- a/victron_ble/devices/dcdc_converter.py +++ b/victron_ble/devices/dcdc_converter.py @@ -7,6 +7,7 @@ DeviceData, OffReason, OperationMode, + parse_enum, ) @@ -73,5 +74,5 @@ def parse_decrypted(self, decrypted: bytes) -> dict: "output_voltage": ( output_voltage / 100 if output_voltage != 0x7FFF else None ), - "off_reason": OffReason(off_reason), + "off_reason": parse_enum(OffReason, off_reason), } diff --git a/victron_ble/devices/orion_xs.py b/victron_ble/devices/orion_xs.py index 0e12e1a..dd70841 100644 --- a/victron_ble/devices/orion_xs.py +++ b/victron_ble/devices/orion_xs.py @@ -7,6 +7,7 @@ DeviceData, OffReason, OperationMode, + parse_enum, ) @@ -94,5 +95,5 @@ def parse_decrypted(self, decrypted: bytes) -> dict: "output_current": output_current / 10 if output_current != 0xFFFF else None, "input_voltage": input_voltage / 100 if input_voltage != 0xFFFF else None, "input_current": input_current / 10 if input_current != 0xFFFF else None, - "off_reason": OffReason(off_reason), + "off_reason": parse_enum(OffReason, off_reason), } diff --git a/victron_ble/devices/smart_battery_protect.py b/victron_ble/devices/smart_battery_protect.py index 1b8f2e6..3079603 100644 --- a/victron_ble/devices/smart_battery_protect.py +++ b/victron_ble/devices/smart_battery_protect.py @@ -9,6 +9,7 @@ DeviceData, OffReason, OperationMode, + parse_enum, ) @@ -91,11 +92,11 @@ def parse_decrypted(self, decrypted: bytes) -> dict: OutputState(output_state) if output_state != 0xFF else None ), "error_code": (ChargerError(error_code) if error_code != 0xFF else None), - "alarm_reason": AlarmReason(alarm_reason), - "warning_reason": AlarmReason(warning_reason), + "alarm_reason": parse_enum(AlarmReason, alarm_reason), + "warning_reason": parse_enum(AlarmReason, warning_reason), "input_voltage": (input_voltage / 100 if input_voltage != 0x7FFF else None), "output_voltage": ( output_voltage / 100 if output_voltage != 0xFFFF else None ), - "off_reason": OffReason(off_reason), + "off_reason": parse_enum(OffReason, off_reason), }