Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More AC60 fields and AC70 support #79

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions bluetti_mqtt/bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from typing import Set
from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bluetti_mqtt.core import BluettiDevice, AC200M, AC300, AC500, AC60, EP500, EP500P, EP600, EB3A
from bluetti_mqtt.core import BluettiDevice, AC200M, AC300, AC500, AC60, AC70, EP500, EP500P, EP600, EB3A
from .client import BluetoothClient
from .exc import BadConnectionError, ModbusError, ParseError
from .manager import MultiDeviceManager


DEVICE_NAME_RE = re.compile(r'^(AC200M|AC300|AC500|AC60|EP500P|EP500|EP600|EB3A)(\d+)$')
DEVICE_NAME_RE = re.compile(r'^(AC200M|AC300|AC500|AC60|AC70|EP500P|EP500|EP600|EB3A)(\d+)$')


async def scan_devices():
Expand All @@ -33,6 +33,8 @@ def build_device(address: str, name: str):
return AC500(address, match[2])
if match[1] == 'AC60':
return AC60(address, match[2])
if match[1] == 'AC70':
return AC70(address, match[2])
if match[1] == 'EP500':
return EP500(address, match[2])
if match[1] == 'EP500P':
Expand Down
1 change: 1 addition & 0 deletions bluetti_mqtt/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .devices.ac300 import AC300
from .devices.ac500 import AC500
from .devices.ac60 import AC60
from .devices.ac70 import AC70
from .devices.ep500 import EP500
from .devices.ep500p import EP500P
from .devices.ep600 import EP600
Expand Down
97 changes: 92 additions & 5 deletions bluetti_mqtt/core/devices/ac60.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,119 @@
from enum import Enum, unique
from typing import List
from ..commands import ReadHoldingRegisters
from .bluetti_device import BluettiDevice
from .struct import DeviceStruct


@unique
class LedMode(Enum):
OFF = 0
LOW = 1
HIGH = 2
SOS = 3


class ChargingMode(Enum):
STANDARD = 0
SILENT = 1
TURBO = 2


class AC60(BluettiDevice):
def __init__(self, address: str, sn: str):
self.struct = DeviceStruct()

# Core (100)
self.struct.add_uint_field('total_battery_percent', 102)
self.struct.add_uint_field('estimated_time_min', 104)
self.struct.add_swap_string_field('device_type', 110, 6)
self.struct.add_sn_field('serial_number', 116)
self.struct.add_decimal_field('power_generation', 154, 1) # Total power generated since last reset (kwh)
self.struct.add_uint_field('dc_output_power', 140)
self.struct.add_uint_field('ac_output_power', 142)
self.struct.add_uint_field('dc_input_power', 144)
self.struct.add_uint_field('ac_input_power', 146)

# Input Details (1100 - 1300)
self.struct.add_swap_string_field('device_type', 1101, 6)
self.struct.add_sn_field('serial_number', 1107)
self.struct.add_decimal_field('power_generation', 1202, 1) # Total power generated since last reset (kwh)
self.struct.add_uint_field('num_packs_connected', 1209)
self.struct.add_bool_field('charging_from_internal_dc', 1210)
self.struct.add_uint_field('internal_dc_input_power', 1212)
self.struct.add_decimal_field('internal_dc_input_voltage', 1213, 1)
self.struct.add_decimal_field('internal_dc_input_current', 1214, 1)
self.struct.add_bool_field('charging_from_pack_dc', 1218)
self.struct.add_uint_field('pack_dc_input_power', 1220)
self.struct.add_decimal_field('pack_dc_input_voltage', 1221, 1)
self.struct.add_decimal_field('pack_dc_input_current', 1222, 1)
self.struct.add_decimal_field('ac_input_frequency', 1300, 1)
self.struct.add_uint_field('internal_ac_input_power', 1313)
self.struct.add_decimal_field('ac_input_voltage', 1314, 1)
self.struct.add_decimal_field('ac_input_current', 1315, 1)

