mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Add update platform to Peblar Rocksolid EV Chargers integration (#133570)
* Add update platform to Peblar Rocksolid EV Chargers integration * Use device class translations
This commit is contained in:
parent
4a063c3f9e
commit
859993e443
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from aiohttp import CookieJar
|
from aiohttp import CookieJar
|
||||||
from peblar import (
|
from peblar import (
|
||||||
AccessMode,
|
AccessMode,
|
||||||
@ -14,22 +16,34 @@ from peblar import (
|
|||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||||
|
|
||||||
from .coordinator import PeblarConfigEntry, PeblarMeterDataUpdateCoordinator
|
from .const import DOMAIN
|
||||||
|
from .coordinator import (
|
||||||
|
PeblarConfigEntry,
|
||||||
|
PeblarMeterDataUpdateCoordinator,
|
||||||
|
PeblarRuntimeData,
|
||||||
|
PeblarVersionDataUpdateCoordinator,
|
||||||
|
)
|
||||||
|
|
||||||
PLATFORMS = [Platform.SENSOR]
|
PLATFORMS = [
|
||||||
|
Platform.SENSOR,
|
||||||
|
Platform.UPDATE,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bool:
|
||||||
"""Set up Peblar from a config entry."""
|
"""Set up Peblar from a config entry."""
|
||||||
|
|
||||||
|
# Set up connection to the Peblar charger
|
||||||
peblar = Peblar(
|
peblar = Peblar(
|
||||||
host=entry.data[CONF_HOST],
|
host=entry.data[CONF_HOST],
|
||||||
session=async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)),
|
session=async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await peblar.login(password=entry.data[CONF_PASSWORD])
|
await peblar.login(password=entry.data[CONF_PASSWORD])
|
||||||
|
system_information = await peblar.system_information()
|
||||||
api = await peblar.rest_api(enable=True, access_mode=AccessMode.READ_WRITE)
|
api = await peblar.rest_api(enable=True, access_mode=AccessMode.READ_WRITE)
|
||||||
except PeblarConnectionError as err:
|
except PeblarConnectionError as err:
|
||||||
raise ConfigEntryNotReady("Could not connect to Peblar charger") from err
|
raise ConfigEntryNotReady("Could not connect to Peblar charger") from err
|
||||||
@ -40,10 +54,41 @@ async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bo
|
|||||||
"Unknown error occurred while connecting to Peblar charger"
|
"Unknown error occurred while connecting to Peblar charger"
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
coordinator = PeblarMeterDataUpdateCoordinator(hass, entry, api)
|
# Setup the data coordinators
|
||||||
await coordinator.async_config_entry_first_refresh()
|
meter_coordinator = PeblarMeterDataUpdateCoordinator(hass, entry, api)
|
||||||
|
version_coordinator = PeblarVersionDataUpdateCoordinator(hass, entry, peblar)
|
||||||
|
await asyncio.gather(
|
||||||
|
meter_coordinator.async_config_entry_first_refresh(),
|
||||||
|
version_coordinator.async_config_entry_first_refresh(),
|
||||||
|
)
|
||||||
|
|
||||||
entry.runtime_data = coordinator
|
# Store the runtime data
|
||||||
|
entry.runtime_data = PeblarRuntimeData(
|
||||||
|
system_information=system_information,
|
||||||
|
meter_coordinator=meter_coordinator,
|
||||||
|
version_coordinator=version_coordinator,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Peblar is a single device integration. Setting up the device directly
|
||||||
|
# during setup. This way we only have to reference it in all entities.
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
configuration_url=f"http://{entry.data[CONF_HOST]}",
|
||||||
|
connections={
|
||||||
|
(dr.CONNECTION_NETWORK_MAC, system_information.ethernet_mac_address),
|
||||||
|
(dr.CONNECTION_NETWORK_MAC, system_information.wlan_mac_address),
|
||||||
|
},
|
||||||
|
identifiers={(DOMAIN, system_information.product_serial_number)},
|
||||||
|
manufacturer=system_information.product_vendor_name,
|
||||||
|
model_id=system_information.product_number,
|
||||||
|
model=system_information.product_model_name,
|
||||||
|
name="Peblar EV Charger",
|
||||||
|
serial_number=system_information.product_serial_number,
|
||||||
|
sw_version=version_coordinator.data.current.firmware,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Forward the setup to the platforms
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1,16 +1,67 @@
|
|||||||
"""Data update coordinator for Peblar EV chargers."""
|
"""Data update coordinator for Peblar EV chargers."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from peblar import PeblarApi, PeblarError, PeblarMeter
|
from peblar import Peblar, PeblarApi, PeblarError, PeblarMeter, PeblarVersions
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
from tests.components.peblar.conftest import PeblarSystemInformation
|
||||||
|
|
||||||
from .const import LOGGER
|
from .const import LOGGER
|
||||||
|
|
||||||
type PeblarConfigEntry = ConfigEntry[PeblarMeterDataUpdateCoordinator]
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class PeblarRuntimeData:
|
||||||
|
"""Class to hold runtime data."""
|
||||||
|
|
||||||
|
system_information: PeblarSystemInformation
|
||||||
|
meter_coordinator: PeblarMeterDataUpdateCoordinator
|
||||||
|
version_coordinator: PeblarVersionDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
type PeblarConfigEntry = ConfigEntry[PeblarRuntimeData]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True, frozen=True)
|
||||||
|
class PeblarVersionInformation:
|
||||||
|
"""Class to hold version information."""
|
||||||
|
|
||||||
|
current: PeblarVersions
|
||||||
|
available: PeblarVersions
|
||||||
|
|
||||||
|
|
||||||
|
class PeblarVersionDataUpdateCoordinator(
|
||||||
|
DataUpdateCoordinator[PeblarVersionInformation]
|
||||||
|
):
|
||||||
|
"""Class to manage fetching Peblar version information."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, entry: PeblarConfigEntry, peblar: Peblar
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the coordinator."""
|
||||||
|
self.peblar = peblar
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
LOGGER,
|
||||||
|
config_entry=entry,
|
||||||
|
name=f"Peblar {entry.title} version",
|
||||||
|
update_interval=timedelta(hours=2),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> PeblarVersionInformation:
|
||||||
|
"""Fetch data from the Peblar device."""
|
||||||
|
try:
|
||||||
|
return PeblarVersionInformation(
|
||||||
|
current=await self.peblar.current_versions(),
|
||||||
|
available=await self.peblar.available_versions(),
|
||||||
|
)
|
||||||
|
except PeblarError as err:
|
||||||
|
raise UpdateFailed(err) from err
|
||||||
|
|
||||||
|
|
||||||
class PeblarMeterDataUpdateCoordinator(DataUpdateCoordinator[PeblarMeter]):
|
class PeblarMeterDataUpdateCoordinator(DataUpdateCoordinator[PeblarMeter]):
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
"""Base entity for the Peblar integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from homeassistant.const import CONF_HOST
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
|
||||||
from .coordinator import PeblarConfigEntry, PeblarMeterDataUpdateCoordinator
|
|
||||||
|
|
||||||
|
|
||||||
class PeblarEntity(CoordinatorEntity[PeblarMeterDataUpdateCoordinator]):
|
|
||||||
"""Defines a Peblar entity."""
|
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
def __init__(self, entry: PeblarConfigEntry) -> None:
|
|
||||||
"""Initialize the Peblar entity."""
|
|
||||||
super().__init__(coordinator=entry.runtime_data)
|
|
||||||
self._attr_device_info = DeviceInfo(
|
|
||||||
configuration_url=f"http://{entry.data[CONF_HOST]}",
|
|
||||||
identifiers={(DOMAIN, str(entry.unique_id))},
|
|
||||||
manufacturer="Peblar",
|
|
||||||
name="Peblar EV charger",
|
|
||||||
)
|
|
9
homeassistant/components/peblar/icons.json
Normal file
9
homeassistant/components/peblar/icons.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"update": {
|
||||||
|
"customization": {
|
||||||
|
"default": "mdi:palette"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,12 @@ from homeassistant.components.sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import UnitOfEnergy
|
from homeassistant.const import UnitOfEnergy
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .coordinator import PeblarConfigEntry
|
from .const import DOMAIN
|
||||||
from .entity import PeblarEntity
|
from .coordinator import PeblarConfigEntry, PeblarMeterDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -28,7 +30,7 @@ class PeblarSensorDescription(SensorEntityDescription):
|
|||||||
value_fn: Callable[[PeblarMeter], int | None]
|
value_fn: Callable[[PeblarMeter], int | None]
|
||||||
|
|
||||||
|
|
||||||
SENSORS: tuple[PeblarSensorDescription, ...] = (
|
DESCRIPTIONS: tuple[PeblarSensorDescription, ...] = (
|
||||||
PeblarSensorDescription(
|
PeblarSensorDescription(
|
||||||
key="energy_total",
|
key="energy_total",
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
@ -48,24 +50,33 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Peblar sensors based on a config entry."""
|
"""Set up Peblar sensors based on a config entry."""
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
PeblarSensorEntity(entry, description) for description in SENSORS
|
PeblarSensorEntity(entry, description) for description in DESCRIPTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PeblarSensorEntity(PeblarEntity, SensorEntity):
|
class PeblarSensorEntity(
|
||||||
|
CoordinatorEntity[PeblarMeterDataUpdateCoordinator], SensorEntity
|
||||||
|
):
|
||||||
"""Defines a Peblar sensor."""
|
"""Defines a Peblar sensor."""
|
||||||
|
|
||||||
entity_description: PeblarSensorDescription
|
entity_description: PeblarSensorDescription
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
entry: PeblarConfigEntry,
|
entry: PeblarConfigEntry,
|
||||||
description: PeblarSensorDescription,
|
description: PeblarSensorDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Peblar entity."""
|
"""Initialize the Peblar entity."""
|
||||||
super().__init__(entry)
|
super().__init__(entry.runtime_data.meter_coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
|
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={
|
||||||
|
(DOMAIN, entry.runtime_data.system_information.product_serial_number)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> int | None:
|
def native_value(self) -> int | None:
|
||||||
|
@ -31,5 +31,12 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"no_serial_number": "The discovered Peblar device did not provide a serial number."
|
"no_serial_number": "The discovered Peblar device did not provide a serial number."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"update": {
|
||||||
|
"customization": {
|
||||||
|
"name": "Customization"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
93
homeassistant/components/peblar/update.py
Normal file
93
homeassistant/components/peblar/update.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
"""Support for Peblar updates."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from homeassistant.components.update import (
|
||||||
|
UpdateDeviceClass,
|
||||||
|
UpdateEntity,
|
||||||
|
UpdateEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import (
|
||||||
|
PeblarConfigEntry,
|
||||||
|
PeblarVersionDataUpdateCoordinator,
|
||||||
|
PeblarVersionInformation,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class PeblarUpdateEntityDescription(UpdateEntityDescription):
|
||||||
|
"""Describe an Peblar update entity."""
|
||||||
|
|
||||||
|
installed_fn: Callable[[PeblarVersionInformation], str | None]
|
||||||
|
available_fn: Callable[[PeblarVersionInformation], str | None]
|
||||||
|
|
||||||
|
|
||||||
|
DESCRIPTIONS: tuple[PeblarUpdateEntityDescription, ...] = (
|
||||||
|
PeblarUpdateEntityDescription(
|
||||||
|
key="firmware",
|
||||||
|
device_class=UpdateDeviceClass.FIRMWARE,
|
||||||
|
installed_fn=lambda x: x.current.firmware,
|
||||||
|
available_fn=lambda x: x.available.firmware,
|
||||||
|
),
|
||||||
|
PeblarUpdateEntityDescription(
|
||||||
|
key="customization",
|
||||||
|
translation_key="customization",
|
||||||
|
installed_fn=lambda x: x.current.customization,
|
||||||
|
available_fn=lambda x: x.available.customization,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: PeblarConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Peblar update based on a config entry."""
|
||||||
|
async_add_entities(
|
||||||
|
PeblarUpdateEntity(entry, description) for description in DESCRIPTIONS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PeblarUpdateEntity(
|
||||||
|
CoordinatorEntity[PeblarVersionDataUpdateCoordinator], UpdateEntity
|
||||||
|
):
|
||||||
|
"""Defines a Peblar update entity."""
|
||||||
|
|
||||||
|
entity_description: PeblarUpdateEntityDescription
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
entry: PeblarConfigEntry,
|
||||||
|
description: PeblarUpdateEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the update entity."""
|
||||||
|
super().__init__(entry.runtime_data.version_coordinator)
|
||||||
|
self.entity_description = description
|
||||||
|
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={
|
||||||
|
(DOMAIN, entry.runtime_data.system_information.product_serial_number)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def installed_version(self) -> str | None:
|
||||||
|
"""Version currently installed and in use."""
|
||||||
|
return self.entity_description.installed_fn(self.coordinator.data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_version(self) -> str | None:
|
||||||
|
"""Latest version available for install."""
|
||||||
|
return self.entity_description.available_fn(self.coordinator.data)
|
Loading…
x
Reference in New Issue
Block a user