black code formatting
This commit is contained in:
1 parent
928ff2bf5d
commit
432d46d0f1
11 files changed
+255
-240
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)
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user