# Output Details (1400 - 1500)
self.struct.add_uint_field('total_dc_output_power', 1400)
self.struct.add_uint_field('dc_usb_output_power', 1404)
self.struct.add_uint_field('dc_12v_output_power', 1406)
self.struct.add_uint_field('dc_output_uptime_minutes', 1410)
self.struct.add_uint_field('ac_output_power', 1420)
self.struct.add_uint_field('ac_output_uptime_minutes', 1424)
self.struct.add_uint_field('ac_output_power', 1430)
self.struct.add_decimal_field('ac_output_frequency', 1500, 1)
self.struct.add_bool_field('ac_output_on', 1509)
self.struct.add_uint_field('battery_inputoutput_power', 1510)
self.struct.add_decimal_field('ac_output_voltage', 1511, 1)
self.struct.add_decimal_field('ac_output_amps', 1512, 1)

# Controls (2000)
self.struct.add_enum_field('led_mode', 2007, LedMode)
self.struct.add_bool_field('ac_output_on', 2011)
self.struct.add_bool_field('dc_output_on', 2012)
self.struct.add_bool_field('dc_eco_on', 2014)
self.struct.add_uint_field('dc_eco_hours', 2015)
self.struct.add_uint_field('dc_eco_watts', 2016)
self.struct.add_bool_field('ac_eco_on', 2017)
self.struct.add_uint_field('ac_eco_hours', 2018)
self.struct.add_uint_field('ac_eco_watts', 2019)
self.struct.add_enum_field('charging_mode', 2020, ChargingMode)
self.struct.add_bool_field('power_lifting_on', 2021)

# More Controls (2200)
self.struct.add_bool_field('grid_enhancement_mode_on', 2225)

# Battery Data Register (6000)
self.struct.add_decimal_field('total_battery_voltage', 6003, 2)

# Battery Data Register (6100)
self.struct.add_swap_string_field('battery_type', 6101, 6)
self.struct.add_sn_field('battery_serial_number', 6107)
self.struct.add_sn_field('pack_serial_number', 6107)
self.struct.add_decimal_field('pack_voltage', 6111, 2)
self.struct.add_uint_field('pack_battery_percent', 6113)
self.struct.add_version_field('bcu_version', 6175)

# Battery Data Register (6300)

super().__init__(address, 'AC60', sn)

@property
def polling_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(100, 62),
ReadHoldingRegisters(100, 50),
ReadHoldingRegisters(1100, 51),
ReadHoldingRegisters(1200, 90),
ReadHoldingRegisters(1300, 31),
ReadHoldingRegisters(1400, 48),
ReadHoldingRegisters(1500, 30),
ReadHoldingRegisters(2000, 67),
ReadHoldingRegisters(2200, 29),
ReadHoldingRegisters(6000, 31),
ReadHoldingRegisters(6100, 100),
ReadHoldingRegisters(6300, 52),
]

@property
def logging_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(100, 62),
ReadHoldingRegisters(100, 50),
ReadHoldingRegisters(1100, 51),
ReadHoldingRegisters(1200, 90),
ReadHoldingRegisters(1300, 31),
Expand All @@ -42,3 +125,7 @@ def logging_commands(self) -> List[ReadHoldingRegisters]:
ReadHoldingRegisters(6100, 100),
ReadHoldingRegisters(6300, 52),
]

@property
def writable_ranges(self) -> List[range]:
return [range(2000, 2225)]
122 changes: 122 additions & 0 deletions bluetti_mqtt/core/devices/ac70.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from enum import Enum, unique
from typing import List
from ..commands import ReadHoldingRegisters
from .bluetti_device import BluettiDevice
from .struct import DeviceStruct


@unique

class ChargingMode(Enum):
STANDARD = 0
SILENT = 1
TURBO = 2


class AC70(BluettiDevice):
def __init__(self, address: str, sn: str):
self.struct = DeviceStruct()

# Core (100)
self.struct.add_uint_field('total_battery_percent', 102)
self.struct.add_decimal_field('estimated_time_hr', 104,1)
self.struct.add_swap_string_field('device_type', 110, 6)
self.struct.add_sn_field('serial_number', 116)
self.struct.add_uint_field('dc_output_power', 140)
self.struct.add_uint_field('ac_output_power', 142)
self.struct.add_uint_field('dc_input_power', 144)
self.struct.add_uint_field('ac_input_power', 146)

