From 4231b34bb8036573093304a3292085ac752f588a Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Fri, 16 Feb 2024 21:30:29 +0100 Subject: [PATCH] added basic v1 and v2 protocol devices --- .../base_devices/ProtocolV1Device.py | 48 +++++++++++++ .../base_devices/ProtocolV2Device.py | 49 +++++++++++++ .../bluetti_bt_lib/field_attributes.py | 72 +++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py create mode 100644 custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py create mode 100644 custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py new file mode 100644 index 0000000..d70d667 --- /dev/null +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py @@ -0,0 +1,48 @@ +"""Base device definition for V1 Protocol devices.""" + +from typing import List + +from ..utils.commands import ReadHoldingRegisters +from ..utils.struct import DeviceStruct +from .BluettiDevice import BluettiDevice + + +class ProtocolV1Device(BluettiDevice): + def __init__(self, address: str, type: str, sn: str): + self.struct = DeviceStruct() + + # Device info + self.struct.add_string_field('device_type', 10, 6) + self.struct.add_sn_field('serial_number', 17) + + # Power IO + self.struct.add_uint_field('dc_input_power', 36) + self.struct.add_uint_field('ac_input_power', 37) + self.struct.add_uint_field('ac_output_power', 38) + self.struct.add_uint_field('dc_output_power', 39) + + # Battery + self.struct.add_uint_field('total_battery_percent', 43) + + # Output state + self.struct.add_bool_field('ac_output_on', 48) + self.struct.add_bool_field('dc_output_on', 49) + + # Output controls + self.struct.add_bool_field('ac_output_on_switch', 3007) + self.struct.add_bool_field('dc_output_on_switch', 3008) + + super().__init__(address, type, sn) + + @property + def polling_commands(self) -> List[ReadHoldingRegisters]: + return [ + ReadHoldingRegisters(10, 10), + ReadHoldingRegisters(36, 4), + ReadHoldingRegisters(43, 1), + ReadHoldingRegisters(48, 2), + ] + + @property + def writable_ranges(self) -> List[range]: + return [range(3007, 3008)] diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py new file mode 100644 index 0000000..a3670ae --- /dev/null +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py @@ -0,0 +1,49 @@ +"""Base device definition for V2 Protocol devices.""" + +from typing import List + +from ..utils.commands import ReadHoldingRegisters +from ..utils.struct import DeviceStruct +from .BluettiDevice import BluettiDevice + + +class ProtocolV2Device(BluettiDevice): + def __init__(self, address: str, type: str, sn: str): + self.struct = DeviceStruct() + + # Device info + self.struct.add_string_field('device_type', 110, 6) + self.struct.add_sn_field('serial_number', 116) + + # Power IO + #self.struct.add_uint_field('dc_input_power', ?) + #self.struct.add_uint_field('ac_input_power', ?) + #self.struct.add_uint_field('ac_output_power', ?) + #self.struct.add_uint_field('dc_output_power', ?) + + # Battery + self.struct.add_uint_field('total_battery_percent', 102) + + # Output state + #self.struct.add_bool_field('ac_output_on', ?) + #self.struct.add_bool_field('dc_output_on', ?) + + # Output controls + #self.struct.add_bool_field('ac_output_on_switch', ?) + #self.struct.add_bool_field('dc_output_on_switch', ?) + + # Add battery fields here? + + super().__init__(address, type, sn) + + @property + def polling_commands(self) -> List[ReadHoldingRegisters]: + return [ + ReadHoldingRegisters(110, 6), + ReadHoldingRegisters(116, 4), + ReadHoldingRegisters(102, 1), + ] + + @property + def writable_ranges(self) -> List[range]: + return [] diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py b/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py new file mode 100644 index 0000000..6d4ce70 --- /dev/null +++ b/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py @@ -0,0 +1,72 @@ +"""Field attributes for device fields.""" + +from dataclasses import dataclass +from enum import Enum, auto, unique + + +@unique +class FieldType(Enum): + NUMERIC = auto() + BOOL = auto() + ENUM = auto() + BUTTON = auto() + + +@dataclass(frozen=True) +class FieldAttributes: + type: FieldType + setter: bool = False + name: str + unit_of_measurement: str|None = None + device_class: str|None = None + state_class: str|None = None + +@dataclass(frozen=True) +class PowerFieldAttributes(FieldAttributes): + type = FieldType.NUMERIC + name: str + unit_of_measurement = 'W' + device_class = 'power' + state_class = 'measurement' + +@dataclass(frozen=True) +class OutletFieldAttributes(FieldAttributes): + type = FieldType.BOOL + name: str + device_class = 'outlet' + +FIELD_ATTRIBUTES = { + 'dc_input_power': PowerFieldAttributes( + name = 'DC Input Power', + ), + 'ac_input_power': PowerFieldAttributes( + name = 'AC Input Power', + ), + 'ac_output_power': PowerFieldAttributes( + name = 'AC Output Power', + ), + 'dc_output_power': PowerFieldAttributes( + name = 'DC Output Power', + ), + 'total_battery_percent': FieldAttributes( + type = FieldType.NUMERIC, + name = 'Total Battery Percent', + unit_of_measurement = '%', + device_class ='battery', + state_class ='measurement', + ), + 'ac_output_on': OutletFieldAttributes( + name = 'AC Output', + ), + 'dc_output_on': OutletFieldAttributes( + name = 'DC Output', + ), + 'ac_output_on_switch': OutletFieldAttributes( + name = 'AC Output', + setter = True, + ), + 'dc_output_on_switch': OutletFieldAttributes( + name = 'DC Output', + setter = True, + ), +}