Skip to content

Commit 32f8149

Browse files
committed
Refactor SolisCloudControl to use inverter serial number for unique identification and API calls
1 parent 66576f3 commit 32f8149

File tree

6 files changed

+50
-223
lines changed

6 files changed

+50
-223
lines changed
Lines changed: 21 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,224 +1,48 @@
1+
from homeassistant.config_entries import ConfigEntry
12
from homeassistant.const import CONF_API_KEY, CONF_TOKEN, Platform
23
from homeassistant.core import HomeAssistant
34
from homeassistant.helpers import aiohttp_client
5+
from homeassistant.helpers import device_registry as dr
46

5-
from custom_components.solis_cloud_control.coordinator import SolisCloudControlConfigEntry, SolisCloudControlCoordinator
7+
from custom_components.solis_cloud_control.coordinator import SolisCloudControlCoordinator
68

79
from .api import SolisCloudControlApiClient
810
from .const import CONF_INVERTER_SN
911

1012
PLATFORMS: list[Platform] = [Platform.SELECT]
1113

1214

13-
async def async_setup_entry(hass: HomeAssistant, entry: SolisCloudControlConfigEntry) -> bool:
15+
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
1416
api_key = entry.data[CONF_API_KEY]
1517
api_token = entry.data[CONF_TOKEN]
1618
inverter_sn = entry.data[CONF_INVERTER_SN]
1719

1820
session = aiohttp_client.async_get_clientsession(hass)
19-
api_client = SolisCloudControlApiClient(api_key, api_token, inverter_sn, session)
20-
21-
coordinator = SolisCloudControlCoordinator(
22-
hass,
23-
entry,
24-
api_client,
25-
)
21+
api_client = SolisCloudControlApiClient(api_key, api_token, session)
2622

23+
coordinator = SolisCloudControlCoordinator(hass, entry, api_client)
2724
entry.runtime_data = coordinator
2825

26+
device_registry = dr.async_get(hass)
27+
device_registry.async_get_or_create(
28+
config_entry_id=entry.entry_id,
29+
identifiers={(entry.domain, inverter_sn)},
30+
manufacturer="Solis",
31+
name=f"Inverter {inverter_sn}",
32+
)
33+
2934
await coordinator.async_config_entry_first_refresh()
3035