# Input Details (1100 - 1300)
self.struct.add_swap_string_field('device_type', 1101, 6)
self.struct.add_sn_field('serial_number', 1107)
self.struct.add_uint_field('num_packs_connected', 1209)
self.struct.add_bool_field('charging_from_internal_dc', 1210)
self.struct.add_uint_field('internal_dc_input_power', 1212)
self.struct.add_decimal_field('internal_dc_input_voltage', 1213, 1)
self.struct.add_decimal_field('internal_dc_input_current', 1214, 1)
self.struct.add_bool_field('charging_from_pack_dc', 1218)
self.struct.add_uint_field('pack_dc_input_power', 1220)
self.struct.add_decimal_field('pack_dc_input_voltage', 1221, 1)
self.struct.add_decimal_field('pack_dc_input_current', 1222, 1)
self.struct.add_decimal_field('ac_input_frequency', 1300, 1)
self.struct.add_uint_field('internal_ac_input_power', 1313)
self.struct.add_decimal_field('ac_input_voltage', 1314, 1)
self.struct.add_decimal_field('ac_input_current', 1315, 1)

# Output Details (1400 - 1500)
self.struct.add_uint_field('total_dc_output_power', 1400)
self.struct.add_uint_field('dc_usb_output_power', 1404)
self.struct.add_uint_field('dc_12v_output_power', 1406)
self.struct.add_uint_field('dc_output_uptime_minutes', 1410)
self.struct.add_uint_field('ac_output_power', 1420)
self.struct.add_uint_field('ac_output_uptime_minutes', 1424)
self.struct.add_uint_field('ac_output_power', 1430)
self.struct.add_decimal_field('ac_output_frequency', 1500, 1)
self.struct.add_bool_field('ac_output_on', 1509)
self.struct.add_uint_field('battery_inputoutput_power', 1510)
self.struct.add_decimal_field('ac_output_voltage', 1511, 1)
self.struct.add_decimal_field('ac_output_amps', 1512, 1)

# Controls (2000)
self.struct.add_bool_field('ac_output_on', 2011)
self.struct.add_bool_field('dc_output_on', 2012)
self.struct.add_bool_field('dc_eco_on', 2014)
self.struct.add_uint_field('dc_eco_hours', 2015)
self.struct.add_uint_field('dc_eco_watts', 2016)
self.struct.add_bool_field('ac_eco_on', 2017)
self.struct.add_uint_field('ac_eco_hours', 2018)
self.struct.add_uint_field('ac_eco_watts', 2019)
self.struct.add_enum_field('charging_mode', 2020, ChargingMode)
self.struct.add_bool_field('power_lifting_on', 2021)

# More Controls (2200)
self.struct.add_bool_field('grid_enhancement_mode_on', 2225)

# Battery Data Register (6000)
self.struct.add_decimal_field('total_battery_voltage', 6003, 2)

# Battery Data Register (6100)
self.struct.add_swap_string_field('battery_type', 6101, 6)
self.struct.add_sn_field('pack_serial_number', 6107)
self.struct.add_decimal_field('pack_voltage', 6111, 2)
self.struct.add_uint_field('pack_battery_percent', 6113)
self.struct.add_version_field('bcu_version', 6175)

super().__init__(address, 'AC70', sn)

@property
def polling_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(100, 50),
ReadHoldingRegisters(1100, 51),
ReadHoldingRegisters(1200, 90),
ReadHoldingRegisters(1300, 31),
ReadHoldingRegisters(1400, 48),
ReadHoldingRegisters(1500, 30),
ReadHoldingRegisters(2000, 67),
ReadHoldingRegisters(2200, 29),
ReadHoldingRegisters(6000, 31),
ReadHoldingRegisters(6100, 100),
ReadHoldingRegisters(6300, 52),
]

@property
def logging_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(100, 50),
ReadHoldingRegisters(1100, 51),
ReadHoldingRegisters(1200, 90),
ReadHoldingRegisters(1300, 31),
ReadHoldingRegisters(1400, 48),
ReadHoldingRegisters(1500, 30),
ReadHoldingRegisters(2000, 67),
ReadHoldingRegisters(2200, 29),
ReadHoldingRegisters(6000, 31),
ReadHoldingRegisters(6100, 100),
ReadHoldingRegisters(6300, 52),
]

@property
def writable_ranges(self) -> List[range]:
return [range(2000, 2225)]
Loading