forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add integration for APsystems EZ1 microinverter (home-assistant#114531)
* Add APsystems local API integration * Fix session usage in config_flow in apsystems local api * Remove skip check option for apsystems_loca api * Update APsystems API dependency and increased test coverage to 100% * Utilize EntityDescriptions for APsystems Local integration * Ensure coverage entries are sorted (home-assistant#114424) * Ensure coverage entries are sorted * Use autofix * Adjust * Add comment to coverage file * test CI * revert CI test --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Use patch instead of Http Mocks for APsystems API tests * Fix linter waring for apsystemsapi * Fix apsystemsapi test * Fix CODEOWNERS for apsystemsapi * Address small PR review changes for apsystems_local * Remove wrong lines in coveragerc * Add serial number for apsystems_local * Remove option of custom refresh interval fro apsystems_local * Remove function override and fix stale comments * Use native device id and name storage instead of custom one for apsystems_local * Use runtime_data for apsystems_local * Don't store entry data in runtime data * Move from apsystems_local to apsystems domain --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
- Loading branch information
1 parent
2590db1
commit d4d30f1
Showing
18 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"""The APsystems local API integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
from APsystemsEZ1 import APsystemsEZ1M | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_IP_ADDRESS, Platform | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .coordinator import ApSystemsDataCoordinator | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up this integration using UI.""" | ||
entry.runtime_data = {} | ||
api = APsystemsEZ1M(ip_address=entry.data[CONF_IP_ADDRESS], timeout=8) | ||
coordinator = ApSystemsDataCoordinator(hass, api) | ||
await coordinator.async_config_entry_first_refresh() | ||
entry.runtime_data = {"COORDINATOR": coordinator} | ||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""The config_flow for APsystems local API integration.""" | ||
|
||
from aiohttp import client_exceptions | ||
from APsystemsEZ1 import APsystemsEZ1M | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.const import CONF_IP_ADDRESS | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from .const import DOMAIN, LOGGER | ||
|
||
DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_IP_ADDRESS): str, | ||
} | ||
) | ||
|
||
|
||
class APsystemsLocalAPIFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Config flow for Apsystems local.""" | ||
|
||
VERSION = 1 | ||
|
||
async def async_step_user( | ||
self, | ||
user_input: dict | None = None, | ||
) -> config_entries.ConfigFlowResult: | ||
"""Handle a flow initialized by the user.""" | ||
_errors = {} | ||
session = async_get_clientsession(self.hass, False) | ||
|
||
if user_input is not None: | ||
try: | ||
session = async_get_clientsession(self.hass, False) | ||
api = APsystemsEZ1M(user_input[CONF_IP_ADDRESS], session=session) | ||
device_info = await api.get_device_info() | ||
await self.async_set_unique_id(device_info.deviceId) | ||
except (TimeoutError, client_exceptions.ClientConnectionError) as exception: | ||
LOGGER.warning(exception) | ||
_errors["base"] = "connection_refused" | ||
else: | ||
return self.async_create_entry( | ||
title="Solar", | ||
data=user_input, | ||
) | ||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=DATA_SCHEMA, | ||
errors=_errors, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Constants for the APsystems Local API integration.""" | ||
|
||
from logging import Logger, getLogger | ||
|
||
LOGGER: Logger = getLogger(__package__) | ||
DOMAIN = "apsystems" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
"""The coordinator for APsystems local API integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from datetime import timedelta | ||
import logging | ||
|
||
from APsystemsEZ1 import APsystemsEZ1M, ReturnOutputData | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class InverterNotAvailable(Exception): | ||
"""Error used when Device is offline.""" | ||
|
||
|
||
class ApSystemsDataCoordinator(DataUpdateCoordinator): | ||
"""Coordinator used for all sensors.""" | ||
|
||
def __init__(self, hass: HomeAssistant, api: APsystemsEZ1M) -> None: | ||
"""Initialize my coordinator.""" | ||
super().__init__( | ||
hass, | ||
_LOGGER, | ||
# Name of the data. For logging purposes. | ||
name="APSystems Data", | ||
# Polling interval. Will only be polled if there are subscribers. | ||
update_interval=timedelta(seconds=12), | ||
) | ||
self.api = api | ||
self.always_update = True | ||
|
||
async def _async_update_data(self) -> ReturnOutputData: | ||
return await self.api.get_output_data() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"domain": "apsystems", | ||
"name": "APsystems", | ||
"codeowners": ["@mawoka-myblock", "@SonnenladenGmbH"], | ||
"config_flow": true, | ||
"dependencies": [], | ||
"documentation": "https://www.home-assistant.io/integrations/apsystems", | ||
"homekit": {}, | ||
"iot_class": "local_polling", | ||
"requirements": ["apsystems-ez1==1.3.1"], | ||
"ssdp": [], | ||
"zeroconf": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
"""The read-only sensors for APsystems local API integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Callable | ||
from dataclasses import dataclass | ||
|
||
from APsystemsEZ1 import ReturnOutputData | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.components.sensor import ( | ||
SensorDeviceClass, | ||
SensorEntity, | ||
SensorEntityDescription, | ||
SensorStateClass, | ||
) | ||
from homeassistant.const import UnitOfEnergy, UnitOfPower | ||
from homeassistant.core import HomeAssistant, callback | ||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
from homeassistant.helpers.typing import DiscoveryInfoType | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .coordinator import ApSystemsDataCoordinator | ||
|
||
|
||
@dataclass(frozen=True, kw_only=True) | ||
class ApsystemsLocalApiSensorDescription(SensorEntityDescription): | ||
"""Describes Apsystens Inverter sensor entity.""" | ||
|
||
value_fn: Callable[[ReturnOutputData], float | None] | ||
|
||
|
||
SENSORS: tuple[ApsystemsLocalApiSensorDescription, ...] = ( | ||
ApsystemsLocalApiSensorDescription( | ||
key="total_power", | ||
translation_key="total_power", | ||
native_unit_of_measurement=UnitOfPower.WATT, | ||
device_class=SensorDeviceClass.POWER, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
value_fn=lambda c: c.p1 + c.p2, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="total_power_p1", | ||
translation_key="total_power_p1", | ||
native_unit_of_measurement=UnitOfPower.WATT, | ||
device_class=SensorDeviceClass.POWER, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
value_fn=lambda c: c.p1, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="total_power_p2", | ||
translation_key="total_power_p2", | ||
native_unit_of_measurement=UnitOfPower.WATT, | ||
device_class=SensorDeviceClass.POWER, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
value_fn=lambda c: c.p2, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="lifetime_production", | ||
translation_key="lifetime_production", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.te1 + c.te2, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="lifetime_production_p1", | ||
translation_key="lifetime_production_p1", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.te1, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="lifetime_production_p2", | ||
translation_key="lifetime_production_p2", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.te2, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="today_production", | ||
translation_key="today_production", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.e1 + c.e2, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="today_production_p1", | ||
translation_key="today_production_p1", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.e1, | ||
), | ||
ApsystemsLocalApiSensorDescription( | ||
key="today_production_p2", | ||
translation_key="today_production_p2", | ||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, | ||
device_class=SensorDeviceClass.ENERGY, | ||
state_class=SensorStateClass.TOTAL_INCREASING, | ||
value_fn=lambda c: c.e2, | ||
), | ||
) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config_entry: config_entries.ConfigEntry, | ||
add_entities: AddEntitiesCallback, | ||
discovery_info: DiscoveryInfoType | None = None, | ||
) -> None: | ||
"""Set up the sensor platform.""" | ||
config = config_entry.runtime_data | ||
coordinator = config["COORDINATOR"] | ||
device_name = config_entry.title | ||
device_id: str = config_entry.unique_id # type: ignore[assignment] | ||
|
||
add_entities( | ||
ApSystemsSensorWithDescription(coordinator, desc, device_name, device_id) | ||
for desc in SENSORS | ||
) | ||
|
||
|
||
class ApSystemsSensorWithDescription(CoordinatorEntity, SensorEntity): | ||
"""Base sensor to be used with description.""" | ||
|
||
entity_description: ApsystemsLocalApiSensorDescription | ||
|
||
def __init__( | ||
self, | ||
coordinator: ApSystemsDataCoordinator, | ||
entity_description: ApsystemsLocalApiSensorDescription, | ||
device_name: str, | ||
device_id: str, | ||
) -> None: | ||
"""Initialize the sensor.""" | ||
super().__init__(coordinator) | ||
self.entity_description = entity_description | ||
self._device_name = device_name | ||
self._device_id = device_id | ||
self._attr_unique_id = f"{device_id}_{entity_description.key}" | ||
|
||
@property | ||
def device_info(self) -> DeviceInfo: | ||
"""Get the DeviceInfo.""" | ||
return DeviceInfo( | ||
identifiers={("apsystems", self._device_id)}, | ||
name=self._device_name, | ||
serial_number=self._device_id, | ||
manufacturer="APsystems", | ||
model="EZ1-M", | ||
) | ||
|
||
@callback | ||
def _handle_coordinator_update(self) -> None: | ||
if self.coordinator.data is None: | ||
return # type: ignore[unreachable] | ||
self._attr_native_value = self.entity_description.value_fn( | ||
self.coordinator.data | ||
) | ||
self.async_write_ha_state() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"config": { | ||
"step": { | ||
"user": { | ||
"data": { | ||
"host": "[%key:common::config_flow::data::host%]", | ||
"username": "[%key:common::config_flow::data::username%]", | ||
"password": "[%key:common::config_flow::data::password%]" | ||
} | ||
} | ||
}, | ||
"error": { | ||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", | ||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", | ||
"unknown": "[%key:common::config_flow::error::unknown%]" | ||
}, | ||
"abort": { | ||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,7 @@ | |
"apcupsd", | ||
"apple_tv", | ||
"aprilaire", | ||
"apsystems", | ||
"aranet", | ||
"arcam_fmj", | ||
"arve", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.