Compare commits

...
21 Commits
Author SHA1 Message Date
Patrick762 5bd98d6de3 Merge pull request #86 from Patrick762/ac200l
Ac200l support and manifest fix
2024-04-01 12:41:23 +02:00
Patrick762 2a4c3e943c updated manifest version 2024-04-01 12:37:48 +02:00
Patrick762 dcfebb0f84 added untested AC200L support based on AC200M 2024-04-01 12:37:22 +02:00
Patrick762 44dbd2b965 Merge pull request #85 from Patrick762/manifest-fix
fix dependency for bt adapters
2024-04-01 12:29:54 +02:00
Patrick762 89928e2d9c fix dependency for bt adapters 2024-04-01 12:29:01 +02:00
Patrick762 dc3aaf92e1 Merge pull request #77 from Patrick762/register-readers-fix
Bugfixes
2024-03-16 17:53:19 +01:00
Patrick762 95b1dafeb1 increase version number 2024-03-16 17:51:46 +01:00
Patrick762 15a890fb7d fix enum entities 2024-03-16 17:49:17 +01:00
Patrick762 542f244a28 removed pack polling from base device 2024-03-16 17:27:28 +01:00
Patrick762 eb701de865 fixed some polling addresses 2024-03-16 16:57:25 +01:00
Patrick762 f8ed8141ac Merge pull request #71 from Patrick762/bugfixes
Bugfixes and improvements
2024-03-12 21:03:07 +01:00
Patrick762 bf86fe172b update version number 2024-03-12 20:56:13 +01:00
Patrick762 babdd1755e fix dc switch, added some switches for eb3a 2024-03-12 20:55:09 +01:00
Patrick762 7596f8cee1 fix ac_output_on switches 2024-03-12 20:25:24 +01:00
Patrick762 98d2a0b73e Merge pull request #72 from Patrick762/debug-logging
Debug logging
2024-03-12 20:20:37 +01:00
Patrick762 c499fbcd9b fix writable_ranges 2024-03-12 20:13:20 +01:00
Patrick762 ec996d5728 added debug logging to init 2024-03-12 20:12:39 +01:00
Patrick762 ec657ad59b Merge pull request #66 from Patrick762/ac180-ac180p
AC180 AC180P
2024-02-25 16:48:03 +01:00
Patrick762 df8375016c prepare next beta release 2024-02-25 16:46:14 +01:00
Patrick762 8901c11afc added unittest 2024-02-25 16:41:47 +01:00
Patrick762 da5443172b added ac180 and ac180p 2024-02-25 16:41:29 +01:00
20 changed files with 350 additions and 84 deletions

No files matched your search

+3
View File
@@ -17,6 +17,9 @@ After the installation, you can use this button to install the integration:
### Supported devices: ### Supported devices:
- AC60 - AC60
- AC180 (basic data)
- AC180P (basic data)
- AC200L (untested)
- AC200M - AC200M
- AC300 (tested) - AC300 (tested)
- AC500 - AC500
+6
View File
@@ -33,6 +33,8 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Bluetti Powerstation from a config entry.""" """Set up Bluetti Powerstation from a config entry."""
_LOGGER.debug("Init Bluetti BT Integration")
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)
@@ -53,10 +55,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN][entry.entry_id].setdefault(DATA_POLLING_RUNNING, False) hass.data[DOMAIN][entry.entry_id].setdefault(DATA_POLLING_RUNNING, False)
# Create coordinator for polling # Create coordinator for polling
_LOGGER.debug("Creating coordinator")
coordinator = PollingCoordinator(hass, address, device_name, polling_interval, persistent_conn, polling_timeout, max_retries) coordinator = PollingCoordinator(hass, address, device_name, polling_interval, persistent_conn, polling_timeout, max_retries)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id].setdefault(DATA_COORDINATOR, coordinator) hass.data[DOMAIN][entry.entry_id].setdefault(DATA_COORDINATOR, coordinator)
_LOGGER.debug("Creating entities")
platforms: list = PLATFORMS platforms: list = PLATFORMS
if use_controls is True: if use_controls is True:
_LOGGER.warning("You are using controls with this integration at your own risk!") _LOGGER.warning("You are using controls with this integration at your own risk!")
@@ -65,6 +69,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Setup platforms # Setup platforms
await hass.config_entries.async_forward_entry_setups(entry, platforms) await hass.config_entries.async_forward_entry_setups(entry, platforms)
_LOGGER.debug("Setup done")
return True return True
@@ -29,13 +29,6 @@ class ProtocolV1Device(BluettiDevice):
# Battery # 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)
# 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)
@@ -56,20 +49,10 @@ class ProtocolV1Device(BluettiDevice):
ReadHoldingRegisters(36, 4), ReadHoldingRegisters(36, 4),
ReadHoldingRegisters(41, 1), ReadHoldingRegisters(41, 1),
ReadHoldingRegisters(43, 1), ReadHoldingRegisters(43, 1),
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(48, 2), ReadHoldingRegisters(48, 2),
ReadHoldingRegisters(3007, 2),
] ]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
return [range(3006, 3008)] return [range(3006, 3009)]
@property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
]
@@ -0,0 +1,8 @@
"""AC180 fields."""
from ..base_devices.ProtocolV2Device import ProtocolV2Device
class AC180(ProtocolV2Device):
def __init__(self, address: str, sn: str):
super().__init__(address, "AC180", sn)
@@ -0,0 +1,8 @@
"""AC180P fields."""
from ..base_devices.ProtocolV2Device import ProtocolV2Device
class AC180P(ProtocolV2Device):
def __init__(self, address: str, sn: str):
super().__init__(address, "AC180P", sn)
@@ -0,0 +1,59 @@
"""AC200L fields."""
from typing import List
from ..utils.commands import ReadHoldingRegisters
from ..field_enums import AutoSleepMode, OutputMode
from ..base_devices.ProtocolV1Device import ProtocolV1Device
class AC200L(ProtocolV1Device):
def __init__(self, address: str, sn: str):
super().__init__(address, "AC200L", sn)
# Details
self.struct.add_enum_field("ac_output_mode", 70, OutputMode)
self.struct.add_decimal_field("internal_ac_voltage", 71, 1)
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, 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_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_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)
@property
def pack_num_max(self):
return 3
@property
def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [
ReadHoldingRegisters(70, 5),
ReadHoldingRegisters(86, 3),
ReadHoldingRegisters(3060, 2),
]
@property
def writable_ranges(self) -> List[range]:
return super().writable_ranges + [range(3060, 3062)]
@property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16)
]
@@ -13,15 +13,20 @@ class AC200M(ProtocolV1Device):
# 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_decimal_field("internal_ac_voltage", 71, 1)
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, 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_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_decimal_array_field("cell_voltages", 105, 16, 2) # internal self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal
# Controls # Controls
@@ -35,14 +40,20 @@ class AC200M(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(70, 4), ReadHoldingRegisters(70, 5),
ReadHoldingRegisters(86, 3), ReadHoldingRegisters(86, 3),
ReadHoldingRegisters(3060, 2),
] ]
@property @property
def writable_ranges(self) -> List[range]: def writable_ranges(self) -> List[range]:
return super().writable_ranges + [range(3060, 3061)] return super().writable_ranges + [range(3060, 3062)]
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return super().pack_polling_commands + [ReadHoldingRegisters(105, 16)] return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16)
]
@@ -11,12 +11,12 @@ class AC300(ProtocolV1Device):
def __init__(self, address: str, sn: str): def __init__(self, address: str, sn: str):
super().__init__(address, "AC300", sn) super().__init__(address, "AC300", sn)
# Details (matches EP500 + AC500) # 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_decimal_field("internal_ac_voltage", 71, 1)
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, 2)
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)
@@ -28,10 +28,15 @@ class AC300(ProtocolV1Device):
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_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_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 (matches EP500 + AC500) # 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( self.struct.add_enum_field(
@@ -50,20 +55,29 @@ class AC300(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(70, 10), ReadHoldingRegisters(70, 11),
ReadHoldingRegisters(86, 3), ReadHoldingRegisters(86, 3),
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]:
return super().writable_ranges + [ return super().writable_ranges + [
range(3001, 3016), range(3001, 3017),
3061, range(3061),
] ]
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return super().pack_polling_commands + [ return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16), ReadHoldingRegisters(105, 16),
ReadHoldingRegisters(201, 2), ReadHoldingRegisters(201, 2),
] ]
@@ -11,12 +11,12 @@ class AC500(ProtocolV1Device):
def __init__(self, address: str, sn: str): def __init__(self, address: str, sn: str):
super().__init__(address, "AC500", sn) super().__init__(address, "AC500", sn)
# Details (matches EP500 + AC300) # 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_decimal_field("internal_ac_voltage", 71, 1)
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, 2)
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)
@@ -27,10 +27,15 @@ class AC500(ProtocolV1Device):
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 (matches EP500) # 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_decimal_array_field("cell_voltages", 105, 16, 2) # internal self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal
# Controls (matches EP500 + AC300) # 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( self.struct.add_enum_field(
@@ -49,19 +54,28 @@ class AC500(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(70, 10), ReadHoldingRegisters(70, 11),
ReadHoldingRegisters(86, 3), ReadHoldingRegisters(86, 3),
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]:
return super().writable_ranges + [ return super().writable_ranges + [
range(3001, 3016), range(3001, 3017),
3061, range(3061),
] ]
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return super().pack_polling_commands + [ return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16), ReadHoldingRegisters(105, 16),
] ]
@@ -28,12 +28,15 @@ class EB3A(ProtocolV1Device):
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(77, 1), ReadHoldingRegisters(77, 1),
ReadHoldingRegisters(86, 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]:
return super().writable_ranges + [ return super().writable_ranges + [
3034, range(3034, 3035),
3060, range(3060, 3061),
range(3063, 3066), range(3063, 3067),
] ]
@@ -11,12 +11,12 @@ class EP500(ProtocolV1Device):
def __init__(self, address: str, sn: str): def __init__(self, address: str, sn: str):
super().__init__(address, "EP500", sn) super().__init__(address, "EP500", sn)
# Details (matches AC500 + AC300) # 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_decimal_field("internal_ac_voltage", 71, 1)
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, 2)
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)
@@ -27,10 +27,15 @@ class EP500(ProtocolV1Device):
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 (matches AC500) # 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_decimal_array_field("cell_voltages", 105, 16, 2) # internal self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal
# Controls (matches AC500 + AC300) # 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( self.struct.add_enum_field(
@@ -45,19 +50,28 @@ class EP500(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(70, 10), ReadHoldingRegisters(70, 11),
ReadHoldingRegisters(86, 3), ReadHoldingRegisters(86, 3),
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]:
return super().writable_ranges + [ return super().writable_ranges + [
range(3001, 3016), range(3001, 3017),
3061, range(3061),
] ]
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return super().pack_polling_commands + [ return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16), ReadHoldingRegisters(105, 16),
] ]
@@ -11,12 +11,12 @@ class EP500P(ProtocolV1Device):
def __init__(self, address: str, sn: str): def __init__(self, address: str, sn: str):
super().__init__(address, "EP500P", sn) super().__init__(address, "EP500P", sn)
# Details (matches AC500 + AC300) # 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_decimal_field("internal_ac_voltage", 71, 1)
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, 2)
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)
@@ -27,10 +27,15 @@ class EP500P(ProtocolV1Device):
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 (matches AC500) # 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_decimal_array_field("cell_voltages", 105, 16, 2) # internal self.struct.add_decimal_array_field("cell_voltages", 105, 16, 2) # internal
# Controls (matches AC500 + AC300) # 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( self.struct.add_enum_field(
@@ -45,19 +50,28 @@ class EP500P(ProtocolV1Device):
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
ReadHoldingRegisters(70, 10), ReadHoldingRegisters(70, 11),
ReadHoldingRegisters(86, 3), ReadHoldingRegisters(86, 3),
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]:
return super().writable_ranges + [ return super().writable_ranges + [
range(3001, 3016), range(3001, 3017),
3061, range(3061),
] ]
@property @property
def pack_polling_commands(self) -> List[ReadHoldingRegisters]: def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
return super().pack_polling_commands + [ return [
ReadHoldingRegisters(91, 2),
ReadHoldingRegisters(96, 1),
ReadHoldingRegisters(98, 2),
ReadHoldingRegisters(105, 16), ReadHoldingRegisters(105, 16),
] ]
@@ -79,10 +79,6 @@ class EP600(ProtocolV2Device):
self.struct.add_version_field("safety_module_version", 6181) # internal self.struct.add_version_field("safety_module_version", 6181) # internal
self.struct.add_version_field("high_voltage_module_version", 6184) # internal self.struct.add_version_field("high_voltage_module_version", 6184) # internal
# @property
# def pack_num_max(self):
# return 16
@property @property
def polling_commands(self) -> List[ReadHoldingRegisters]: def polling_commands(self) -> List[ReadHoldingRegisters]:
return super().polling_commands + [ return super().polling_commands + [
@@ -105,10 +101,3 @@ class EP600(ProtocolV2Device):
ReadHoldingRegisters(156, 1), ReadHoldingRegisters(156, 1),
ReadHoldingRegisters(158, 1), ReadHoldingRegisters(158, 1),
] ]
# @property
# def pack_polling_commands(self) -> List[ReadHoldingRegisters]:
# return super().pack_polling_commands + [
# ReadHoldingRegisters(6101, 10),
# ReadHoldingRegisters(6175, 12),
# ]
@@ -18,7 +18,6 @@ class FieldType(Enum):
NUMERIC = auto() NUMERIC = auto()
BOOL = auto() BOOL = auto()
ENUM = auto() ENUM = auto()
BUTTON = auto()
class FieldAttributes: class FieldAttributes:
@@ -220,7 +219,7 @@ FIELD_ATTRIBUTES: dict[str, FieldAttributes] = {
"total_grid_feed": EnergyFieldAttributes("Total Grid Feed"), "total_grid_feed": EnergyFieldAttributes("Total Grid Feed"),
# Device specific controls # Device specific controls
"power_off": FieldAttributes( "power_off": FieldAttributes(
type=FieldType.BUTTON, type=FieldType.BOOL,
setter=True, setter=True,
name="Power Off", name="Power Off",
), ),
@@ -294,7 +293,7 @@ FIELD_ATTRIBUTES: dict[str, FieldAttributes] = {
), ),
"power_lifting_on": FieldAttributes( "power_lifting_on": FieldAttributes(
type=FieldType.BOOL, type=FieldType.BOOL,
setter=False, # Disabled for safety reasons setter=True,
name="Power Lifting", name="Power Lifting",
), ),
} }
@@ -3,6 +3,9 @@
import re import re
from ..devices.ac60 import AC60 from ..devices.ac60 import AC60
from ..devices.ac180 import AC180
from ..devices.ac180p import AC180P
from ..devices.ac200l import AC200L
from ..devices.ac200m import AC200M from ..devices.ac200m import AC200M
from ..devices.ac300 import AC300 from ..devices.ac300 import AC300
from ..devices.ac500 import AC500 from ..devices.ac500 import AC500
@@ -14,7 +17,7 @@ from ..devices.ep760 import EP760
from ..devices.ep800 import EP800 from ..devices.ep800 import EP800
DEVICE_NAME_RE = re.compile( DEVICE_NAME_RE = re.compile(
r"^(AC60|AC200M|AC300|AC500|EB3A|EP500|EP500P|EP600|EP760|EP800)(\d+)$" r"^(AC60|AC180|AC180P|AC200L|AC200M|AC300|AC500|EB3A|EP500|EP500P|EP600|EP760|EP800)(\d+)$"
) )
@@ -22,6 +25,12 @@ def build_device(address: str, name: str):
match = DEVICE_NAME_RE.match(name) match = DEVICE_NAME_RE.match(name)
if match[1] == "AC60": if match[1] == "AC60":
return AC60(address, match[2]) return AC60(address, match[2])
if match[1] == "AC180":
return AC180(address, match[2])
if match[1] == "AC180P":
return AC180P(address, match[2])
if match[1] == "AC200L":
return AC200L(address, match[2])
if match[1] == "AC200M": if match[1] == "AC200M":
return AC200M(address, match[2]) return AC200M(address, match[2])
if match[1] == "AC300": if match[1] == "AC300":
+5 -2
View File
@@ -26,8 +26,11 @@ SUPPORTED_MODELS = [
] ]
CONTROL_FIELDS = [ CONTROL_FIELDS = [
"ac_output_on", "ac_output_on_switch",
"dc_output_on", "dc_output_on_switch",
"power_off",
"eco_on",
"power_lifting_on",
] ]
DIAGNOSTIC_FIELDS = [ DIAGNOSTIC_FIELDS = [
+5 -3
View File
@@ -2,10 +2,12 @@
"domain": "bluetti_bt", "domain": "bluetti_bt",
"name": "Bluetti BT", "name": "Bluetti BT",
"bluetooth": [ "bluetooth": [
{ "local_name": "AC60*" },
{ "local_name": "AC180*" },
{ "local_name": "AC200L*" },
{ "local_name": "AC200M*" }, { "local_name": "AC200M*" },
{ "local_name": "AC300*" }, { "local_name": "AC300*" },
{ "local_name": "AC500*" }, { "local_name": "AC500*" },
{ "local_name": "AC60*" },
{ "local_name": "EB3A*" }, { "local_name": "EB3A*" },
{ "local_name": "EP500*" }, { "local_name": "EP500*" },
{ "local_name": "EP600*" }, { "local_name": "EP600*" },
@@ -15,11 +17,11 @@
], ],
"codeowners": ["@Patrick762"], "codeowners": ["@Patrick762"],
"config_flow": true, "config_flow": true,
"dependencies": ["bluetooth"], "dependencies": ["bluetooth_adapters"],
"documentation": "https://github.com/Patrick762/hassio-bluetti-bt", "documentation": "https://github.com/Patrick762/hassio-bluetti-bt",
"integration_type": "device", "integration_type": "device",
"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.0.18" "version": "0.0.22"
} }
+1 -2
View File
@@ -175,10 +175,9 @@ class BluettiSensor(CoordinatorEntity, SensorEntity):
if ( if (
self._options is not None self._options is not None
and isinstance(response_data, Enum) and isinstance(response_data, Enum)
and response_data.value < len(self._options)
): ):
# Enum # Enum
self._attr_native_value = self._options[response_data.value] self._attr_native_value = response_data.name
else: else:
# Numeric # Numeric
self._attr_native_value = response_data self._attr_native_value = response_data
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
# Execute unittests of python library
python3 -m unittest discover -s tests -p "*_test.py"
+124
View File
@@ -0,0 +1,124 @@
"""Unittest for device builder."""
import unittest
from custom_components.bluetti_bt.bluetti_bt_lib.utils.device_builder import build_device
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac60 import AC60
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac180 import AC180
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac180p import AC180P
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac200m import AC200M
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac300 import AC300
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac500 import AC500
from custom_components.bluetti_bt.bluetti_bt_lib.devices.eb3a import EB3A
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ep500 import EP500
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ep500p import EP500P
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ep600 import EP600
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ep760 import EP760
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ep800 import EP800
class TestDeviceBuilder(unittest.TestCase):
def test_build_Unknow(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "PBOX56786746478"
with self.assertRaises(TypeError):
build_device(bt_addr, bt_name)
def test_build_ac60(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC6056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC60)
self.assertEqual(built.address, bt_addr)
def test_build_ac180(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC18056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC180)
self.assertEqual(built.address, bt_addr)
def test_build_ac180p(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC180P56786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC180P)
self.assertEqual(built.address, bt_addr)
def test_build_ac200m(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC200M56786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC200M)
self.assertEqual(built.address, bt_addr)
def test_build_ac300(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC30056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC300)
self.assertEqual(built.address, bt_addr)
def test_build_ac500(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC50056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC500)
self.assertEqual(built.address, bt_addr)
def test_build_eb3a(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EB3A56786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EB3A)
self.assertEqual(built.address, bt_addr)
def test_build_ep500(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EP50056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EP500)
self.assertEqual(built.address, bt_addr)
def test_build_ep500p(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EP500P56786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EP500P)
self.assertEqual(built.address, bt_addr)
def test_build_EP600(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EP60056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EP600)
self.assertEqual(built.address, bt_addr)
def test_build_EP760(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EP76056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EP760)
self.assertEqual(built.address, bt_addr)
def test_build_EP800(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "EP80056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, EP800)
self.assertEqual(built.address, bt_addr)
if __name__ == '__main__':
unittest.main()