Compare commits

...
30 Commits
Author SHA1 Message Date
Patrick762 edf5e87720 Merge pull request #128 from Patrick762/version-update
update integration manifest version
2024-08-04 12:13:29 +02:00
Patrick762 742964b78b update integration manifest version 2024-08-04 12:12:55 +02:00
Patrick762 21e89e738d Merge pull request #127 from Kathou99/main
Update config_flow.py
2024-08-04 12:08:16 +02:00
Patrick762 52fd1932fa Merge pull request #118 from AChep/main
Various imprvements for AC300 & AC500
2024-08-04 12:07:43 +02:00
KathouQC(GirlsPowers) 3e54283faf Update config_flow.py
fix this bug: OptionsFlowHandler.async_step_init: Config entry f3a445b566f0876e0d79f02059b7cb2a has not been changed
2024-08-03 18:15:49 -04:00
Artem Chepurnyi 9be8aa47ac Add dsp/arm_version to diagnostic fields 2024-08-03 21:36:27 +03:00
Artem ChepurnyiandPatrick762 8967f3379c Fix a typo in the Polling commands text
Co-authored-by: Patrick762 <[email protected]>
2024-08-03 21:32:28 +03:00
Patrick762 6f9afedfdb Merge pull request #119 from kvanonavk/main
AC180P Advanced Integration
2024-07-19 16:17:16 +02:00
Patrick762 fe23dd00cd Merge pull request #122 from Patrick762/ac70-fix
Added ac70 auto detection
2024-07-19 16:13:48 +02:00
Patrick762 566761dba7 update version 2024-07-19 16:13:19 +02:00
Patrick762 33cb6e6fbd added ac70 auto detection 2024-07-19 15:02:57 +02:00
kvanonavk 5823136280 Merge branch 'Patrick762:main' into main 2024-07-05 14:44:12 +03:00
kvanonavk 7b6f845edb fixes 2024-07-05 14:42:56 +03:00
kvanonavk 1fbcf83b1b simple set charging mode (no turbo) 2024-07-01 18:16:58 +03:00
Artem Chepurnyi da2017a24b chore: Mention that AC500 is tested 2024-06-30 17:14:30 +03:00
Artem Chepurnyi 83f0db267c Merge branch 'Patrick762:main' into main 2024-06-30 17:12:50 +03:00
kvanonavk c96f551024 fixes 2024-06-30 14:11:31 +03:00
kvanonavk 75209ee1aa readme fix 2 2024-06-30 13:59:45 +03:00
kvanonavk 7396616113 readme fix 2024-06-30 13:56:58 +03:00
kvanonavk a7f7ff1843 fixes 2024-06-30 13:55:10 +03:00
kvanonavk cc755b3003 test + fix 2024-06-30 13:25:39 +03:00
kvanonavk e5f690ef3b AC180P more data and functionality 2024-06-30 12:24:52 +03:00
Artem Chepurnyi fa9bcf63ca fix: Actually retry connecting to a device 2024-06-29 17:04:43 +03:00
Artem Chepurnyi 211f1971c5 chore: Remove .DS_Store from the repo 2024-06-29 16:48:09 +03:00
Artem Chepurnyi a1e9c180b3 improvement: Automatically build polling commands basing on the fields 2024-06-29 16:45:59 +03:00
Artem Chepurnyi 0e5c1a0b44 fix: Increase polling interval and timeout #109 2024-06-29 16:45:24 +03:00
Artem Chepurnyi 0f33a31ee5 fix: Correctly read battery packs' values - wait before setting the target pack number #109 2024-06-29 16:44:44 +03:00
Artem Chepurnyi 23551ae829 improvement: Add ARM and DSP versions 2024-06-29 16:42:32 +03:00
Artem Chepurnyi a54b36a90b fix: Initialize sensor. _unavailable_counter with a 5 2024-06-29 16:42:04 +03:00
Artem Chepurnyi f85661345c fix(AC500): Change max pack number to 6 2024-06-29 13:58:22 +03:00
20 changed files with 158 additions and 129 deletions

No files matched your search

