Compare commits

...
19 Commits
Author SHA1 Message Date
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
Patrick762 149633f9c8 Increase version number 2024-06-30 15:04:23 +02:00
Patrick762 9ab7cefda7 Merge pull request #116 from matthewpucc/main
Support AC70 and AC70P Device
2024-06-30 15:03:35 +02:00
Patrick762 52853d6d3e Merge pull request #117 from rotx/fix-multiply
Fix multiplication of Decimal with float
2024-06-30 15:03:14 +02: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
Robert Muchsel 3d3401c942 Fix multiplication of Decimal with float 2024-06-21 20:52:48 -05:00
Matthew Puccerella f50232ee89 Merge pull request #1 from matthewpucc/support_ac70_devices
Add support AC70 and AC70P devices
2024-06-20 16:12:53 -07:00
Matthew Puccerella 5922362772 Add support AC70 and AC70P devices 2024-06-20 23:11:47 +00:00
11 changed files with 111 additions and 5 deletions

No files matched your search

+3 -1
View File
@@ -17,8 +17,10 @@ After the installation, you can use this button to install the integration:
### Supported devices: ### Supported devices:
- AC60 - AC60
- AC70 (basic data)
- AC70P (untested)
- AC180 (basic data) - AC180 (basic data)
- AC180P (basic data) - AC180P (tested)
- AC200L (untested) - AC200L (untested)
- AC200M - AC200M
- AC300 (tested) - AC300 (tested)
@@ -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)
]
@@ -0,0 +1,8 @@
"""AC70 fields."""
from ..base_devices.ProtocolV2Device import ProtocolV2Device
class AC70(ProtocolV2Device):
def __init__(self, address: str, sn: str):
super().__init__(address, "AC70", sn)
@@ -0,0 +1,8 @@
"""AC70P fields."""
from ..base_devices.ProtocolV2Device import ProtocolV2Device
class AC70P(ProtocolV2Device):
def __init__(self, address: str, sn: str):
super().__init__(address, "AC70P", sn)
@@ -316,6 +316,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",
),
} }
@@ -3,6 +3,8 @@
import re import re
from ..devices.ac60 import AC60 from ..devices.ac60 import AC60
from ..devices.ac70 import AC70
from ..devices.ac70p import AC70P
from ..devices.ac180 import AC180 from ..devices.ac180 import AC180
from ..devices.ac180p import AC180P from ..devices.ac180p import AC180P
from ..devices.ac200l import AC200L from ..devices.ac200l import AC200L
@@ -17,7 +19,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|AC180|AC180P|AC200L|AC200M|AC300|AC500|EB3A|EP500|EP500P|EP600|EP760|EP800)(\d+)$" r"^(AC60|AC70|AC70P|AC180|AC180P|AC200L|AC200M|AC300|AC500|EB3A|EP500|EP500P|EP600|EP760|EP800)(\d+)$"
) )
@@ -25,6 +27,10 @@ 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] == "AC70":
return AC70(address, match[2])
if match[1] == "AC70P":
return AC70P(address, match[2])
if match[1] == "AC180": if match[1] == "AC180":
return AC180(address, match[2]) return AC180(address, match[2])
if match[1] == "AC180P": if match[1] == "AC180P":
@@ -91,7 +91,7 @@ class DecimalField(DeviceField):
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) * self.multiplier return (val / 10 ** self.scale) * Decimal(self.multiplier)
def in_range(self, val: Decimal) -> bool: def in_range(self, val: Decimal) -> bool:
if self.range is None: if self.range is None:
+4
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 = [
@@ -45,4 +48,5 @@ DIAGNOSTIC_FIELDS = [
"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.1" "version": "0.1.3"
} }
+5
View File
@@ -0,0 +1,5 @@
homeassistant
bleak_retry_connector
pyserial
bluetooth_auto_recovery
crcmod
+18
View File
@@ -4,6 +4,8 @@ import unittest
from custom_components.bluetti_bt.bluetti_bt_lib.utils.device_builder import build_device 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.ac60 import AC60
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac70 import AC70
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac70p import AC70P
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac180 import AC180 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.ac180p import AC180P
from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac200m import AC200M from custom_components.bluetti_bt.bluetti_bt_lib.devices.ac200m import AC200M
@@ -32,6 +34,22 @@ class TestDeviceBuilder(unittest.TestCase):
self.assertIsInstance(built, AC60) self.assertIsInstance(built, AC60)
self.assertEqual(built.address, bt_addr) self.assertEqual(built.address, bt_addr)
def test_build_ac70(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC7056786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC70)
self.assertEqual(built.address, bt_addr)
def test_build_ac70p(self):
bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC70P56786746478"
built = build_device(bt_addr, bt_name)
self.assertIsInstance(built, AC70P)
self.assertEqual(built.address, bt_addr)
def test_build_ac180(self): def test_build_ac180(self):
bt_addr = "aa:bb:cc:dd:ee:ff" bt_addr = "aa:bb:cc:dd:ee:ff"
bt_name = "AC18056786746478" bt_name = "AC18056786746478"