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):
|
def build_setter_command(self, field: str, value: Any):
|
||||||
matches = [f for f in self.struct.fields if f.name == field]
|
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
|
# Convert value to an integer
|
||||||
if isinstance(device_field, EnumField):
|
if isinstance(device_field, EnumField):
|
||||||
|
|||||||
@@ -12,38 +12,40 @@ class ProtocolV1Device(BluettiDevice):
|
|||||||
self.struct = DeviceStruct()
|
self.struct = DeviceStruct()
|
||||||
|
|
||||||
# Device info
|
# Device info
|
||||||
self.struct.add_string_field('device_type', 10, 6)
|
self.struct.add_string_field("device_type", 10, 6)
|
||||||
self.struct.add_sn_field('serial_number', 17)
|
self.struct.add_sn_field("serial_number", 17)
|
||||||
|
|
||||||
# Power IO
|
# Power IO
|
||||||
self.struct.add_uint_field('dc_input_power', 36)
|
self.struct.add_uint_field("dc_input_power", 36)
|
||||||
self.struct.add_uint_field('ac_input_power', 37)
|
self.struct.add_uint_field("ac_input_power", 37)
|
||||||
self.struct.add_uint_field('ac_output_power', 38)
|
self.struct.add_uint_field("ac_output_power", 38)
|
||||||
self.struct.add_uint_field('dc_output_power', 39)
|
self.struct.add_uint_field("dc_output_power", 39)
|
||||||
|
|
||||||
# Power IO statistics
|
# 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
|
# Battery
|
||||||
self.struct.add_uint_field('total_battery_percent', 43)
|
self.struct.add_uint_field("total_battery_percent", 43)
|
||||||
|
|
||||||
# Battery packs
|
# Battery packs
|
||||||
self.struct.add_uint_field('pack_num_max', 91) # internal
|
self.struct.add_uint_field("pack_num_max", 91) # internal
|
||||||
self.struct.add_decimal_field('total_battery_voltage', 92, 1)
|
self.struct.add_decimal_field("total_battery_voltage", 92, 1)
|
||||||
self.struct.add_uint_field('pack_num', 96) # internal
|
self.struct.add_uint_field("pack_num", 96) # internal
|
||||||
self.struct.add_decimal_field('pack_voltage', 98, 2) # Full pack voltage
|
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_battery_percent", 99)
|
||||||
|
|
||||||
# Output state
|
# Output state
|
||||||
self.struct.add_bool_field('ac_output_on', 48)
|
self.struct.add_bool_field("ac_output_on", 48)
|
||||||
self.struct.add_bool_field('dc_output_on', 49)
|
self.struct.add_bool_field("dc_output_on", 49)
|
||||||
|
|
||||||
# Pack selector
|
# Pack selector
|
||||||
self.struct.add_uint_field('pack_num', 3006) # internal
|
self.struct.add_uint_field("pack_num", 3006) # internal
|
||||||
|
|
||||||
# Output controls
|
# Output controls
|
||||||
self.struct.add_bool_field('ac_output_on_switch', 3007)
|
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("dc_output_on_switch", 3008)
|
||||||
|
|
||||||
super().__init__(address, type, sn)
|
super().__init__(address, type, sn)
|
||||||
|
|
||||||
|
|||||||
@@ -12,38 +12,40 @@ class ProtocolV2Device(BluettiDevice):
|
|||||||
self.struct = DeviceStruct()
|
self.struct = DeviceStruct()
|
||||||
|
|
||||||
# Device info
|
# Device info
|
||||||
self.struct.add_string_field('device_type', 110, 6)
|
self.struct.add_string_field("device_type", 110, 6)
|
||||||
self.struct.add_sn_field('serial_number', 116)
|
self.struct.add_sn_field("serial_number", 116)
|
||||||
|
|
||||||
# Power IO
|
# Power IO
|
||||||
#self.struct.add_uint_field('dc_input_power', ?)
|
# self.struct.add_uint_field('dc_input_power', ?)
|
||||||
#self.struct.add_uint_field('ac_input_power', ?)
|
# self.struct.add_uint_field('ac_input_power', ?)
|
||||||
#self.struct.add_uint_field('ac_output_power', ?)
|
# self.struct.add_uint_field('ac_output_power', ?)
|
||||||
#self.struct.add_uint_field('dc_output_power', ?)
|
# self.struct.add_uint_field('dc_output_power', ?)
|
||||||
|
|
||||||
# Power IO statistics
|
# 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
|
# Battery
|
||||||
self.struct.add_uint_field('total_battery_percent', 102)
|
self.struct.add_uint_field("total_battery_percent", 102)
|
||||||
|
|
||||||
# Battery packs
|
# Battery packs
|
||||||
#self.struct.add_uint_field('pack_num_max', ?) # internal
|
# self.struct.add_uint_field('pack_num_max', ?) # internal
|
||||||
#self.struct.add_decimal_field('total_battery_voltage', ?, 1)
|
# self.struct.add_decimal_field('total_battery_voltage', ?, 1)
|
||||||
#self.struct.add_uint_field('pack_num', ?) # internal
|
# self.struct.add_uint_field('pack_num', ?) # internal
|
||||||
#self.struct.add_decimal_field('pack_voltage', ?, 2) # Full pack voltage
|
# 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_battery_percent', ?)
|
||||||
|
|
||||||
# Output state
|
# Output state
|
||||||
#self.struct.add_bool_field('ac_output_on', ?)
|
# self.struct.add_bool_field('ac_output_on', ?)
|
||||||
#self.struct.add_bool_field('dc_output_on', ?)
|
# self.struct.add_bool_field('dc_output_on', ?)
|
||||||
|
|
||||||
# Pack selector
|
# Pack selector
|
||||||
#self.struct.add_uint_field('pack_num', ?) # internal
|
# self.struct.add_uint_field('pack_num', ?) # internal
|
||||||
|
|
||||||
# Output controls
|
# Output controls
|
||||||
#self.struct.add_bool_field('ac_output_on_switch', ?)
|
# self.struct.add_bool_field('ac_output_on_switch', ?)
|
||||||
#self.struct.add_bool_field('dc_output_on_switch', ?)
|
# self.struct.add_bool_field('dc_output_on_switch', ?)
|
||||||
|
|
||||||
# Add battery fields here?
|
# Add battery fields here?
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Const definitions."""
|
"""Const definitions."""
|
||||||
|
|
||||||
RESPONSE_TIMEOUT = 5
|
RESPONSE_TIMEOUT = 5
|
||||||
WRITE_UUID = '0000ff02-0000-1000-8000-00805f9b34fb'
|
WRITE_UUID = "0000ff02-0000-1000-8000-00805f9b34fb"
|
||||||
NOTIFY_UUID = '0000ff01-0000-1000-8000-00805f9b34fb'
|
NOTIFY_UUID = "0000ff01-0000-1000-8000-00805f9b34fb"
|
||||||
DEVICE_NAME_UUID = '00002a00-0000-1000-8000-00805f9b34fb'
|
DEVICE_NAME_UUID = "00002a00-0000-1000-8000-00805f9b34fb"
|
||||||
@@ -12,21 +12,21 @@ class AC200M(ProtocolV1Device):
|
|||||||
super().__init__(address, "AC200M", sn)
|
super().__init__(address, "AC200M", sn)
|
||||||
|
|
||||||
# Details
|
# Details
|
||||||
self.struct.add_enum_field('ac_output_mode', 70, OutputMode)
|
self.struct.add_enum_field("ac_output_mode", 70, OutputMode)
|
||||||
self.struct.add_uint_field('internal_ac_voltage', 71)
|
self.struct.add_uint_field("internal_ac_voltage", 71)
|
||||||
self.struct.add_decimal_field('internal_current_one', 72, 1)
|
self.struct.add_decimal_field("internal_current_one", 72, 1)
|
||||||
self.struct.add_uint_field('internal_power_one', 73)
|
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_ac_frequency", 74, 1)
|
||||||
self.struct.add_uint_field('internal_dc_input_voltage', 86)
|
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_power", 87, 1)
|
||||||
self.struct.add_decimal_field('internal_dc_input_current', 88, 2)
|
self.struct.add_decimal_field("internal_dc_input_current", 88, 2)
|
||||||
|
|
||||||
# Battery packs
|
# 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
|
# Controls
|
||||||
self.struct.add_bool_field('power_off', 3060)
|
self.struct.add_bool_field("power_off", 3060)
|
||||||
self.struct.add_enum_field('auto_sleep_mode', 3061, AutoSleepMode)
|
self.struct.add_enum_field("auto_sleep_mode", 3061, AutoSleepMode)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pack_num_max(self):
|
def pack_num_max(self):
|
||||||
|
|||||||
@@ -12,35 +12,36 @@ class AC300(ProtocolV1Device):
|
|||||||
super().__init__(address, "AC300", sn)
|
super().__init__(address, "AC300", sn)
|
||||||
|
|
||||||
# Details
|
# Details
|
||||||
self.struct.add_enum_field('ac_output_mode', 70, OutputMode)
|
self.struct.add_enum_field("ac_output_mode", 70, OutputMode)
|
||||||
self.struct.add_uint_field('internal_ac_voltage', 71)
|
self.struct.add_uint_field("internal_ac_voltage", 71)
|
||||||
self.struct.add_decimal_field('internal_current_one', 72, 1)
|
self.struct.add_decimal_field("internal_current_one", 72, 1)
|
||||||
self.struct.add_uint_field('internal_power_one', 73)
|
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_ac_frequency", 74, 1)
|
||||||
self.struct.add_decimal_field('internal_current_two', 75, 1)
|
self.struct.add_decimal_field("internal_current_two", 75, 1)
|
||||||
self.struct.add_uint_field('internal_power_two', 76)
|
self.struct.add_uint_field("internal_power_two", 76)
|
||||||
self.struct.add_decimal_field('ac_input_voltage', 77, 1)
|
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_decimal_field("internal_current_three", 78, 1, (0, 100))
|
||||||
self.struct.add_uint_field('internal_power_three', 79)
|
self.struct.add_uint_field("internal_power_three", 79)
|
||||||
self.struct.add_decimal_field('ac_input_frequency', 80, 2)
|
self.struct.add_decimal_field("ac_input_frequency", 80, 2)
|
||||||
self.struct.add_uint_field('internal_dc_input_voltage', 86)
|
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_power", 87, 1)
|
||||||
self.struct.add_decimal_field('internal_dc_input_current', 88, 2)
|
self.struct.add_decimal_field("internal_dc_input_current", 88, 2)
|
||||||
|
|
||||||
# Battery packs
|
# 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
|
||||||
self.struct.add_version_field('pack_bms_version', 201)
|
self.struct.add_version_field("pack_bms_version", 201)
|
||||||
|
|
||||||
# Controls
|
# Controls
|
||||||
self.struct.add_enum_field('ups_mode', 3001, UpsMode)
|
self.struct.add_enum_field("ups_mode", 3001, UpsMode)
|
||||||
self.struct.add_bool_field('split_phase_on', 3004)
|
self.struct.add_bool_field("split_phase_on", 3004)
|
||||||
self.struct.add_enum_field('split_phase_machine_mode', 3005, SplitPhaseMachineType)
|
self.struct.add_enum_field(
|
||||||
self.struct.add_bool_field('grid_charge_on', 3011)
|
"split_phase_machine_mode", 3005, SplitPhaseMachineType
|
||||||
self.struct.add_bool_field('time_control_on', 3013)
|
)
|
||||||
self.struct.add_uint_field('battery_range_start', 3015)
|
self.struct.add_bool_field("grid_charge_on", 3011)
|
||||||
self.struct.add_uint_field('battery_range_end', 3016)
|
self.struct.add_bool_field("time_control_on", 3013)
|
||||||
self.struct.add_enum_field('auto_sleep_mode', 3061, AutoSleepMode)
|
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
|
@property
|
||||||
def pack_num_max(self):
|
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
|
# Modified copy of https://github.com/warhammerkid/bluetti_mqtt/blob/main/bluetti_mqtt/bluetooth/exc.py
|
||||||
|
|
||||||
|
|
||||||
class ParseError(Exception):
|
class ParseError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -19,205 +19,209 @@ class FieldAttributes:
|
|||||||
type: FieldType
|
type: FieldType
|
||||||
setter: bool = False
|
setter: bool = False
|
||||||
name: str
|
name: str
|
||||||
unit_of_measurement: str|None = None
|
unit_of_measurement: str | None = None
|
||||||
device_class: str|None = None
|
device_class: str | None = None
|
||||||
state_class: str|None = None
|
state_class: str | None = None
|
||||||
options: Enum
|
options: Enum
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class PowerFieldAttributes(FieldAttributes):
|
class PowerFieldAttributes(FieldAttributes):
|
||||||
type = FieldType.NUMERIC
|
type = FieldType.NUMERIC
|
||||||
unit_of_measurement = 'W'
|
unit_of_measurement = "W"
|
||||||
device_class = 'power'
|
device_class = "power"
|
||||||
state_class = 'measurement'
|
state_class = "measurement"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VoltageFieldAttributes(FieldAttributes):
|
class VoltageFieldAttributes(FieldAttributes):
|
||||||
type = FieldType.NUMERIC
|
type = FieldType.NUMERIC
|
||||||
unit_of_measurement = 'V'
|
unit_of_measurement = "V"
|
||||||
device_class = 'voltage'
|
device_class = "voltage"
|
||||||
state_class = 'measurement'
|
state_class = "measurement"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CurrentFieldAttributes(FieldAttributes):
|
class CurrentFieldAttributes(FieldAttributes):
|
||||||
type = FieldType.NUMERIC
|
type = FieldType.NUMERIC
|
||||||
unit_of_measurement = 'A'
|
unit_of_measurement = "A"
|
||||||
device_class = 'current'
|
device_class = "current"
|
||||||
state_class = 'measurement'
|
state_class = "measurement"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FrequencyFieldAttributes(FieldAttributes):
|
class FrequencyFieldAttributes(FieldAttributes):
|
||||||
type = FieldType.NUMERIC,
|
type = (FieldType.NUMERIC,)
|
||||||
unit_of_measurement = 'Hz',
|
unit_of_measurement = ("Hz",)
|
||||||
device_class = 'frequency',
|
device_class = ("frequency",)
|
||||||
state_class ='measurement',
|
state_class = ("measurement",)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class OutletFieldAttributes(FieldAttributes):
|
class OutletFieldAttributes(FieldAttributes):
|
||||||
type = FieldType.BOOL
|
type = FieldType.BOOL
|
||||||
device_class = 'outlet'
|
device_class = "outlet"
|
||||||
|
|
||||||
|
|
||||||
FIELD_ATTRIBUTES = {
|
FIELD_ATTRIBUTES = {
|
||||||
# Base device fields
|
# Base device fields
|
||||||
'dc_input_power': PowerFieldAttributes(
|
"dc_input_power": PowerFieldAttributes(
|
||||||
name = 'DC Input Power',
|
name="DC Input Power",
|
||||||
),
|
),
|
||||||
'ac_input_power': PowerFieldAttributes(
|
"ac_input_power": PowerFieldAttributes(
|
||||||
name = 'AC Input Power',
|
name="AC Input Power",
|
||||||
),
|
),
|
||||||
'ac_output_power': PowerFieldAttributes(
|
"ac_output_power": PowerFieldAttributes(
|
||||||
name = 'AC Output Power',
|
name="AC Output Power",
|
||||||
),
|
),
|
||||||
'dc_output_power': PowerFieldAttributes(
|
"dc_output_power": PowerFieldAttributes(
|
||||||
name = 'DC Output Power',
|
name="DC Output Power",
|
||||||
),
|
),
|
||||||
'power_generation': FieldAttributes(
|
"power_generation": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
name = 'Total Power Generation',
|
name="Total Power Generation",
|
||||||
unit_of_measurement = 'kWh',
|
unit_of_measurement="kWh",
|
||||||
device_class = 'energy',
|
device_class="energy",
|
||||||
state_class = 'total_increasing',
|
state_class="total_increasing",
|
||||||
),
|
),
|
||||||
'total_battery_percent': FieldAttributes(
|
"total_battery_percent": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
name = 'Total Battery Percent',
|
name="Total Battery Percent",
|
||||||
unit_of_measurement = '%',
|
unit_of_measurement="%",
|
||||||
device_class = 'battery',
|
device_class="battery",
|
||||||
state_class ='measurement',
|
state_class="measurement",
|
||||||
),
|
),
|
||||||
'total_battery_voltage': VoltageFieldAttributes(
|
"total_battery_voltage": VoltageFieldAttributes(
|
||||||
name = 'Total Battery Voltage',
|
name="Total Battery Voltage",
|
||||||
),
|
),
|
||||||
'ac_output_on': OutletFieldAttributes(
|
"ac_output_on": OutletFieldAttributes(
|
||||||
name = 'AC Output',
|
name="AC Output",
|
||||||
),
|
),
|
||||||
'dc_output_on': OutletFieldAttributes(
|
"dc_output_on": OutletFieldAttributes(
|
||||||
name = 'DC Output',
|
name="DC Output",
|
||||||
),
|
),
|
||||||
|
|
||||||
# Controls
|
# Controls
|
||||||
'ac_output_on_switch': OutletFieldAttributes(
|
"ac_output_on_switch": OutletFieldAttributes(
|
||||||
name = 'AC Output',
|
name="AC Output",
|
||||||
setter = True,
|
setter=True,
|
||||||
),
|
),
|
||||||
'dc_output_on_switch': OutletFieldAttributes(
|
"dc_output_on_switch": OutletFieldAttributes(
|
||||||
name = 'DC Output',
|
name="DC Output",
|
||||||
setter = True,
|
setter=True,
|
||||||
),
|
),
|
||||||
|
|
||||||
# Device specific fields
|
# Device specific fields
|
||||||
'ac_output_mode': FieldAttributes(
|
"ac_output_mode": FieldAttributes(
|
||||||
type = FieldType.ENUM,
|
type=FieldType.ENUM,
|
||||||
name = 'AC Output Mode',
|
name="AC Output Mode",
|
||||||
options = OutputMode,
|
options=OutputMode,
|
||||||
),
|
),
|
||||||
'internal_ac_voltage': VoltageFieldAttributes(
|
"internal_ac_voltage": VoltageFieldAttributes(
|
||||||
name = 'Internal AC Voltage',
|
name="Internal AC Voltage",
|
||||||
),
|
),
|
||||||
'internal_current_one': CurrentFieldAttributes(
|
"internal_current_one": CurrentFieldAttributes(
|
||||||
name = 'Internal Current Sensor 1',
|
name="Internal Current Sensor 1",
|
||||||
),
|
),
|
||||||
'internal_power_one': PowerFieldAttributes(
|
"internal_power_one": PowerFieldAttributes(
|
||||||
name = 'Internal Power Sensor 1',
|
name="Internal Power Sensor 1",
|
||||||
),
|
),
|
||||||
'internal_ac_frequency': FrequencyFieldAttributes(
|
"internal_ac_frequency": FrequencyFieldAttributes(
|
||||||
name = 'Internal AC Frequency',
|
name="Internal AC Frequency",
|
||||||
),
|
),
|
||||||
'internal_current_two': CurrentFieldAttributes(
|
"internal_current_two": CurrentFieldAttributes(
|
||||||
name = 'Internal Current Sensor 2',
|
name="Internal Current Sensor 2",
|
||||||
),
|
),
|
||||||
'internal_power_two': PowerFieldAttributes(
|
"internal_power_two": PowerFieldAttributes(
|
||||||
name = 'Internal Power Sensor 2',
|
name="Internal Power Sensor 2",
|
||||||
),
|
),
|
||||||
'ac_input_voltage': VoltageFieldAttributes(
|
"ac_input_voltage": VoltageFieldAttributes(
|
||||||
name = 'AC Input Voltage',
|
name="AC Input Voltage",
|
||||||
),
|
),
|
||||||
'internal_current_three': CurrentFieldAttributes(
|
"internal_current_three": CurrentFieldAttributes(
|
||||||
name = 'Internal Current Sensor 3',
|
name="Internal Current Sensor 3",
|
||||||
),
|
),
|
||||||
'internal_power_three': PowerFieldAttributes(
|
"internal_power_three": PowerFieldAttributes(
|
||||||
name = 'Internal Power Sensor 3',
|
name="Internal Power Sensor 3",
|
||||||
),
|
),
|
||||||
'ac_input_frequency': FrequencyFieldAttributes(
|
"ac_input_frequency": FrequencyFieldAttributes(
|
||||||
name = 'AC Input Frequency',
|
name="AC Input Frequency",
|
||||||
),
|
),
|
||||||
'internal_dc_input_voltage': VoltageFieldAttributes(
|
"internal_dc_input_voltage": VoltageFieldAttributes(
|
||||||
name = 'Internal DC Input Voltage',
|
name="Internal DC Input Voltage",
|
||||||
),
|
),
|
||||||
'internal_dc_input_power': PowerFieldAttributes(
|
"internal_dc_input_power": PowerFieldAttributes(
|
||||||
name = 'Internal DC Input Power',
|
name="Internal DC Input Power",
|
||||||
),
|
),
|
||||||
'internal_dc_input_current': CurrentFieldAttributes(
|
"internal_dc_input_current": CurrentFieldAttributes(
|
||||||
name = 'Internal DC Input Current',
|
name="Internal DC Input Current",
|
||||||
),
|
),
|
||||||
|
|
||||||
# Device specific controls
|
# Device specific controls
|
||||||
'power_off': FieldAttributes(
|
"power_off": FieldAttributes(
|
||||||
type = FieldType.BUTTON,
|
type=FieldType.BUTTON,
|
||||||
setter = True,
|
setter=True,
|
||||||
name = 'Power Off',
|
name="Power Off",
|
||||||
),
|
),
|
||||||
'auto_sleep_mode': FieldAttributes(
|
"auto_sleep_mode": FieldAttributes(
|
||||||
type = FieldType.ENUM,
|
type=FieldType.ENUM,
|
||||||
setter = True,
|
setter=True,
|
||||||
name = 'Screen Auto Sleep Mode',
|
name="Screen Auto Sleep Mode",
|
||||||
options = AutoSleepMode,
|
options=AutoSleepMode,
|
||||||
),
|
),
|
||||||
'ups_mode': FieldAttributes(
|
"ups_mode": FieldAttributes(
|
||||||
type = FieldType.ENUM,
|
type=FieldType.ENUM,
|
||||||
setter = False, # Disabled for safety reasons
|
setter=False, # Disabled for safety reasons
|
||||||
name = 'UPS Working Mode',
|
name="UPS Working Mode",
|
||||||
options = UpsMode,
|
options=UpsMode,
|
||||||
),
|
),
|
||||||
'split_phase_on': FieldAttributes(
|
"split_phase_on": FieldAttributes(
|
||||||
type = FieldType.BOOL,
|
type=FieldType.BOOL,
|
||||||
setter = False, # Disabled for safety reasons
|
setter=False, # Disabled for safety reasons
|
||||||
name = 'Split Phase',
|
name="Split Phase",
|
||||||
),
|
),
|
||||||
'split_phase_machine_mode': FieldAttributes(
|
"split_phase_machine_mode": FieldAttributes(
|
||||||
type = FieldType.ENUM,
|
type=FieldType.ENUM,
|
||||||
setter = False, # Disabled for safety reasons
|
setter=False, # Disabled for safety reasons
|
||||||
name = 'Split Phase Machine Type',
|
name="Split Phase Machine Type",
|
||||||
options = SplitPhaseMachineType,
|
options=SplitPhaseMachineType,
|
||||||
),
|
),
|
||||||
'grid_charge_on': FieldAttributes(
|
"grid_charge_on": FieldAttributes(
|
||||||
type = FieldType.BOOL,
|
type=FieldType.BOOL,
|
||||||
setter = False, # Disabled for safety reasons
|
setter=False, # Disabled for safety reasons
|
||||||
name = 'Grid Charge',
|
name="Grid Charge",
|
||||||
),
|
),
|
||||||
'time_control_on': FieldAttributes(
|
"time_control_on": FieldAttributes(
|
||||||
type = FieldType.BOOL,
|
type=FieldType.BOOL,
|
||||||
setter = False, # Disabled for safety reasons
|
setter=False, # Disabled for safety reasons
|
||||||
name = 'Time Control',
|
name="Time Control",
|
||||||
),
|
),
|
||||||
'battery_range_start': FieldAttributes(
|
"battery_range_start": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
setter = False, # Disabled
|
setter=False, # Disabled
|
||||||
name = 'Battery Range Start',
|
name="Battery Range Start",
|
||||||
unit_of_measurement = '%',
|
unit_of_measurement="%",
|
||||||
),
|
),
|
||||||
'battery_range_end': FieldAttributes(
|
"battery_range_end": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
setter = False, # Disabled
|
setter=False, # Disabled
|
||||||
name = 'Battery Range End',
|
name="Battery Range End",
|
||||||
unit_of_measurement = '%',
|
unit_of_measurement="%",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def PACK_FIELD_ATTRIBUTES(pack: int):
|
def PACK_FIELD_ATTRIBUTES(pack: int):
|
||||||
return {
|
return {
|
||||||
'pack_voltage': VoltageFieldAttributes(
|
"pack_voltage": VoltageFieldAttributes(
|
||||||
name = f'Battery Pack {pack} Voltage',
|
name=f"Battery Pack {pack} Voltage",
|
||||||
),
|
),
|
||||||
'pack_battery_percent': FieldAttributes(
|
"pack_battery_percent": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
name = f'Battery Pack {pack} Percent',
|
name=f"Battery Pack {pack} Percent",
|
||||||
unit_of_measurement = '%',
|
unit_of_measurement="%",
|
||||||
device_class ='battery',
|
device_class="battery",
|
||||||
state_class ='measurement',
|
state_class="measurement",
|
||||||
),
|
),
|
||||||
'pack_bms_version': FieldAttributes(
|
"pack_bms_version": FieldAttributes(
|
||||||
type = FieldType.NUMERIC,
|
type=FieldType.NUMERIC,
|
||||||
name = f'Battery Pack {pack} BMS Version',
|
name=f"Battery Pack {pack} BMS Version",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ class AutoSleepMode(Enum):
|
|||||||
FIVE_MINUTES = 4
|
FIVE_MINUTES = 4
|
||||||
NEVER = 5
|
NEVER = 5
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class UpsMode(Enum):
|
class UpsMode(Enum):
|
||||||
CUSTOMIZED = 1
|
CUSTOMIZED = 1
|
||||||
@@ -26,6 +27,7 @@ class UpsMode(Enum):
|
|||||||
STANDARD = 3
|
STANDARD = 3
|
||||||
TIME_CONTROL = 4
|
TIME_CONTROL = 4
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class SplitPhaseMachineType(Enum):
|
class SplitPhaseMachineType(Enum):
|
||||||
SLAVE = 0
|
SLAVE = 0
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import struct
|
import struct
|
||||||
import crcmod.predefined
|
import crcmod.predefined
|
||||||
|
|
||||||
modbus_crc = crcmod.predefined.mkCrcFun('modbus')
|
modbus_crc = crcmod.predefined.mkCrcFun("modbus")
|
||||||
|
|
||||||
|
|
||||||
class DeviceCommand:
|
class DeviceCommand:
|
||||||
@@ -16,7 +16,7 @@ class DeviceCommand:
|
|||||||
self.cmd[0] = 1 # MODBUS address
|
self.cmd[0] = 1 # MODBUS address
|
||||||
self.cmd[1] = function_code
|
self.cmd[1] = function_code
|
||||||
self.cmd[2:-2] = data
|
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:
|
def response_size(self) -> int:
|
||||||
"""Returns the expected response size in bytes"""
|
"""Returns the expected response size in bytes"""
|
||||||
@@ -39,7 +39,7 @@ class DeviceCommand:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
crc = modbus_crc(response[0:-2])
|
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
|
return response[-2:] == crc_bytes
|
||||||
|
|
||||||
def parse_response(self, response: bytes):
|
def parse_response(self, response: bytes):
|
||||||
@@ -52,7 +52,7 @@ class ReadHoldingRegisters(DeviceCommand):
|
|||||||
self.starting_address = starting_address
|
self.starting_address = starting_address
|
||||||
self.quantity = quantity
|
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):
|
def response_size(self):
|
||||||
# 3 byte header
|
# 3 byte header
|
||||||
@@ -64,9 +64,7 @@ class ReadHoldingRegisters(DeviceCommand):
|
|||||||
return bytes(response[3:-2])
|
return bytes(response[3:-2])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return f"ReadHoldingRegisters(starting_address={self.starting_address}, quantity={self.quantity})"
|
||||||
f'ReadHoldingRegisters(starting_address={self.starting_address}, quantity={self.quantity})'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WriteSingleRegister(DeviceCommand):
|
class WriteSingleRegister(DeviceCommand):
|
||||||
@@ -74,7 +72,7 @@ class WriteSingleRegister(DeviceCommand):
|
|||||||
self.address = address
|
self.address = address
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
super().__init__(6, struct.pack('!HH', address, value))
|
super().__init__(6, struct.pack("!HH", address, value))
|
||||||
|
|
||||||
def response_size(self):
|
def response_size(self):
|
||||||
return 8
|
return 8
|
||||||
@@ -83,22 +81,20 @@ class WriteSingleRegister(DeviceCommand):
|
|||||||
return bytes(response[4:6])
|
return bytes(response[4:6])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return f"WriteSingleRegister(address={self.address}, value={self.value:#04x})"
|
||||||
f'WriteSingleRegister(address={self.address}, value={self.value:#04x})'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WriteMultipleRegisters(DeviceCommand):
|
class WriteMultipleRegisters(DeviceCommand):
|
||||||
def __init__(self, starting_address: int, data: bytes):
|
def __init__(self, starting_address: int, data: bytes):
|
||||||
if len(data) % 2 != 0:
|
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.starting_address = starting_address
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
body = bytearray(len(data) + 5)
|
body = bytearray(len(data) + 5)
|
||||||
half_len = len(data) >> 1
|
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
|
body[5:] = data
|
||||||
super().__init__(16, body)
|
super().__init__(16, body)
|
||||||
|
|
||||||
@@ -106,6 +102,4 @@ class WriteMultipleRegisters(DeviceCommand):
|
|||||||
return 8
|
return 8
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return f"WriteMultipleRegisters(starting_address={self.starting_address}, data={self.data})"
|
||||||
f'WriteMultipleRegisters(starting_address={self.starting_address}, data={self.data})'
|
|
||||||
)
|
|
||||||
@@ -35,7 +35,7 @@ class UintField(DeviceField):
|
|||||||
super().__init__(name, address, 1)
|
super().__init__(name, address, 1)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> int:
|
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:
|
def in_range(self, val: int) -> bool:
|
||||||
if self.range is None:
|
if self.range is None:
|
||||||
@@ -49,7 +49,7 @@ class BoolField(DeviceField):
|
|||||||
super().__init__(name, address, 1)
|
super().__init__(name, address, 1)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> bool:
|
def parse(self, data: bytes) -> bool:
|
||||||
return struct.unpack('!H', data)[0] == 1
|
return struct.unpack("!H", data)[0] == 1
|
||||||
|
|
||||||
|
|
||||||
class EnumField(DeviceField):
|
class EnumField(DeviceField):
|
||||||
@@ -58,18 +58,20 @@ class EnumField(DeviceField):
|
|||||||
super().__init__(name, address, 1)
|
super().__init__(name, address, 1)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> Any:
|
def parse(self, data: bytes) -> Any:
|
||||||
val = struct.unpack('!H', data)[0]
|
val = struct.unpack("!H", data)[0]
|
||||||
return self.enum(val)
|
return self.enum(val)
|
||||||
|
|
||||||
|
|
||||||
class DecimalField(DeviceField):
|
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.scale = scale
|
||||||
self.range = range
|
self.range = range
|
||||||
super().__init__(name, address, 1)
|
super().__init__(name, address, 1)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> Decimal:
|
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
|
return val / 10 ** self.scale
|
||||||
|
|
||||||
def in_range(self, val: Decimal) -> bool:
|
def in_range(self, val: Decimal) -> bool:
|
||||||
@@ -85,20 +87,22 @@ class DecimalArrayField(DeviceField):
|
|||||||
super().__init__(name, address, size)
|
super().__init__(name, address, size)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> Decimal:
|
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]
|
return [Decimal(v) / 10 ** self.scale for v in values]
|
||||||
|
|
||||||
|
|
||||||
class StringField(DeviceField):
|
class StringField(DeviceField):
|
||||||
"""Fixed-width null-terminated string field"""
|
"""Fixed-width null-terminated string field"""
|
||||||
|
|
||||||
def parse(self, data: bytes) -> str:
|
def parse(self, data: bytes) -> str:
|
||||||
return data.rstrip(b'\0').decode('ascii')
|
return data.rstrip(b"\0").decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
class SwapStringField(DeviceField):
|
class SwapStringField(DeviceField):
|
||||||
"""Fixed-width null-terminated string field"""
|
"""Fixed-width null-terminated string field"""
|
||||||
|
|
||||||
def parse(self, data: bytes) -> str:
|
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):
|
class VersionField(DeviceField):
|
||||||
@@ -106,7 +110,7 @@ class VersionField(DeviceField):
|
|||||||
super().__init__(name, address, 2)
|
super().__init__(name, address, 2)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> int:
|
def parse(self, data: bytes) -> int:
|
||||||
values = struct.unpack('!2H', data)
|
values = struct.unpack("!2H", data)
|
||||||
return Decimal(values[0] + (values[1] << 16)) / 100
|
return Decimal(values[0] + (values[1] << 16)) / 100
|
||||||
|
|
||||||
|
|
||||||
@@ -115,7 +119,7 @@ class SerialNumberField(DeviceField):
|
|||||||
super().__init__(name, address, 4)
|
super().__init__(name, address, 4)
|
||||||
|
|
||||||
def parse(self, data: bytes) -> int:
|
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)
|
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]):
|
def add_enum_field(self, name: str, address: int, enum: Type[Enum]):
|
||||||
self.fields.append(EnumField(name, address, 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))
|
self.fields.append(DecimalField(name, address, scale, range))
|
||||||
|
|
||||||
def add_decimal_array_field(self, name: str, address: int, size: int, scale: int):
|
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
|
# Filter out fields not in range
|
||||||
r = range(starting_address, starting_address + data_size)
|
r = range(starting_address, starting_address + data_size)
|
||||||
fields = [f for f in self.fields
|
fields = [
|
||||||
if f.address in r and f.address + f.size - 1 in r]
|
f for f in self.fields if f.address in r and f.address + f.size - 1 in r
|
||||||
|
]
|
||||||
|
|
||||||
# Parse fields
|
# Parse fields
|
||||||
parsed = {}
|
parsed = {}
|
||||||
for f in fields:
|
for f in fields:
|
||||||
data_start = 2 * (f.address - starting_address)
|
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)
|
val = f.parse(field_data)
|
||||||
|
|
||||||
# Skip if the value is "out-of-range" - sometimes the sensors
|
# Skip if the value is "out-of-range" - sometimes the sensors
|
||||||
|
|||||||
Reference in new issue
Block a user