+1
View File
@@ -1,2 +1,3 @@
*/__pycache__/ */__pycache__/
*.pyc *.pyc
.DS_Store
+2 -2
View File
@@ -20,11 +20,11 @@ After the installation, you can use this button to install the integration:
- AC70 (basic data) - AC70 (basic data)
- AC70P (untested) - AC70P (untested)
- AC180 (basic data) - AC180 (basic data)
- AC180P (basic data) - AC180P (tested)
- AC200L (untested) - AC200L (untested)
- AC200M - AC200M
- AC300 (tested) - AC300 (tested)
- AC500 - AC500 (tested)
- EB3A - EB3A
- EP500 - EP500
- EP500P - EP500P
+2 -2
View File
@@ -38,9 +38,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
address = entry.data.get(CONF_ADDRESS) address = entry.data.get(CONF_ADDRESS)
device_name = entry.data.get(CONF_NAME) device_name = entry.data.get(CONF_NAME)
use_controls = entry.data.get(CONF_USE_CONTROLS) use_controls = entry.data.get(CONF_USE_CONTROLS)
polling_interval = entry.data.get(CONF_POLLING_INTERVAL, 20) polling_interval = entry.data.get(CONF_POLLING_INTERVAL, 60)
persistent_conn = entry.data.get(CONF_PERSISTENT_CONN, False) persistent_conn = entry.data.get(CONF_PERSISTENT_CONN, False)
polling_timeout = entry.data.get(CONF_POLLING_TIMEOUT, 45) polling_timeout = entry.data.get(CONF_POLLING_TIMEOUT, 120)
max_retries = entry.data.get(CONF_MAX_RETRIES, 5) max_retries = entry.data.get(CONF_MAX_RETRIES, 5)
if address is None: if address is None:
@@ -86,6 +86,7 @@ class BluettiBinarySensor(CoordinatorEntity, BinarySensorEntity):
e_name = f"{device_info.get('name')} {name}" e_name = f"{device_info.get('name')} {name}"
self._address = address self._address = address
self._response_key = response_key self._response_key = response_key
self._unavailable_counter = 5
self._attr_device_info = device_info self._attr_device_info = device_info
self._attr_name = name self._attr_name = name
@@ -14,6 +14,8 @@ class ProtocolV1Device(BluettiDevice):
# 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)
self.struct.add_version_field('arm_version', 23)
self.struct.add_version_field('dsp_version', 25)
# Power IO # Power IO
self.struct.add_uint_field("dc_input_power", 36) self.struct.add_uint_field("dc_input_power", 36)
@@ -53,6 +53,8 @@ class DeviceReader:
if filter_registers is not None: if filter_registers is not None:
polling_commands = filter_registers polling_commands = filter_registers
pack_commands = [] pack_commands = []
_LOGGER.info("Polling commands: " + ",".join([f"{c.starting_address}-{c.starting_address + c.quantity - 1}" for c in polling_commands]))
_LOGGER.info("Pack comands: " + ",".join([f"{c.starting_address}-{c.starting_address + c.quantity - 1}" for c in pack_commands]))
parsed_data: dict = {} parsed_data: dict = {}
@@ -66,8 +68,8 @@ class DeviceReader:
await self.client.connect() await self.client.connect()
break break
except Exception as e: except Exception as e:
if attempt == self.max_retries or attempt == 1: if attempt == self.max_retries:
raise # pass exception on max_retries attempt raise e # pass exception on max_retries attempt
else: else:
_LOGGER.warning( _LOGGER.warning(
f"Connect unsucessful (attempt {attempt}): {e}. Retrying..." f"Connect unsucessful (attempt {attempt}): {e}. Retrying..."
@@ -117,6 +119,11 @@ class DeviceReader:
_LOGGER.warning("Pack polling failed (pack_num %i doesn't match expected %i)", set_pack, pack) _LOGGER.warning("Pack polling failed (pack_num %i doesn't match expected %i)", set_pack, pack)
continue continue
if self.bluetti_device.pack_num_max > 1:
# We need to wait after switching packs
# for the data to be available
await asyncio.sleep(5)
for command in pack_commands: for command in pack_commands:
# Request & parse result for each pack # Request & parse result for each pack
try: try:
@@ -136,8 +143,8 @@ class DeviceReader:
except ParseError: except ParseError:
_LOGGER.warning("Got a parse exception...") _LOGGER.warning("Got a parse exception...")
except TimeoutError: except TimeoutError as err:
_LOGGER.warning("Polling timed out. Trying again later") _LOGGER.error(f"Polling timed out ({self.polling_timeout}s). Trying again later", exc_info=err)
return None return None
except BleakError as err: except BleakError as err:
_LOGGER.error("Bleak error: %s", err) _LOGGER.error("Bleak error: %s", err)
@@ -1,8 +1,52 @@
"""AC180P fields.""" """AC180P fields."""
from ..base_devices.ProtocolV2Device import ProtocolV2Device from typing import List
from ..field_enums import ChargingMode
from ..utils.commands import ReadHoldingRegisters
from ..base_devices.ProtocolV2Device import ProtocolV2Device
class AC180P(ProtocolV2Device): class AC180P(ProtocolV2Device):
def __init__(self, address: str, sn: str): def __init__(self, address: str, sn: str):
super().__init__(address, "AC180P", sn) super().__init__(address, "AC180P", sn)
# Power IO
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_decimal_field('ac_input_voltage', 1314, 1)
# Controls (2000)
self.struct.add_bool_field('ac_output_on_switch', 2011)
self.struct.add_bool_field('dc_output_on_switch', 2012)
self.struct.add_bool_field('silent_charging_on', 2020) # to be able to choose between normal and silent charging modes
self.struct.add_enum_field('charging_mode', 2020, ChargingMode) # can't be set from HA, custom switch needed
self.struct.add_bool_field('power_lifting_on', 2021)
# Controls (2200)
self.struct.add_bool_field('grid_enhancement_mode_on', 2225)
@property
def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [
ReadHoldingRegisters(140, 1),
ReadHoldingRegisters(142, 1),
ReadHoldingRegisters(144, 1),
ReadHoldingRegisters(146, 1),
ReadHoldingRegisters(1314, 1),
ReadHoldingRegisters(2011, 1),
ReadHoldingRegisters(2012, 1),
ReadHoldingRegisters(2020, 1),
ReadHoldingRegisters(2021, 1),
ReadHoldingRegisters(2225, 1),
]
@property
def writable_ranges(self) -> List[range]:
return super().writable_ranges + [
range(2000, 2022),
range(2200, 2226)
]
@@ -28,6 +28,7 @@ class AC200L(ProtocolV1Device):
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)
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)
# Controls # Controls
self.struct.add_bool_field("power_off", 3060) self.struct.add_bool_field("power_off", 3060)
@@ -39,12 +40,9 @@ class AC200L(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 5), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3060, 2),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -53,8 +51,6 @@ class AC200L(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1), ReadHoldingRegisters(201, 2),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16)
] ]
@@ -28,6 +28,7 @@ class AC200M(ProtocolV1Device):
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)
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)
# Controls # Controls
self.struct.add_bool_field("power_off", 3060) self.struct.add_bool_field("power_off", 3060)
@@ -39,12 +40,9 @@ class AC200M(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 5), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3060, 2),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -53,8 +51,6 @@ class AC200M(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1), ReadHoldingRegisters(201, 2),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16)
] ]
@@ -54,17 +54,9 @@ class AC300(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 11), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3001, 1),
ReadHoldingRegisters(3004, 2),
ReadHoldingRegisters(3011, 1),
ReadHoldingRegisters(3013, 1),
ReadHoldingRegisters(3015, 2),
ReadHoldingRegisters(3061, 1),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -76,9 +68,6 @@ class AC300(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16),
ReadHoldingRegisters(201, 2), ReadHoldingRegisters(201, 2),
] ]
@@ -34,6 +34,7 @@ class AC500(ProtocolV1Device):
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)
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)
# Controls # Controls
self.struct.add_enum_field("ups_mode", 3001, UpsMode) self.struct.add_enum_field("ups_mode", 3001, UpsMode)
@@ -49,21 +50,13 @@ class AC500(ProtocolV1Device):
@property @property
def pack_num_max(self): def pack_num_max(self):
return 4 return 6
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 11), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3001, 1),
ReadHoldingRegisters(3004, 2),
ReadHoldingRegisters(3011, 1),
ReadHoldingRegisters(3013, 1),
ReadHoldingRegisters(3015, 2),
ReadHoldingRegisters(3061, 1),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -75,8 +68,6 @@ class AC500(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1), ReadHoldingRegisters(201, 2),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16),
] ]
@@ -25,13 +25,7 @@ class EB3A(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers()
ReadHoldingRegisters(77, 1),
ReadHoldingRegisters(86, 1),
ReadHoldingRegisters(3034, 1),
ReadHoldingRegisters(3060, 1),
ReadHoldingRegisters(3063, 5),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -34,6 +34,7 @@ class EP500(ProtocolV1Device):
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)
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)
# Controls # Controls
self.struct.add_enum_field("ups_mode", 3001, UpsMode) self.struct.add_enum_field("ups_mode", 3001, UpsMode)
@@ -49,17 +50,9 @@ class EP500(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 11), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3001, 1),
ReadHoldingRegisters(3004, 2),
ReadHoldingRegisters(3011, 1),
ReadHoldingRegisters(3013, 1),
ReadHoldingRegisters(3015, 2),
ReadHoldingRegisters(3061, 1),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -71,8 +64,6 @@ class EP500(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1), ReadHoldingRegisters(201, 2),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16),
] ]
@@ -34,6 +34,7 @@ class EP500P(ProtocolV1Device):
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)
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)
# Controls # Controls
self.struct.add_enum_field("ups_mode", 3001, UpsMode) self.struct.add_enum_field("ups_mode", 3001, UpsMode)
@@ -49,17 +50,9 @@ class EP500P(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers(
ReadHoldingRegisters(70, 11), filter=lambda address: address <= 90 or address >= 256 # pack_polling_commands
ReadHoldingRegisters(86, 3), )
ReadHoldingRegisters(92, 1),
ReadHoldingRegisters(3001, 1),
ReadHoldingRegisters(3004, 2),
ReadHoldingRegisters(3011, 1),
ReadHoldingRegisters(3013, 1),
ReadHoldingRegisters(3015, 2),
ReadHoldingRegisters(3061, 1),
]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
@@ -71,8 +64,6 @@ class EP500P(ProtocolV1Device):
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [ return [
ReadHoldingRegisters(91, 2), ReadHoldingRegisters(91, 37),
ReadHoldingRegisters(96, 1), ReadHoldingRegisters(201, 2),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16),
] ]
@@ -97,28 +97,4 @@ class EP600(ProtocolV2Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return self.struct.get_read_holding_registers()
ReadHoldingRegisters(2022, 2),
ReadHoldingRegisters(2213, 5),
ReadHoldingRegisters(1212, 3),
ReadHoldingRegisters(1220, 3),
ReadHoldingRegisters(1228, 3),
ReadHoldingRegisters(1236, 3),
ReadHoldingRegisters(1244, 3),
ReadHoldingRegisters(1300, 1),
ReadHoldingRegisters(1313, 3),
ReadHoldingRegisters(1319, 3),
ReadHoldingRegisters(1325, 3),
ReadHoldingRegisters(1500, 1),
ReadHoldingRegisters(1510, 3),
ReadHoldingRegisters(1517, 3),
ReadHoldingRegisters(1524, 3),
ReadHoldingRegisters(152, 1),
ReadHoldingRegisters(156, 1),
ReadHoldingRegisters(158, 1),
# KM - Consumption
ReadHoldingRegisters(1430, 15),
# KM - Totals
ReadHoldingRegisters(142, 5),
]
@@ -125,6 +125,14 @@ FIELD_ATTRIBUTES: dict[str, FieldAttributes] = {
"ac_input_power": PowerFieldAttributes("AC Input Power"), "ac_input_power": PowerFieldAttributes("AC Input Power"),
"ac_output_power": PowerFieldAttributes("AC Output Power"), "ac_output_power": PowerFieldAttributes("AC Output Power"),
"dc_output_power": PowerFieldAttributes("DC Output Power"), "dc_output_power": PowerFieldAttributes("DC Output Power"),
"dsp_version": FieldAttributes(
type=FieldType.NUMERIC,
name=f"DSP Version",
),
"arm_version": FieldAttributes(
type=FieldType.NUMERIC,
name=f"ARM Version",
),
"power_generation": EnergyFieldAttributes("Total Power Generation"), "power_generation": EnergyFieldAttributes("Total Power Generation"),
"total_battery_percent": FieldAttributes( "total_battery_percent": FieldAttributes(
type=FieldType.NUMERIC, type=FieldType.NUMERIC,
@@ -316,6 +324,16 @@ FIELD_ATTRIBUTES: dict[str, FieldAttributes] = {
setter=True, setter=True,
name="Power Lifting", name="Power Lifting",
), ),
"grid_enhancement_mode_on": FieldAttributes(
type=FieldType.BOOL,
setter=True,
name="Grid Enhancement Mode",
),
"silent_charging_on": FieldAttributes(
type=FieldType.BOOL,
setter=True,
name="Silent Charging",
),
} }
@@ -4,6 +4,7 @@
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from .commands import ReadHoldingRegisters
import struct import struct
from typing import Any, List, Optional, Tuple, Type from typing import Any, List, Optional, Tuple, Type
@@ -180,6 +181,35 @@ class DeviceStruct:
def add_sn_field(self, name: str, address: int): def add_sn_field(self, name: str, address: int):
self.fields.append(SerialNumberField(name, address)) self.fields.append(SerialNumberField(name, address))
def get_read_holding_registers(self, tolerance=20, filter=lambda address: True):
address_set = set()
for field in self.fields:
for address in range(field.address, field.address + field.size):
address_set.add(address)
# Filter out addresses that we specificly
# specified to exclude.
for address in set(address_set):
if not filter(address):
address_set.remove(address)
# Fast path: no addresses -> no commands.
if not address_set:
return []
commands = []
address_list = sorted(address_set)
start = address_list[0]
last = address_list[0]
for address in address_list[1:]:
# Create a new group if there's a gap
# in the addresses.
if address >= last + tolerance:
commands.append(ReadHoldingRegisters(start, last - start + 1))
start = address
last = address
commands.append(ReadHoldingRegisters(start, last - start + 1))
return commands
def parse(self, starting_address: int, data: bytes) -> dict: def parse(self, starting_address: int, data: bytes) -> dict:
# Offsets and size are counted in 2 byte chunks, so for the range we # Offsets and size are counted in 2 byte chunks, so for the range we
# need to divide the byte size by 2 # need to divide the byte size by 2
+1 -6
View File
@@ -150,7 +150,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
if user_input[CONF_MAX_RETRIES] < 1: if user_input[CONF_MAX_RETRIES] < 1:
return self.async_abort(reason="invalid_retries") return self.async_abort(reason="invalid_retries")
changed = self.hass.config_entries.async_update_entry( self.hass.config_entries.async_update_entry(
self.config_entry, self.config_entry,
data={ data={
**self.config_entry.data, **self.config_entry.data,
@@ -163,11 +163,6 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
}, },
}, },
) )
if changed is False:
_LOGGER.error(
"Method OptionsFlowHandler.async_step_init: Config entry %s has not been changed",
self.config_entry.entry_id,
)
return self.async_create_entry( return self.async_create_entry(
title="", title="",
data={ data={
+6
View File
@@ -23,6 +23,7 @@ SUPPORTED_MODELS = [
"EP500P", "EP500P",
"EP600", "EP600",
"EP760", "EP760",
"AC180P",
] ]
CONTROL_FIELDS = [ CONTROL_FIELDS = [
@@ -31,6 +32,8 @@ CONTROL_FIELDS = [
"power_off", "power_off",
"eco_on", "eco_on",
"power_lifting_on", "power_lifting_on",
"grid_enhancement_mode_on",
"silent_charging_on",
] ]
DIAGNOSTIC_FIELDS = [ DIAGNOSTIC_FIELDS = [
@@ -39,10 +42,13 @@ DIAGNOSTIC_FIELDS = [
"max_ac_output_power", "max_ac_output_power",
"max_ac_output_current", "max_ac_output_current",
"bcu_version", "bcu_version",
"dsp_version",
"arm_version",
"safety_module_version", "safety_module_version",
"high_voltage_module_version", "high_voltage_module_version",
"power_generation", "power_generation",
"total_ac_consumption", "total_ac_consumption",
"total_grid_consumption", "total_grid_consumption",
"total_grid_feed", "total_grid_feed",
"ac_input_voltage",
] ]
+2 -1
View File
@@ -3,6 +3,7 @@
"name": "Bluetti BT", "name": "Bluetti BT",
"bluetooth": [ "bluetooth": [
{ "local_name": "AC60*" }, { "local_name": "AC60*" },
{ "local_name": "AC70*" },
{ "local_name": "AC180*" }, { "local_name": "AC180*" },
{ "local_name": "AC200L*" }, { "local_name": "AC200L*" },
{ "local_name": "AC200M*" }, { "local_name": "AC200M*" },
@@ -23,5 +24,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"issue_tracker": "https://github.com/Patrick762/hassio-bluetti-bt/issues", "issue_tracker": "https://github.com/Patrick762/hassio-bluetti-bt/issues",
"requirements": [], "requirements": [],
"version": "0.1.2" "version": "0.1.4"
} }