From 432d46d0f1f98dc7278497e55fa801edc70d9193 Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Sat, 17 Feb 2024 20:14:12 +0100 Subject: [PATCH] black code formatting --- .../base_devices/BluettiDevice.py | 4 +- .../base_devices/ProtocolV1Device.py | 40 +-- .../base_devices/ProtocolV2Device.py | 40 +-- .../bluetti_bt/bluetti_bt_lib/const.py | 6 +- .../bluetti_bt_lib/devices/ac200m.py | 22 +- .../bluetti_bt_lib/devices/ac300.py | 51 ++-- .../bluetti_bt/bluetti_bt_lib/exceptions.py | 1 + .../bluetti_bt_lib/field_attributes.py | 272 +++++++++--------- .../bluetti_bt/bluetti_bt_lib/field_enums.py | 2 + .../bluetti_bt_lib/utils/commands.py | 26 +- .../bluetti_bt/bluetti_bt_lib/utils/struct.py | 35 ++- 11 files changed, 257 insertions(+), 242 deletions(-) diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/BluettiDevice.py b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/BluettiDevice.py index 8ce7432..dcc5cd7 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/BluettiDevice.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/BluettiDevice.py @@ -52,7 +52,9 @@ class BluettiDevice: def build_setter_command(self, field: str, value: Any): matches = [f for f in self.struct.fields if f.name == field] - device_field = next(f for f in matches if any(f.address in r for r in self.writable_ranges)) + device_field = next( + f for f in matches if any(f.address in r for r in self.writable_ranges) + ) # Convert value to an integer if isinstance(device_field, EnumField): 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 index ba6ba2d..b7d717d 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV1Device.py @@ -12,38 +12,40 @@ class ProtocolV1Device(BluettiDevice): self.struct = DeviceStruct() # Device info - self.struct.add_string_field('device_type', 10, 6) - self.struct.add_sn_field('serial_number', 17) + 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) + 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) # Power IO statistics - self.struct.add_decimal_field('power_generation', 41, 1) # Total power generated since last reset (kwh) + self.struct.add_decimal_field( + "power_generation", 41, 1 + ) # Total power generated since last reset (kwh) # Battery - self.struct.add_uint_field('total_battery_percent', 43) + self.struct.add_uint_field("total_battery_percent", 43) # Battery packs - self.struct.add_uint_field('pack_num_max', 91) # internal - self.struct.add_decimal_field('total_battery_voltage', 92, 1) - self.struct.add_uint_field('pack_num', 96) # internal - self.struct.add_decimal_field('pack_voltage', 98, 2) # Full pack voltage - self.struct.add_uint_field('pack_battery_percent', 99) + self.struct.add_uint_field("pack_num_max", 91) # internal + self.struct.add_decimal_field("total_battery_voltage", 92, 1) + self.struct.add_uint_field("pack_num", 96) # internal + self.struct.add_decimal_field("pack_voltage", 98, 2) # Full pack voltage + self.struct.add_uint_field("pack_battery_percent", 99) # Output state - self.struct.add_bool_field('ac_output_on', 48) - self.struct.add_bool_field('dc_output_on', 49) + self.struct.add_bool_field("ac_output_on", 48) + self.struct.add_bool_field("dc_output_on", 49) # Pack selector - self.struct.add_uint_field('pack_num', 3006) # internal + self.struct.add_uint_field("pack_num", 3006) # internal # Output controls - self.struct.add_bool_field('ac_output_on_switch', 3007) - self.struct.add_bool_field('dc_output_on_switch', 3008) + 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) @@ -63,7 +65,7 @@ class ProtocolV1Device(BluettiDevice): @property def writable_ranges(self) -> List[range]: return [range(3006, 3008)] - + @property def pack_polling_commands(self) -> List[ReadHoldingRegisters]: return [ 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 index 21abd98..92f4808 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py @@ -12,38 +12,40 @@ class ProtocolV2Device(BluettiDevice): self.struct = DeviceStruct() # Device info - self.struct.add_string_field('device_type', 110, 6) - self.struct.add_sn_field('serial_number', 116) + 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', ?) + # 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', ?) # Power IO statistics - self.struct.add_decimal_field('power_generation', 154, 1) # Total power generated since last reset (kwh) + self.struct.add_decimal_field( + "power_generation", 154, 1 + ) # Total power generated since last reset (kwh) # Battery - self.struct.add_uint_field('total_battery_percent', 102) + self.struct.add_uint_field("total_battery_percent", 102) # Battery packs - #self.struct.add_uint_field('pack_num_max', ?) # internal - #self.struct.add_decimal_field('total_battery_voltage', ?, 1) - #self.struct.add_uint_field('pack_num', ?) # internal - #self.struct.add_decimal_field('pack_voltage', ?, 2) # Full pack voltage - #self.struct.add_uint_field('pack_battery_percent', ?) + # self.struct.add_uint_field('pack_num_max', ?) # internal + # self.struct.add_decimal_field('total_battery_voltage', ?, 1) + # self.struct.add_uint_field('pack_num', ?) # internal + # self.struct.add_decimal_field('pack_voltage', ?, 2) # Full pack voltage + # self.struct.add_uint_field('pack_battery_percent', ?) # Output state - #self.struct.add_bool_field('ac_output_on', ?) - #self.struct.add_bool_field('dc_output_on', ?) + # self.struct.add_bool_field('ac_output_on', ?) + # self.struct.add_bool_field('dc_output_on', ?) # Pack selector - #self.struct.add_uint_field('pack_num', ?) # internal + # self.struct.add_uint_field('pack_num', ?) # internal # Output controls - #self.struct.add_bool_field('ac_output_on_switch', ?) - #self.struct.add_bool_field('dc_output_on_switch', ?) + # self.struct.add_bool_field('ac_output_on_switch', ?) + # self.struct.add_bool_field('dc_output_on_switch', ?) # Add battery fields here? @@ -61,7 +63,7 @@ class ProtocolV2Device(BluettiDevice): @property def writable_ranges(self) -> List[range]: return [] - + @property def pack_polling_commands(self) -> List[ReadHoldingRegisters]: return [] diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/const.py b/custom_components/bluetti_bt/bluetti_bt_lib/const.py index 7bc8f82..6524cad 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/const.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/const.py @@ -1,6 +1,6 @@ """Const definitions.""" RESPONSE_TIMEOUT = 5 -WRITE_UUID = '0000ff02-0000-1000-8000-00805f9b34fb' -NOTIFY_UUID = '0000ff01-0000-1000-8000-00805f9b34fb' -DEVICE_NAME_UUID = '00002a00-0000-1000-8000-00805f9b34fb' +WRITE_UUID = "0000ff02-0000-1000-8000-00805f9b34fb" +NOTIFY_UUID = "0000ff01-0000-1000-8000-00805f9b34fb" +DEVICE_NAME_UUID = "00002a00-0000-1000-8000-00805f9b34fb" diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac200m.py b/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac200m.py index f1193e6..f5fa41a 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac200m.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac200m.py @@ -12,21 +12,21 @@ class AC200M(ProtocolV1Device): super().__init__(address, "AC200M", sn) # Details - self.struct.add_enum_field('ac_output_mode', 70, OutputMode) - self.struct.add_uint_field('internal_ac_voltage', 71) - self.struct.add_decimal_field('internal_current_one', 72, 1) - self.struct.add_uint_field('internal_power_one', 73) - self.struct.add_decimal_field('internal_ac_frequency', 74, 1) - self.struct.add_uint_field('internal_dc_input_voltage', 86) - self.struct.add_decimal_field('internal_dc_input_power', 87, 1) - self.struct.add_decimal_field('internal_dc_input_current', 88, 2) + self.struct.add_enum_field("ac_output_mode", 70, OutputMode) + self.struct.add_uint_field("internal_ac_voltage", 71) + self.struct.add_decimal_field("internal_current_one", 72, 1) + self.struct.add_uint_field("internal_power_one", 73) + self.struct.add_decimal_field("internal_ac_frequency", 74, 1) + self.struct.add_uint_field("internal_dc_input_voltage", 86) + self.struct.add_decimal_field("internal_dc_input_power", 87, 1) + self.struct.add_decimal_field("internal_dc_input_current", 88, 2) # Battery packs - self.struct.add_decimal_array_field('cell_voltages', 105, 16, 2) # internal + self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal # Controls - self.struct.add_bool_field('power_off', 3060) - self.struct.add_enum_field('auto_sleep_mode', 3061, AutoSleepMode) + self.struct.add_bool_field("power_off", 3060) + self.struct.add_enum_field("auto_sleep_mode", 3061, AutoSleepMode) @property def pack_num_max(self): diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac300.py b/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac300.py index 8e61af9..bb757fc 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac300.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/devices/ac300.py @@ -12,35 +12,36 @@ class AC300(ProtocolV1Device): super().__init__(address, "AC300", sn) # Details - self.struct.add_enum_field('ac_output_mode', 70, OutputMode) - self.struct.add_uint_field('internal_ac_voltage', 71) - self.struct.add_decimal_field('internal_current_one', 72, 1) - self.struct.add_uint_field('internal_power_one', 73) - self.struct.add_decimal_field('internal_ac_frequency', 74, 1) - self.struct.add_decimal_field('internal_current_two', 75, 1) - self.struct.add_uint_field('internal_power_two', 76) - self.struct.add_decimal_field('ac_input_voltage', 77, 1) - self.struct.add_decimal_field('internal_current_three', 78, 1, (0, 100)) - self.struct.add_uint_field('internal_power_three', 79) - self.struct.add_decimal_field('ac_input_frequency', 80, 2) - self.struct.add_uint_field('internal_dc_input_voltage', 86) - self.struct.add_decimal_field('internal_dc_input_power', 87, 1) - self.struct.add_decimal_field('internal_dc_input_current', 88, 2) + self.struct.add_enum_field("ac_output_mode", 70, OutputMode) + self.struct.add_uint_field("internal_ac_voltage", 71) + self.struct.add_decimal_field("internal_current_one", 72, 1) + self.struct.add_uint_field("internal_power_one", 73) + self.struct.add_decimal_field("internal_ac_frequency", 74, 1) + self.struct.add_decimal_field("internal_current_two", 75, 1) + self.struct.add_uint_field("internal_power_two", 76) + self.struct.add_decimal_field("ac_input_voltage", 77, 1) + self.struct.add_decimal_field("internal_current_three", 78, 1, (0, 100)) + self.struct.add_uint_field("internal_power_three", 79) + self.struct.add_decimal_field("ac_input_frequency", 80, 2) + self.struct.add_uint_field("internal_dc_input_voltage", 86) + self.struct.add_decimal_field("internal_dc_input_power", 87, 1) + self.struct.add_decimal_field("internal_dc_input_current", 88, 2) # Battery packs - self.struct.add_decimal_array_field('cell_voltages', 105, 16, 2) # internal - self.struct.add_version_field('pack_bms_version', 201) + self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal + self.struct.add_version_field("pack_bms_version", 201) # Controls - self.struct.add_enum_field('ups_mode', 3001, UpsMode) - self.struct.add_bool_field('split_phase_on', 3004) - self.struct.add_enum_field('split_phase_machine_mode', 3005, SplitPhaseMachineType) - self.struct.add_bool_field('grid_charge_on', 3011) - self.struct.add_bool_field('time_control_on', 3013) - self.struct.add_uint_field('battery_range_start', 3015) - self.struct.add_uint_field('battery_range_end', 3016) - self.struct.add_enum_field('auto_sleep_mode', 3061, AutoSleepMode) - + self.struct.add_enum_field("ups_mode", 3001, UpsMode) + self.struct.add_bool_field("split_phase_on", 3004) + self.struct.add_enum_field( + "split_phase_machine_mode", 3005, SplitPhaseMachineType + ) + self.struct.add_bool_field("grid_charge_on", 3011) + self.struct.add_bool_field("time_control_on", 3013) + self.struct.add_uint_field("battery_range_start", 3015) + self.struct.add_uint_field("battery_range_end", 3016) + self.struct.add_enum_field("auto_sleep_mode", 3061, AutoSleepMode) @property def pack_num_max(self): diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/exceptions.py b/custom_components/bluetti_bt/bluetti_bt_lib/exceptions.py index 6e708b9..ad81536 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/exceptions.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/exceptions.py @@ -2,6 +2,7 @@ # Modified copy of https://github.com/warhammerkid/bluetti_mqtt/blob/main/bluetti_mqtt/bluetooth/exc.py + class ParseError(Exception): pass diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py b/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py index 56fd903..cedeaf9 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/field_attributes.py @@ -19,205 +19,209 @@ 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 + unit_of_measurement: str | None = None + device_class: str | None = None + state_class: str | None = None options: Enum + @dataclass(frozen=True) class PowerFieldAttributes(FieldAttributes): type = FieldType.NUMERIC - unit_of_measurement = 'W' - device_class = 'power' - state_class = 'measurement' + unit_of_measurement = "W" + device_class = "power" + state_class = "measurement" + @dataclass class VoltageFieldAttributes(FieldAttributes): type = FieldType.NUMERIC - unit_of_measurement = 'V' - device_class = 'voltage' - state_class = 'measurement' + unit_of_measurement = "V" + device_class = "voltage" + state_class = "measurement" + @dataclass class CurrentFieldAttributes(FieldAttributes): type = FieldType.NUMERIC - unit_of_measurement = 'A' - device_class = 'current' - state_class = 'measurement' + unit_of_measurement = "A" + device_class = "current" + state_class = "measurement" + @dataclass class FrequencyFieldAttributes(FieldAttributes): - type = FieldType.NUMERIC, - unit_of_measurement = 'Hz', - device_class = 'frequency', - state_class ='measurement', + type = (FieldType.NUMERIC,) + unit_of_measurement = ("Hz",) + device_class = ("frequency",) + state_class = ("measurement",) + @dataclass(frozen=True) class OutletFieldAttributes(FieldAttributes): type = FieldType.BOOL - device_class = 'outlet' + device_class = "outlet" + FIELD_ATTRIBUTES = { # Base device fields - 'dc_input_power': PowerFieldAttributes( - name = 'DC Input Power', + "dc_input_power": PowerFieldAttributes( + name="DC Input Power", ), - 'ac_input_power': PowerFieldAttributes( - name = 'AC Input Power', + "ac_input_power": PowerFieldAttributes( + name="AC Input Power", ), - 'ac_output_power': PowerFieldAttributes( - name = 'AC Output Power', + "ac_output_power": PowerFieldAttributes( + name="AC Output Power", ), - 'dc_output_power': PowerFieldAttributes( - name = 'DC Output Power', + "dc_output_power": PowerFieldAttributes( + name="DC Output Power", ), - 'power_generation': FieldAttributes( - type = FieldType.NUMERIC, - name = 'Total Power Generation', - unit_of_measurement = 'kWh', - device_class = 'energy', - state_class = 'total_increasing', + "power_generation": FieldAttributes( + type=FieldType.NUMERIC, + name="Total Power Generation", + unit_of_measurement="kWh", + device_class="energy", + state_class="total_increasing", ), - 'total_battery_percent': FieldAttributes( - type = FieldType.NUMERIC, - name = 'Total Battery Percent', - unit_of_measurement = '%', - device_class = 'battery', - state_class ='measurement', + "total_battery_percent": FieldAttributes( + type=FieldType.NUMERIC, + name="Total Battery Percent", + unit_of_measurement="%", + device_class="battery", + state_class="measurement", ), - 'total_battery_voltage': VoltageFieldAttributes( - name = 'Total Battery Voltage', + "total_battery_voltage": VoltageFieldAttributes( + name="Total Battery Voltage", ), - 'ac_output_on': OutletFieldAttributes( - name = 'AC Output', + "ac_output_on": OutletFieldAttributes( + name="AC Output", ), - 'dc_output_on': OutletFieldAttributes( - name = 'DC Output', + "dc_output_on": OutletFieldAttributes( + name="DC Output", ), - # Controls - 'ac_output_on_switch': OutletFieldAttributes( - name = 'AC Output', - setter = True, + "ac_output_on_switch": OutletFieldAttributes( + name="AC Output", + setter=True, ), - 'dc_output_on_switch': OutletFieldAttributes( - name = 'DC Output', - setter = True, + "dc_output_on_switch": OutletFieldAttributes( + name="DC Output", + setter=True, ), - # Device specific fields - 'ac_output_mode': FieldAttributes( - type = FieldType.ENUM, - name = 'AC Output Mode', - options = OutputMode, + "ac_output_mode": FieldAttributes( + type=FieldType.ENUM, + name="AC Output Mode", + options=OutputMode, ), - 'internal_ac_voltage': VoltageFieldAttributes( - name = 'Internal AC Voltage', + "internal_ac_voltage": VoltageFieldAttributes( + name="Internal AC Voltage", ), - 'internal_current_one': CurrentFieldAttributes( - name = 'Internal Current Sensor 1', + "internal_current_one": CurrentFieldAttributes( + name="Internal Current Sensor 1", ), - 'internal_power_one': PowerFieldAttributes( - name = 'Internal Power Sensor 1', + "internal_power_one": PowerFieldAttributes( + name="Internal Power Sensor 1", ), - 'internal_ac_frequency': FrequencyFieldAttributes( - name = 'Internal AC Frequency', + "internal_ac_frequency": FrequencyFieldAttributes( + name="Internal AC Frequency", ), - 'internal_current_two': CurrentFieldAttributes( - name = 'Internal Current Sensor 2', + "internal_current_two": CurrentFieldAttributes( + name="Internal Current Sensor 2", ), - 'internal_power_two': PowerFieldAttributes( - name = 'Internal Power Sensor 2', + "internal_power_two": PowerFieldAttributes( + name="Internal Power Sensor 2", ), - 'ac_input_voltage': VoltageFieldAttributes( - name = 'AC Input Voltage', + "ac_input_voltage": VoltageFieldAttributes( + name="AC Input Voltage", ), - 'internal_current_three': CurrentFieldAttributes( - name = 'Internal Current Sensor 3', + "internal_current_three": CurrentFieldAttributes( + name="Internal Current Sensor 3", ), - 'internal_power_three': PowerFieldAttributes( - name = 'Internal Power Sensor 3', + "internal_power_three": PowerFieldAttributes( + name="Internal Power Sensor 3", ), - 'ac_input_frequency': FrequencyFieldAttributes( - name = 'AC Input Frequency', + "ac_input_frequency": FrequencyFieldAttributes( + name="AC Input Frequency", ), - 'internal_dc_input_voltage': VoltageFieldAttributes( - name = 'Internal DC Input Voltage', + "internal_dc_input_voltage": VoltageFieldAttributes( + name="Internal DC Input Voltage", ), - 'internal_dc_input_power': PowerFieldAttributes( - name = 'Internal DC Input Power', + "internal_dc_input_power": PowerFieldAttributes( + name="Internal DC Input Power", ), - 'internal_dc_input_current': CurrentFieldAttributes( - name = 'Internal DC Input Current', + "internal_dc_input_current": CurrentFieldAttributes( + name="Internal DC Input Current", ), - # Device specific controls - 'power_off': FieldAttributes( - type = FieldType.BUTTON, - setter = True, - name = 'Power Off', + "power_off": FieldAttributes( + type=FieldType.BUTTON, + setter=True, + name="Power Off", ), - 'auto_sleep_mode': FieldAttributes( - type = FieldType.ENUM, - setter = True, - name = 'Screen Auto Sleep Mode', - options = AutoSleepMode, + "auto_sleep_mode": FieldAttributes( + type=FieldType.ENUM, + setter=True, + name="Screen Auto Sleep Mode", + options=AutoSleepMode, ), - 'ups_mode': FieldAttributes( - type = FieldType.ENUM, - setter = False, # Disabled for safety reasons - name = 'UPS Working Mode', - options = UpsMode, + "ups_mode": FieldAttributes( + type=FieldType.ENUM, + setter=False, # Disabled for safety reasons + name="UPS Working Mode", + options=UpsMode, ), - 'split_phase_on': FieldAttributes( - type = FieldType.BOOL, - setter = False, # Disabled for safety reasons - name = 'Split Phase', + "split_phase_on": FieldAttributes( + type=FieldType.BOOL, + setter=False, # Disabled for safety reasons + name="Split Phase", ), - 'split_phase_machine_mode': FieldAttributes( - type = FieldType.ENUM, - setter = False, # Disabled for safety reasons - name = 'Split Phase Machine Type', - options = SplitPhaseMachineType, + "split_phase_machine_mode": FieldAttributes( + type=FieldType.ENUM, + setter=False, # Disabled for safety reasons + name="Split Phase Machine Type", + options=SplitPhaseMachineType, ), - 'grid_charge_on': FieldAttributes( - type = FieldType.BOOL, - setter = False, # Disabled for safety reasons - name = 'Grid Charge', + "grid_charge_on": FieldAttributes( + type=FieldType.BOOL, + setter=False, # Disabled for safety reasons + name="Grid Charge", ), - 'time_control_on': FieldAttributes( - type = FieldType.BOOL, - setter = False, # Disabled for safety reasons - name = 'Time Control', + "time_control_on": FieldAttributes( + type=FieldType.BOOL, + setter=False, # Disabled for safety reasons + name="Time Control", ), - 'battery_range_start': FieldAttributes( - type = FieldType.NUMERIC, - setter = False, # Disabled - name = 'Battery Range Start', - unit_of_measurement = '%', + "battery_range_start": FieldAttributes( + type=FieldType.NUMERIC, + setter=False, # Disabled + name="Battery Range Start", + unit_of_measurement="%", ), - 'battery_range_end': FieldAttributes( - type = FieldType.NUMERIC, - setter = False, # Disabled - name = 'Battery Range End', - unit_of_measurement = '%', + "battery_range_end": FieldAttributes( + type=FieldType.NUMERIC, + setter=False, # Disabled + name="Battery Range End", + unit_of_measurement="%", ), } + def PACK_FIELD_ATTRIBUTES(pack: int): return { - 'pack_voltage': VoltageFieldAttributes( - name = f'Battery Pack {pack} Voltage', + "pack_voltage": VoltageFieldAttributes( + name=f"Battery Pack {pack} Voltage", ), - 'pack_battery_percent': FieldAttributes( - type = FieldType.NUMERIC, - name = f'Battery Pack {pack} Percent', - unit_of_measurement = '%', - device_class ='battery', - state_class ='measurement', + "pack_battery_percent": FieldAttributes( + type=FieldType.NUMERIC, + name=f"Battery Pack {pack} Percent", + unit_of_measurement="%", + device_class="battery", + state_class="measurement", ), - 'pack_bms_version': FieldAttributes( - type = FieldType.NUMERIC, - name = f'Battery Pack {pack} BMS Version', + "pack_bms_version": FieldAttributes( + type=FieldType.NUMERIC, + name=f"Battery Pack {pack} BMS Version", ), } diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/field_enums.py b/custom_components/bluetti_bt/bluetti_bt_lib/field_enums.py index 8040c97..7d77776 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/field_enums.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/field_enums.py @@ -19,6 +19,7 @@ class AutoSleepMode(Enum): FIVE_MINUTES = 4 NEVER = 5 + @unique class UpsMode(Enum): CUSTOMIZED = 1 @@ -26,6 +27,7 @@ class UpsMode(Enum): STANDARD = 3 TIME_CONTROL = 4 + @unique class SplitPhaseMachineType(Enum): SLAVE = 0 diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/utils/commands.py b/custom_components/bluetti_bt/bluetti_bt_lib/utils/commands.py index 87cad7b..e79191b 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/utils/commands.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/utils/commands.py @@ -5,7 +5,7 @@ import struct import crcmod.predefined -modbus_crc = crcmod.predefined.mkCrcFun('modbus') +modbus_crc = crcmod.predefined.mkCrcFun("modbus") class DeviceCommand: @@ -16,7 +16,7 @@ class DeviceCommand: self.cmd[0] = 1 # MODBUS address self.cmd[1] = function_code self.cmd[2:-2] = data - struct.pack_into(' int: """Returns the expected response size in bytes""" @@ -39,7 +39,7 @@ class DeviceCommand: return False crc = modbus_crc(response[0:-2]) - crc_bytes = crc.to_bytes(2, byteorder='little') + crc_bytes = crc.to_bytes(2, byteorder="little") return response[-2:] == crc_bytes def parse_response(self, response: bytes): @@ -52,7 +52,7 @@ class ReadHoldingRegisters(DeviceCommand): self.starting_address = starting_address self.quantity = quantity - super().__init__(3, struct.pack('!HH', starting_address, quantity)) + super().__init__(3, struct.pack("!HH", starting_address, quantity)) def response_size(self): # 3 byte header @@ -64,9 +64,7 @@ class ReadHoldingRegisters(DeviceCommand): return bytes(response[3:-2]) def __repr__(self): - return ( - f'ReadHoldingRegisters(starting_address={self.starting_address}, quantity={self.quantity})' - ) + return f"ReadHoldingRegisters(starting_address={self.starting_address}, quantity={self.quantity})" class WriteSingleRegister(DeviceCommand): @@ -74,7 +72,7 @@ class WriteSingleRegister(DeviceCommand): self.address = address self.value = value - super().__init__(6, struct.pack('!HH', address, value)) + super().__init__(6, struct.pack("!HH", address, value)) def response_size(self): return 8 @@ -83,22 +81,20 @@ class WriteSingleRegister(DeviceCommand): return bytes(response[4:6]) def __repr__(self): - return ( - f'WriteSingleRegister(address={self.address}, value={self.value:#04x})' - ) + return f"WriteSingleRegister(address={self.address}, value={self.value:#04x})" class WriteMultipleRegisters(DeviceCommand): def __init__(self, starting_address: int, data: bytes): if len(data) % 2 != 0: - raise ValueError('data size must be multiple of 2') + raise ValueError("data size must be multiple of 2") self.starting_address = starting_address self.data = data body = bytearray(len(data) + 5) half_len = len(data) >> 1 - struct.pack_into('!HHB', body, 0, starting_address, half_len, len(data)) + struct.pack_into("!HHB", body, 0, starting_address, half_len, len(data)) body[5:] = data super().__init__(16, body) @@ -106,6 +102,4 @@ class WriteMultipleRegisters(DeviceCommand): return 8 def __repr__(self): - return ( - f'WriteMultipleRegisters(starting_address={self.starting_address}, data={self.data})' - ) + return f"WriteMultipleRegisters(starting_address={self.starting_address}, data={self.data})" diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/utils/struct.py b/custom_components/bluetti_bt/bluetti_bt_lib/utils/struct.py index 9e9f27b..ea9418f 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/utils/struct.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/utils/struct.py @@ -35,7 +35,7 @@ class UintField(DeviceField): super().__init__(name, address, 1) def parse(self, data: bytes) -> int: - return struct.unpack('!H', data)[0] + return struct.unpack("!H", data)[0] def in_range(self, val: int) -> bool: if self.range is None: @@ -49,7 +49,7 @@ class BoolField(DeviceField): super().__init__(name, address, 1) def parse(self, data: bytes) -> bool: - return struct.unpack('!H', data)[0] == 1 + return struct.unpack("!H", data)[0] == 1 class EnumField(DeviceField): @@ -58,18 +58,20 @@ class EnumField(DeviceField): super().__init__(name, address, 1) def parse(self, data: bytes) -> Any: - val = struct.unpack('!H', data)[0] + val = struct.unpack("!H", data)[0] return self.enum(val) class DecimalField(DeviceField): - def __init__(self, name: str, address: int, scale: int, range: Optional[Tuple[int, int]]): + def __init__( + self, name: str, address: int, scale: int, range: Optional[Tuple[int, int]] + ): self.scale = scale self.range = range super().__init__(name, address, 1) def parse(self, data: bytes) -> Decimal: - val = Decimal(struct.unpack('!H', data)[0]) + val = Decimal(struct.unpack("!H", data)[0]) return val / 10 ** self.scale def in_range(self, val: Decimal) -> bool: @@ -85,20 +87,22 @@ class DecimalArrayField(DeviceField): super().__init__(name, address, size) def parse(self, data: bytes) -> Decimal: - values = list(struct.unpack(f'!{self.size}H', data)) + values = list(struct.unpack(f"!{self.size}H", data)) return [Decimal(v) / 10 ** self.scale for v in values] class StringField(DeviceField): """Fixed-width null-terminated string field""" + def parse(self, data: bytes) -> str: - return data.rstrip(b'\0').decode('ascii') + return data.rstrip(b"\0").decode("ascii") class SwapStringField(DeviceField): """Fixed-width null-terminated string field""" + def parse(self, data: bytes) -> str: - return swap_bytes(data).rstrip(b'\0').decode('ascii') + return swap_bytes(data).rstrip(b"\0").decode("ascii") class VersionField(DeviceField): @@ -106,7 +110,7 @@ class VersionField(DeviceField): super().__init__(name, address, 2) def parse(self, data: bytes) -> int: - values = struct.unpack('!2H', data) + values = struct.unpack("!2H", data) return Decimal(values[0] + (values[1] << 16)) / 100 @@ -115,7 +119,7 @@ class SerialNumberField(DeviceField): super().__init__(name, address, 4) def parse(self, data: bytes) -> int: - values = struct.unpack('!4H', data) + values = struct.unpack("!4H", data) return values[0] + (values[1] << 16) + (values[2] << 32) + (values[3] << 48) @@ -134,7 +138,9 @@ class DeviceStruct: def add_enum_field(self, name: str, address: int, enum: Type[Enum]): self.fields.append(EnumField(name, address, enum)) - def add_decimal_field(self, name: str, address: int, scale: int, range: Tuple[int, int] = None): + def add_decimal_field( + self, name: str, address: int, scale: int, range: Tuple[int, int] = None + ): self.fields.append(DecimalField(name, address, scale, range)) def add_decimal_array_field(self, name: str, address: int, size: int, scale: int): @@ -159,14 +165,15 @@ class DeviceStruct: # Filter out fields not in range r = range(starting_address, starting_address + data_size) - fields = [f for f in self.fields - if f.address in r and f.address + f.size - 1 in r] + fields = [ + f for f in self.fields if f.address in r and f.address + f.size - 1 in r + ] # Parse fields parsed = {} for f in fields: data_start = 2 * (f.address - starting_address) - field_data = data[data_start:data_start + 2 * f.size] + field_data = data[data_start : data_start + 2 * f.size] val = f.parse(field_data) # Skip if the value is "out-of-range" - sometimes the sensors