Add new sensors of Kostal Plenticore integration (#103802)
This commit is contained in:
parent
0a13968209
commit
09d7679818
11 changed files with 221 additions and 84 deletions
|
@ -3,13 +3,18 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, TypeVar, cast
|
||||
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
from pykoplenti import ApiClient, ApiException, AuthenticationException
|
||||
from pykoplenti import (
|
||||
ApiClient,
|
||||
ApiException,
|
||||
AuthenticationException,
|
||||
ExtendedApiClient,
|
||||
)
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
|
@ -51,7 +56,9 @@ class Plenticore:
|
|||
|
||||
async def async_setup(self) -> bool:
|
||||
"""Set up Plenticore API client."""
|
||||
self._client = ApiClient(async_get_clientsession(self.hass), host=self.host)
|
||||
self._client = ExtendedApiClient(
|
||||
async_get_clientsession(self.hass), host=self.host
|
||||
)
|
||||
try:
|
||||
await self._client.login(self.config_entry.data[CONF_PASSWORD])
|
||||
except AuthenticationException as err:
|
||||
|
@ -124,7 +131,7 @@ class DataUpdateCoordinatorMixin:
|
|||
|
||||
async def async_read_data(
|
||||
self, module_id: str, data_id: str
|
||||
) -> dict[str, dict[str, str]] | None:
|
||||
) -> Mapping[str, Mapping[str, str]] | None:
|
||||
"""Read data from Plenticore."""
|
||||
if (client := self._plenticore.client) is None:
|
||||
return None
|
||||
|
@ -190,7 +197,7 @@ class PlenticoreUpdateCoordinator(DataUpdateCoordinator[_DataT]):
|
|||
|
||||
|
||||
class ProcessDataUpdateCoordinator(
|
||||
PlenticoreUpdateCoordinator[dict[str, dict[str, str]]]
|
||||
PlenticoreUpdateCoordinator[Mapping[str, Mapping[str, str]]]
|
||||
):
|
||||
"""Implementation of PlenticoreUpdateCoordinator for process data."""
|
||||
|
||||
|
@ -206,18 +213,19 @@ class ProcessDataUpdateCoordinator(
|
|||
return {
|
||||
module_id: {
|
||||
process_data.id: process_data.value
|
||||
for process_data in fetched_data[module_id]
|
||||
for process_data in fetched_data[module_id].values()
|
||||
}
|
||||
for module_id in fetched_data
|
||||
}
|
||||
|
||||
|
||||
class SettingDataUpdateCoordinator(
|
||||
PlenticoreUpdateCoordinator[dict[str, dict[str, str]]], DataUpdateCoordinatorMixin
|
||||
PlenticoreUpdateCoordinator[Mapping[str, Mapping[str, str]]],
|
||||
DataUpdateCoordinatorMixin,
|
||||
):
|
||||
"""Implementation of PlenticoreUpdateCoordinator for settings data."""
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict[str, str]]:
|
||||
async def _async_update_data(self) -> Mapping[str, Mapping[str, str]]:
|
||||
client = self._plenticore.client
|
||||
|
||||
if not self._fetch or client is None:
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/kostal_plenticore",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["kostal"],
|
||||
"requirements": ["pykoplenti==1.0.0"]
|
||||
"requirements": ["pykoplenti==1.2.2"]
|
||||
}
|
||||
|
|
|
@ -649,6 +649,39 @@ SENSOR_PROCESS_DATA = [
|
|||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="scb:statistic:EnergyFlow",
|
||||
key="Statistic:EnergyDischarge:Day",
|
||||
name="Battery Discharge Day",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="scb:statistic:EnergyFlow",
|
||||
key="Statistic:EnergyDischarge:Month",
|
||||
name="Battery Discharge Month",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="scb:statistic:EnergyFlow",
|
||||
key="Statistic:EnergyDischarge:Year",
|
||||
name="Battery Discharge Year",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="scb:statistic:EnergyFlow",
|
||||
key="Statistic:EnergyDischarge:Total",
|
||||
name="Battery Discharge Total",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="scb:statistic:EnergyFlow",
|
||||
key="Statistic:EnergyDischargeGrid:Day",
|
||||
|
@ -682,6 +715,52 @@ SENSOR_PROCESS_DATA = [
|
|||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="_virt_",
|
||||
key="pv_P",
|
||||
name="Sum power of all PV DC inputs",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
entity_registry_enabled_default=True,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
formatter="format_round",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="_virt_",
|
||||
key="Statistic:EnergyGrid:Total",
|
||||
name="Energy to Grid Total",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="_virt_",
|
||||
key="Statistic:EnergyGrid:Year",
|
||||
name="Energy to Grid Year",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="_virt_",
|
||||
key="Statistic:EnergyGrid:Month",
|
||||
name="Energy to Grid Month",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
PlenticoreSensorEntityDescription(
|
||||
module_id="_virt_",
|
||||
key="Statistic:EnergyGrid:Day",
|
||||
name="Energy to Grid Day",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
formatter="format_energy",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1835,7 +1835,7 @@ pykmtronic==0.3.0
|
|||
pykodi==0.2.7
|
||||
|
||||
# homeassistant.components.kostal_plenticore
|
||||
pykoplenti==1.0.0
|
||||
pykoplenti==1.2.2
|
||||
|
||||
# homeassistant.components.kraken
|
||||
pykrakenapi==0.1.8
|
||||
|
|
|
@ -1388,7 +1388,7 @@ pykmtronic==0.3.0
|
|||
pykodi==0.2.7
|
||||
|
||||
# homeassistant.components.kostal_plenticore
|
||||
pykoplenti==1.0.0
|
||||
pykoplenti==1.2.2
|
||||
|
||||
# homeassistant.components.kraken
|
||||
pykrakenapi==0.1.8
|
||||
|
|
|
@ -49,24 +49,20 @@ def mock_plenticore() -> Generator[Plenticore, None, None]:
|
|||
|
||||
plenticore.client.get_version = AsyncMock()
|
||||
plenticore.client.get_version.return_value = VersionData(
|
||||
{
|
||||
"api_version": "0.2.0",
|
||||
"hostname": "scb",
|
||||
"name": "PUCK RESTful API",
|
||||
"sw_version": "01.16.05025",
|
||||
}
|
||||
api_version="0.2.0",
|
||||
hostname="scb",
|
||||
name="PUCK RESTful API",
|
||||
sw_version="01.16.05025",
|
||||
)
|
||||
|
||||
plenticore.client.get_me = AsyncMock()
|
||||
plenticore.client.get_me.return_value = MeData(
|
||||
{
|
||||
"locked": False,
|
||||
"active": True,
|
||||
"authenticated": True,
|
||||
"permissions": [],
|
||||
"anonymous": False,
|
||||
"role": "USER",
|
||||
}
|
||||
locked=False,
|
||||
active=True,
|
||||
authenticated=True,
|
||||
permissions=[],
|
||||
anonymous=False,
|
||||
role="USER",
|
||||
)
|
||||
|
||||
plenticore.client.get_process_data = AsyncMock()
|
||||
|
|
|
@ -54,7 +54,19 @@ async def test_form_g1(
|
|||
# mock of the context manager instance
|
||||
mock_apiclient.login = AsyncMock()
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
|
||||
return_value={
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Hostname",
|
||||
type="string",
|
||||
),
|
||||
]
|
||||
}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
|
@ -108,7 +120,19 @@ async def test_form_g2(
|
|||
# mock of the context manager instance
|
||||
mock_apiclient.login = AsyncMock()
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
|
||||
return_value={
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Network:Hostname",
|
||||
type="string",
|
||||
),
|
||||
]
|
||||
}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
|
|
|
@ -26,15 +26,13 @@ async def test_entry_diagnostics(
|
|||
mock_plenticore.client.get_settings.return_value = {
|
||||
"devices:local": [
|
||||
SettingsData(
|
||||
{
|
||||
"id": "Battery:MinSoc",
|
||||
"unit": "%",
|
||||
"default": "None",
|
||||
"min": 5,
|
||||
"max": 100,
|
||||
"type": "byte",
|
||||
"access": "readwrite",
|
||||
}
|
||||
min="5",
|
||||
max="100",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit="%",
|
||||
id="Battery:MinSoc",
|
||||
type="byte",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
@ -56,12 +54,12 @@ async def test_entry_diagnostics(
|
|||
"disabled_by": None,
|
||||
},
|
||||
"client": {
|
||||
"version": "Version(api_version=0.2.0, hostname=scb, name=PUCK RESTful API, sw_version=01.16.05025)",
|
||||
"me": "Me(locked=False, active=True, authenticated=True, permissions=[], anonymous=False, role=USER)",
|
||||
"version": "api_version='0.2.0' hostname='scb' name='PUCK RESTful API' sw_version='01.16.05025'",
|
||||
"me": "is_locked=False is_active=True is_authenticated=True permissions=[] is_anonymous=False role='USER'",
|
||||
"available_process_data": {"devices:local": ["HomeGrid_P", "HomePv_P"]},
|
||||
"available_settings_data": {
|
||||
"devices:local": [
|
||||
"SettingsData(id=Battery:MinSoc, unit=%, default=None, min=5, max=100,type=byte, access=readwrite)"
|
||||
"min='5' max='100' default=None access='readwrite' unit='%' id='Battery:MinSoc' type='byte'"
|
||||
]
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from pykoplenti import ApiClient, SettingsData
|
||||
from pykoplenti import ApiClient, ExtendedApiClient, SettingsData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.kostal_plenticore.const import DOMAIN
|
||||
|
@ -17,10 +17,10 @@ from tests.common import MockConfigEntry
|
|||
def mock_apiclient() -> Generator[ApiClient, None, None]:
|
||||
"""Return a mocked ApiClient class."""
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.helper.ApiClient",
|
||||
"homeassistant.components.kostal_plenticore.helper.ExtendedApiClient",
|
||||
autospec=True,
|
||||
) as mock_api_class:
|
||||
apiclient = MagicMock(spec=ApiClient)
|
||||
apiclient = MagicMock(spec=ExtendedApiClient)
|
||||
apiclient.__aenter__.return_value = apiclient
|
||||
apiclient.__aexit__ = AsyncMock()
|
||||
mock_api_class.return_value = apiclient
|
||||
|
@ -34,7 +34,19 @@ async def test_plenticore_async_setup_g1(
|
|||
) -> None:
|
||||
"""Tests the async_setup() method of the Plenticore class for G1 models."""
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Hostname"})]}
|
||||
return_value={
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Hostname",
|
||||
type="string",
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
|
@ -74,7 +86,19 @@ async def test_plenticore_async_setup_g2(
|
|||
) -> None:
|
||||
"""Tests the async_setup() method of the Plenticore class for G2 models."""
|
||||
mock_apiclient.get_settings = AsyncMock(
|
||||
return_value={"scb:network": [SettingsData({"id": "Network:Hostname"})]}
|
||||
return_value={
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Network:Hostname",
|
||||
type="string",
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
mock_apiclient.get_setting_values = AsyncMock(
|
||||
# G1 model has the entry id "Hostname"
|
||||
|
|
|
@ -23,9 +23,9 @@ from tests.common import MockConfigEntry, async_fire_time_changed
|
|||
|
||||
@pytest.fixture
|
||||
def mock_plenticore_client() -> Generator[ApiClient, None, None]:
|
||||
"""Return a patched ApiClient."""
|
||||
"""Return a patched ExtendedApiClient."""
|
||||
with patch(
|
||||
"homeassistant.components.kostal_plenticore.helper.ApiClient",
|
||||
"homeassistant.components.kostal_plenticore.helper.ExtendedApiClient",
|
||||
autospec=True,
|
||||
) as plenticore_client_class:
|
||||
yield plenticore_client_class.return_value
|
||||
|
@ -41,39 +41,33 @@ def mock_get_setting_values(mock_plenticore_client: ApiClient) -> list:
|
|||
mock_plenticore_client.get_settings.return_value = {
|
||||
"devices:local": [
|
||||
SettingsData(
|
||||
{
|
||||
"default": None,
|
||||
"min": 5,
|
||||
"max": 100,
|
||||
"access": "readwrite",
|
||||
"unit": "%",
|
||||
"type": "byte",
|
||||
"id": "Battery:MinSoc",
|
||||
}
|
||||
min="5",
|
||||
max="100",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit="%",
|
||||
id="Battery:MinSoc",
|
||||
type="byte",
|
||||
),
|
||||
SettingsData(
|
||||
{
|
||||
"default": None,
|
||||
"min": 50,
|
||||
"max": 38000,
|
||||
"access": "readwrite",
|
||||
"unit": "W",
|
||||
"type": "byte",
|
||||
"id": "Battery:MinHomeComsumption",
|
||||
}
|
||||
min="50",
|
||||
max="38000",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit="W",
|
||||
id="Battery:MinHomeComsumption",
|
||||
type="byte",
|
||||
),
|
||||
],
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
{
|
||||
"min": "1",
|
||||
"default": None,
|
||||
"access": "readwrite",
|
||||
"unit": None,
|
||||
"id": "Hostname",
|
||||
"type": "string",
|
||||
"max": "63",
|
||||
}
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Hostname",
|
||||
type="string",
|
||||
)
|
||||
],
|
||||
}
|
||||
|
@ -129,15 +123,13 @@ async def test_setup_no_entries(
|
|||
mock_plenticore_client.get_settings.return_value = {
|
||||
"scb:network": [
|
||||
SettingsData(
|
||||
{
|
||||
"min": "1",
|
||||
"default": None,
|
||||
"access": "readwrite",
|
||||
"unit": None,
|
||||
"id": "Hostname",
|
||||
"type": "string",
|
||||
"max": "63",
|
||||
}
|
||||
min="1",
|
||||
max="63",
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Hostname",
|
||||
type="string",
|
||||
)
|
||||
],
|
||||
}
|
||||
|
|
|
@ -18,8 +18,24 @@ async def test_select_battery_charging_usage_available(
|
|||
|
||||
mock_plenticore.client.get_settings.return_value = {
|
||||
"devices:local": [
|
||||
SettingsData({"id": "Battery:SmartBatteryControl:Enable"}),
|
||||
SettingsData({"id": "Battery:TimeControl:Enable"}),
|
||||
SettingsData(
|
||||
min=None,
|
||||
max=None,
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Battery:SmartBatteryControl:Enable",
|
||||
type="string",
|
||||
),
|
||||
SettingsData(
|
||||
min=None,
|
||||
max=None,
|
||||
default=None,
|
||||
access="readwrite",
|
||||
unit=None,
|
||||
id="Battery:TimeControl:Enable",
|
||||
type="string",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue