|
@@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
|
|
|
|
|
|
|
|
+from .api import resolve_setting_key
|
|
|
from .const import DOMAIN
|
|
from .const import DOMAIN
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -35,10 +36,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
|
|
|
async_add_entities(entities)
|
|
async_add_entities(entities)
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _setting_value(coordinator_data: dict | None, key: str) -> int | None:
|
|
|
|
|
- """Extract the integer value for a device setting key from coordinator data."""
|
|
|
|
|
|
|
+def _setting_value(coordinator_data: dict | None, canonical_key: str) -> int | None:
|
|
|
|
|
+ """Extract the integer value for a canonical setting from coordinator data.
|
|
|
|
|
+
|
|
|
|
|
+ Resolves through resolve_setting_key() so a firmware that exposes this
|
|
|
|
|
+ control under a different key name (#18) still reads its real state
|
|
|
|
|
+ instead of showing unknown.
|
|
|
|
|
+ """
|
|
|
settings = ((coordinator_data or {}).get("settings") or {})
|
|
settings = ((coordinator_data or {}).get("settings") or {})
|
|
|
- entry = settings.get(key)
|
|
|
|
|
|
|
+ key = resolve_setting_key(settings, canonical_key)
|
|
|
|
|
+ entry = settings.get(key) if key else None
|
|
|
if entry is None:
|
|
if entry is None:
|
|
|
return None
|
|
return None
|
|
|
raw = entry.get("value") if isinstance(entry, dict) else entry
|
|
raw = entry.get("value") if isinstance(entry, dict) else entry
|
|
@@ -49,6 +56,13 @@ def _setting_value(coordinator_data: dict | None, key: str) -> int | None:
|
|
|
|
|
|
|
|
|
|
|
|
|
class _BaseSwitch(CoordinatorEntity, SwitchEntity):
|
|
class _BaseSwitch(CoordinatorEntity, SwitchEntity):
|
|
|
|
|
+ # Canonical setting key this entity controls (set by subclasses). See
|
|
|
|
|
+ # resolve_setting_key() in api.py: lets a firmware that exposes this
|
|
|
|
|
+ # control under a different key name still work, and a firmware that
|
|
|
|
|
+ # doesn't expose it under any known name report unavailable instead of
|
|
|
|
|
+ # offering a control guaranteed to fail.
|
|
|
|
|
+ _canonical_key: str = ""
|
|
|
|
|
+
|
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
|
super().__init__(coordinator)
|
|
super().__init__(coordinator)
|
|
|
self._api = api
|
|
self._api = api
|
|
@@ -66,6 +80,13 @@ class _BaseSwitch(CoordinatorEntity, SwitchEntity):
|
|
|
"via_device": (DOMAIN, self._station_id),
|
|
"via_device": (DOMAIN, self._station_id),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @property
|
|
|
|
|
+ def available(self) -> bool:
|
|
|
|
|
+ if not super().available:
|
|
|
|
|
+ return False
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ return resolve_setting_key(settings, self._canonical_key) is not None
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class SolarOfThingsGridChargingSwitch(_BaseSwitch):
|
|
class SolarOfThingsGridChargingSwitch(_BaseSwitch):
|
|
|
"""Switch for AC Input Range setting (acInputRangeSetting).
|
|
"""Switch for AC Input Range setting (acInputRangeSetting).
|
|
@@ -75,6 +96,8 @@ class SolarOfThingsGridChargingSwitch(_BaseSwitch):
|
|
|
The switch reports ON when the inverter is in Appliance (grid-charging) mode.
|
|
The switch reports ON when the inverter is in Appliance (grid-charging) mode.
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+ _canonical_key = "acInputRangeSetting"
|
|
|
|
|
+
|
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
|
self._attr_name = f"{device_name} Grid Charging (AC Input Range)"
|
|
self._attr_name = f"{device_name} Grid Charging (AC Input Range)"
|
|
@@ -84,17 +107,19 @@ class SolarOfThingsGridChargingSwitch(_BaseSwitch):
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def is_on(self) -> bool | None:
|
|
def is_on(self) -> bool | None:
|
|
|
- val = _setting_value(self.coordinator.data, "acInputRangeSetting")
|
|
|
|
|
|
|
+ val = _setting_value(self.coordinator.data, self._canonical_key)
|
|
|
if val is None:
|
|
if val is None:
|
|
|
return None
|
|
return None
|
|
|
return val == 0 # 0=Appliance (charging OK), 1=UPS (bypass)
|
|
return val == 0 # 0=Appliance (charging OK), 1=UPS (bypass)
|
|
|
|
|
|
|
|
async def async_turn_on(self, **kwargs):
|
|
async def async_turn_on(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_grid_charging, self._device_id, True)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_grid_charging, self._device_id, True, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
async def async_turn_off(self, **kwargs):
|
|
async def async_turn_off(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_grid_charging, self._device_id, False)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_grid_charging, self._device_id, False, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
|
|
|
|
@@ -105,6 +130,8 @@ class SolarOfThingsGridFeedInSwitch(_BaseSwitch):
|
|
|
1 = ON (grid switch enabled / feed-in on).
|
|
1 = ON (grid switch enabled / feed-in on).
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+ _canonical_key = "batteryPowerLimitingSetting"
|
|
|
|
|
+
|
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
|
self._attr_name = f"{device_name} Grid Feed-In"
|
|
self._attr_name = f"{device_name} Grid Feed-In"
|
|
@@ -114,17 +141,19 @@ class SolarOfThingsGridFeedInSwitch(_BaseSwitch):
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def is_on(self) -> bool | None:
|
|
def is_on(self) -> bool | None:
|
|
|
- val = _setting_value(self.coordinator.data, "batteryPowerLimitingSetting")
|
|
|
|
|
|
|
+ val = _setting_value(self.coordinator.data, self._canonical_key)
|
|
|
if val is None:
|
|
if val is None:
|
|
|
return None
|
|
return None
|
|
|
return val == 1 # 1=ON
|
|
return val == 1 # 1=ON
|
|
|
|
|
|
|
|
async def async_turn_on(self, **kwargs):
|
|
async def async_turn_on(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_grid_feed_in, self._device_id, True)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_grid_feed_in, self._device_id, True, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
async def async_turn_off(self, **kwargs):
|
|
async def async_turn_off(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_grid_feed_in, self._device_id, False)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_grid_feed_in, self._device_id, False, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
|
|
|
|
@@ -138,6 +167,8 @@ class SolarOfThingsBackupModeSwitch(_BaseSwitch):
|
|
|
to 'Solar+Battery First (SBU)', which is the expected behaviour.
|
|
to 'Solar+Battery First (SBU)', which is the expected behaviour.
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+ _canonical_key = "outputSourcePrioritySetting"
|
|
|
|
|
+
|
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
def __init__(self, api, coordinator, station_id: str, device_id: str, device_name: str) -> None:
|
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
super().__init__(api, coordinator, station_id, device_id, device_name)
|
|
|
self._attr_name = f"{device_name} Backup Mode (SBU Priority)"
|
|
self._attr_name = f"{device_name} Backup Mode (SBU Priority)"
|
|
@@ -147,15 +178,17 @@ class SolarOfThingsBackupModeSwitch(_BaseSwitch):
|
|
|
|
|
|
|
|
@property
|
|
@property
|
|
|
def is_on(self) -> bool | None:
|
|
def is_on(self) -> bool | None:
|
|
|
- val = _setting_value(self.coordinator.data, "outputSourcePrioritySetting")
|
|
|
|
|
|
|
+ val = _setting_value(self.coordinator.data, self._canonical_key)
|
|
|
if val is None:
|
|
if val is None:
|
|
|
return None
|
|
return None
|
|
|
return val == 2 # SBU
|
|
return val == 2 # SBU
|
|
|
|
|
|
|
|
async def async_turn_on(self, **kwargs):
|
|
async def async_turn_on(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_backup_mode, self._device_id, True)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_backup_mode, self._device_id, True, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|
|
|
|
|
|
|
|
async def async_turn_off(self, **kwargs):
|
|
async def async_turn_off(self, **kwargs):
|
|
|
- await self.hass.async_add_executor_job(self._api.set_backup_mode, self._device_id, False)
|
|
|
|
|
|
|
+ settings = (self.coordinator.data or {}).get("settings")
|
|
|
|
|
+ await self.hass.async_add_executor_job(self._api.set_backup_mode, self._device_id, False, settings)
|
|
|
await self.coordinator.async_request_refresh()
|
|
await self.coordinator.async_request_refresh()
|