3136
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
32-
33-
# hass.async_create_task(
34-
# async_load_platform(
35-
# hass,
36-
# "select",
37-
# DOMAIN,
38-
# {"client": api_client, "inverter_sn": inverter_sn},
39-
# entry,
40-
# )
41-
# )
42-
43-
# async def async_service_read(call: ServiceCall) -> ServiceResponse:
44-
# cid = call.data.get("cid")
45-
# try:
46-
# result = await api_client.read(cid)
47-
# _LOGGER.info("Read state for '%s': '%s'", cid, result)
48-
# return {"value": result}
49-
# except SolisCloudControlApiError as err:
50-
# raise HomeAssistantError(str(err)) from err
51-
52-
# async def async_service_control(call: ServiceCall) -> None:
53-
# cid = call.data.get("cid")
54-
# value = call.data.get("value")
55-
# try:
56-
# _LOGGER.info("Control '%s' state with value: '%s'", cid, value)
57-
# await api_client.control(cid, value)
58-
# except SolisCloudControlApiError as err:
59-
# raise HomeAssistantError(str(err)) from err
60-
61-
# async def async_service_set_storage_mode(call: ServiceCall) -> ServiceResponse:
62-
# storage_mode = call.data.get("storage_mode", "Self Use")
63-
# battery_reserve = call.data.get("battery_reserve", "ON")
64-
# allow_grid_charging = call.data.get("allow_grid_charging", "OFF")
65-
66-
# value_int = 0
67-
68-
# if storage_mode == "Self Use":
69-
# value_int |= 1 << STORAGE_MODE_BIT_SELF_USE
70-
# elif storage_mode == "Feed In Priority":
71-
# value_int |= 1 << STORAGE_MODE_BIT_FEED_IN_PRIORITY
72-
73-
# if battery_reserve == "ON":
74-
# value_int |= 1 << STORAGE_MODE_BIT_BACKUP_MODE
75-
76-
# if allow_grid_charging == "ON":
77-
# value_int |= 1 << STORAGE_MODE_BIT_GRID_CHARGING
78-
79-
# value = str(value_int)
80-
81-
# try:
82-
# _LOGGER.info(
83-
# "Setting storage mode: mode='%s', battery_reserve='%s', allow_grid_charging='%s', value='%s'",
84-
# storage_mode,
85-
# battery_reserve,
86-
# allow_grid_charging,
87-
# value,
88-
# )
89-
# await api_client.control(STORAGE_MODE_CID, value)
90-
# return {"value": value}
91-
# except SolisCloudControlApiError as err:
92-
# raise HomeAssistantError(str(err)) from err
93-
94-
# async def async_service_set_charge_slot1(call: ServiceCall) -> ServiceResponse:
95-
# from_time = call.data.get("from_time")
96-
# to_time = call.data.get("to_time")
97-
# current = call.data.get("current")
98-
# soc = call.data.get("soc")
99-
100-
# from_time_formatted = from_time.strftime("%H:%M")
101-
# to_time_formatted = to_time.strftime("%H:%M")
102-
103-
# time_range = f"{from_time_formatted}-{to_time_formatted}"
104-
# response = {"time_range": time_range}
105-
106-
# try:
107-
# _LOGGER.info("Setting charge slot 1 time range: '%s'", time_range)
108-
# await api_client.control(CHARGE_SLOT1_TIME_CID, time_range)
109-
110-
# if current is not None:
111-
# current_str = str(current)
112-
# _LOGGER.info("Setting charge slot 1 current: '%s'", current_str)
113-
# await api_client.control(CHARGE_SLOT1_CURRENT_CID, current_str)
114-
# response["current"] = current
115-
116-
# if soc is not None:
117-
# soc_str = str(soc)
118-
# _LOGGER.info("Setting charge slot 1 SOC: '%s'", soc_str)
119-
# await api_client.control(CHARGE_SLOT1_SOC_CID, soc_str)
120-
# response["soc"] = soc
121-
122-
# return response
123-
# except SolisCloudControlApiError as err:
124-
# raise HomeAssistantError(str(err)) from err
125-
126-
# async def async_service_set_discharge_slot1(call: ServiceCall) -> ServiceResponse:
127-
# from_time = call.data.get("from_time")
128-
# to_time = call.data.get("to_time")
129-
# current = call.data.get("current")
130-
# soc = call.data.get("soc")
131-
132-
# from_time_formatted = from_time.strftime("%H:%M")
133-
# to_time_formatted = to_time.strftime("%H:%M")
134-
135-
# time_range = f"{from_time_formatted}-{to_time_formatted}"
136-
# response = {"time_range": time_range}
137-
138-
# try:
139-
# _LOGGER.info("Setting discharge slot 1 time range: '%s'", time_range)
140-
# await api_client.control(DISCHARGE_SLOT1_TIME_CID, time_range)
141-
142-
# if current is not None:
143-
# current_str = str(current)
144-
# _LOGGER.info("Setting discharge slot 1 current: '%s'", current_str)
145-
# await api_client.control(DISCHARGE_SLOT1_CURRENT_CID, current_str)
146-
# response["current"] = current
147-
148-
# if soc is not None:
149-
# soc_str = str(soc)
150-
# _LOGGER.info("Setting discharge slot 1 SOC: '%s'", soc_str)
151-
# await api_client.control(DISCHARGE_SLOT1_SOC_CID, soc_str)
152-
# response["soc"] = soc
153-
154-
# return response
155-
# except SolisCloudControlApiError as err:
156-
# raise HomeAssistantError(str(err)) from err
157-
158-
# async def async_service_disable_charge_slot1(call: ServiceCall) -> None: # noqa: ARG001
159-
# try:
160-
# _LOGGER.info("Disabling charge slot 1")
161-
# await api_client.control(CHARGE_SLOT1_TIME_CID, "00:00-00:00")
162-
# except SolisCloudControlApiError as err:
163-
# raise HomeAssistantError(str(err)) from err
164-
165-
# async def async_service_disable_discharge_slot1(call: ServiceCall) -> None: # noqa: ARG001
166-
# try:
167-
# _LOGGER.info("Disabling discharge slot 1")
168-
# await api_client.control(DISCHARGE_SLOT1_TIME_CID, "00:00-00:00")
169-
# except SolisCloudControlApiError as err:
170-
# raise HomeAssistantError(str(err)) from err
171-
172-
# hass.services.async_register(
173-
# DOMAIN,
174-
# READ_SERVICE_NAME,
175-
# async_service_read,
176-
# supports_response=SupportsResponse.ONLY,
177-
# schema=READ_SERVICE_SCHEMA,
178-
# )
179-
# hass.services.async_register(
180-
# DOMAIN,
181-
# CONTROL_SERVICE_NAME,
182-
# async_service_control,
183-
# supports_response=SupportsResponse.NONE,
184-
# schema=CONTROL_SERVICE_SCHEMA,
185-
# )
186-
# hass.services.async_register(
187-
# DOMAIN,
188-
# SET_STORAGE_MODE_SERVICE_NAME,
189-
# async_service_set_storage_mode,
190-
# supports_response=SupportsResponse.ONLY,
191-
# schema=SET_STORAGE_MODE_SERVICE_SCHEMA,
192-
# )
193-
# hass.services.async_register(
194-
# DOMAIN,
195-
# SET_CHARGE_SLOT1_SERVICE_NAME,
196-
# async_service_set_charge_slot1,
197-
# supports_response=SupportsResponse.ONLY,
198-
# schema=SET_CHARGE_SLOT1_SERVICE_SCHEMA,
199-
# )
200-
# hass.services.async_register(
201-
# DOMAIN,
202-
# SET_DISCHARGE_SLOT1_SERVICE_NAME,
203-
# async_service_set_discharge_slot1,
204-
# supports_response=SupportsResponse.ONLY,
205-
# schema=SET_DISCHARGE_SLOT1_SERVICE_SCHEMA,
206-
# )
207-
# hass.services.async_register(
208-
# DOMAIN,
209-
# DISABLE_CHARGE_SLOT1_SERVICE_NAME,
210-
# async_service_disable_charge_slot1,
211-
# supports_response=SupportsResponse.NONE,
212-
# )
213-
# hass.services.async_register(
214-
# DOMAIN,
215-
# DISABLE_DISCHARGE_SLOT1_SERVICE_NAME,
216-
# async_service_disable_discharge_slot1,
217-
# supports_response=SupportsResponse.NONE,
218-
# )
37+
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
21938

