black code formatting

This commit is contained in:
Patrick762 committed 2024-02-17 20:14:12 +01:00
1 parent 928ff2bf5d
commit 432d46d0f1
11 files changed
+257 -242

No files matched your search

@@ -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):
@@ -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 [
@@ -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 []
@@ -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"
@@ -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):
@@ -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):
@@ -2,6 +2,7 @@
# Modified copy of https://github.com/warhammerkid/bluetti_mqtt/blob/main/bluetti_mqtt/bluetooth/exc.py
class ParseError(Exception):
pass
@@ -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",
),
}
@@ -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
@@ -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('<H', self.cmd, -2, modbus_crc(self.cmd[:-2]))
struct.pack_into("<H", self.cmd, -2, modbus_crc(self.cmd[:-2]))
def response_size(self) -> 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})"
@@ -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