22039
return True
22140

22241

223-
async def async_unload_entry(hass: HomeAssistant, entry: SolisCloudControlConfigEntry) -> bool:
42+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
22443
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
44+
45+
46+
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
47+
await async_unload_entry(hass, entry)
48+
await async_setup_entry(hass, entry)

custom_components/solis_cloud_control/api.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,10 @@ def __init__(
4545
self,
4646
api_key: str,
4747
api_token: str,
48-
inverter_sn: str,
4948
session: aiohttp.ClientSession,
5049
) -> None:
5150
self._api_key = api_key
5251
self._api_secret = api_token
53-
self._inverter_sn = inverter_sn
5452
self._session = session
5553
self._request_semaphore = asyncio.Semaphore(API_CONCURRENT_REQUESTS)
5654

@@ -108,9 +106,9 @@ async def _request(self, date: datetime, endpoint: str, payload: dict[str, any]
108106
interval=API_RETRY_DELAY_SECONDS,
109107
logger=_LOGGER,
110108
)
111-
async def read(self, cid: int) -> str:
109+
async def read(self, inverter_sn: str, cid: int) -> str:
112110
date = current_date()
113-
payload = {"inverterSn": self._inverter_sn, "cid": cid}
111+
payload = {"inverterSn": inverter_sn, "cid": cid}
114112

115113
data = await self._request(date, API_READ_ENDPOINT, payload)
116114

@@ -129,9 +127,9 @@ async def read(self, cid: int) -> str:
129127
interval=API_RETRY_DELAY_SECONDS,
130128
logger=_LOGGER,
131129
)
132-
async def read_batch(self, cids: list[int]) -> dict[int, str]:
130+
async def read_batch(self, inverter_sn: str, cids: list[int]) -> dict[int, str]:
133131
date = current_date()
134-
payload = {"inverterSn": self._inverter_sn, "cids": ",".join(map(str, cids))}
132+
payload = {"inverterSn": inverter_sn, "cids": ",".join(map(str, cids))}
135133

136134
data = await self._request(date, API_READ_BATCH_ENDPOINT, payload)
137135

@@ -166,9 +164,9 @@ async def read_batch(self, cids: list[int]) -> dict[int, str]:
166164
interval=API_RETRY_DELAY_SECONDS,
167165
logger=_LOGGER,
168166
)
169-
async def control(self, cid: int, value: str) -> None:
167+
async def control(self, inverter_sn: str, cid: int, value: str) -> None:
170168
date = current_date()
171-
payload = {"inverterSn": self._inverter_sn, "cid": cid, "value": value}
169+
payload = {"inverterSn": inverter_sn, "cid": cid, "value": value}
172170

173171
data_array = await self._request(date, API_CONTROL_ENDPOINT, payload)
174172

custom_components/solis_cloud_control/config_flow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async def async_step_user(self, user_input: dict[str, any] | None = None) -> Con
4444
_LOGGER.exception(error)
4545
errors["base"] = "unknown_error"
4646
else:
47-
await self.async_set_unique_id(unique_id=user_input[CONF_API_KEY])
47+
await self.async_set_unique_id(unique_id=user_input[CONF_INVERTER_SN])
4848
self._abort_if_unique_id_configured()
4949
return self.async_create_entry(
5050
title=user_input[CONF_API_KEY],
@@ -59,5 +59,5 @@ async def async_step_user(self, user_input: dict[str, any] | None = None) -> Con
5959

6060
async def _test_credentials(self, api_key: str, api_token: str, inverter_sn: str) -> None:
6161
session = aiohttp_client.async_get_clientsession(self.hass)
62-
api_client = SolisCloudControlApiClient(api_key, api_token, inverter_sn, session)
63-
await api_client.read(CID_STORAGE_MODE)
62+
api_client = SolisCloudControlApiClient(api_key, api_token, session)
63+
await api_client.read(inverter_sn, CID_STORAGE_MODE)

custom_components/solis_cloud_control/coordinator.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@
1414
CID_DISCHARGE_SLOT1_SOC,
1515
CID_DISCHARGE_SLOT1_TIME,
1616
CID_STORAGE_MODE,
17+
CONF_INVERTER_SN,
1718
)
1819

1920
_LOGGER = logging.getLogger(__name__)
2021

22+
_NAME = "Coordinator"
23+
24+
_UPDATE_INTERVAL = timedelta(minutes=5)
25+
2126
_ALL_CIDS = [
2227
CID_STORAGE_MODE,
2328
CID_CHARGE_SLOT1_CURRENT,
@@ -33,27 +38,26 @@ class SolisCloudControlData(dict[int, str | None]):
3338
pass
3439

3540

36-
type SolisCloudControlConfigEntry = ConfigEntry[SolisCloudControlData]
37-
38-
3941
class SolisCloudControlCoordinator(DataUpdateCoordinator[SolisCloudControlData]):
40-
config_entry: SolisCloudControlConfigEntry
41-
4242
def __init__(
43-
self, hass: HomeAssistant, config_entry: SolisCloudControlConfigEntry, api_client: SolisCloudControlApiClient
43+
self,
44+
hass: HomeAssistant,
45+
config_entry: ConfigEntry,
46+
api_client: SolisCloudControlApiClient,
4447
) -> None:
4548
super().__init__(
4649
hass,
4750
_LOGGER,
48-
name="Solis Cloud Control Coordinator",
51+
name=_NAME,
4952
config_entry=config_entry,
50-
update_interval=timedelta(minutes=5),
53+
update_interval=_UPDATE_INTERVAL,
5154
)
5255
self.api_client = api_client
56+
self.inverter_sn = config_entry.data[CONF_INVERTER_SN]
5357

5458
async def _async_update_data(self) -> SolisCloudControlData:
5559
try:
56-
result = await self.api_client.read_batch(_ALL_CIDS)
60+
result = await self.api_client.read_batch(self.inverter_sn, _ALL_CIDS)
5761
data = SolisCloudControlData({cid: result.get(cid) for cid in _ALL_CIDS})
5862
_LOGGER.debug("Data read from API: %s", data)
5963
return data
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
from homeassistant.helpers.device_registry import DeviceInfo
22
from homeassistant.helpers.update_coordinator import CoordinatorEntity
33

4+
from custom_components.solis_cloud_control.const import CONF_INVERTER_SN
45
from custom_components.solis_cloud_control.coordinator import SolisCloudControlCoordinator
56

67

78
class SolisCloudControlEntity(CoordinatorEntity[SolisCloudControlCoordinator]):
8-
_attr_has_entity_name = True
9-
109
def __init__(self, coordinator: SolisCloudControlCoordinator) -> None:
1110
super().__init__(coordinator)
11+
self._attr_unique_id = coordinator.config_entry.entry_id
1212
self._attr_device_info = DeviceInfo(
1313
identifiers={
1414
(
1515
coordinator.config_entry.domain,
16-
coordinator.config_entry.entry_id,
16+
coordinator.config_entry.data[CONF_INVERTER_SN],
1717
),
1818
},
1919
)

0 commit comments

Comments
 (0)