Add more sensors to Peblar Rocksolid EV Chargers integration (#133754)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/133757/head
Franck Nijhof 2024-12-21 22:40:15 +01:00 committed by GitHub
parent 5e4e1ce5a7
commit 9dc20b5709
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 386 additions and 19 deletions

View File

@ -5,6 +5,38 @@ from __future__ import annotations
import logging
from typing import Final
from peblar import ChargeLimiter, CPState
DOMAIN: Final = "peblar"
LOGGER = logging.getLogger(__package__)
PEBLAR_CHARGE_LIMITER_TO_HOME_ASSISTANT = {
ChargeLimiter.CHARGING_CABLE: "charging_cable",
ChargeLimiter.CURRENT_LIMITER: "current_limiter",
ChargeLimiter.DYNAMIC_LOAD_BALANCING: "dynamic_load_balancing",
ChargeLimiter.EXTERNAL_POWER_LIMIT: "external_power_limit",
ChargeLimiter.GROUP_LOAD_BALANCING: "group_load_balancing",
ChargeLimiter.HARDWARE_LIMITATION: "hardware_limitation",
ChargeLimiter.HIGH_TEMPERATURE: "high_temperature",
ChargeLimiter.HOUSEHOLD_POWER_LIMIT: "household_power_limit",
ChargeLimiter.INSTALLATION_LIMIT: "installation_limit",
ChargeLimiter.LOCAL_MODBUS_API: "local_modbus_api",
ChargeLimiter.LOCAL_REST_API: "local_rest_api",
ChargeLimiter.LOCAL_SCHEDULED: "local_scheduled",
ChargeLimiter.OCPP_SMART_CHARGING: "ocpp_smart_charging",
ChargeLimiter.OVERCURRENT_PROTECTION: "overcurrent_protection",
ChargeLimiter.PHASE_IMBALANCE: "phase_imbalance",
ChargeLimiter.POWER_FACTOR: "power_factor",
ChargeLimiter.SOLAR_CHARGING: "solar_charging",
}
PEBLAR_CP_STATE_TO_HOME_ASSISTANT = {
CPState.CHARGING_SUSPENDED: "suspended",
CPState.CHARGING_VENTILATION: "charging",
CPState.CHARGING: "charging",
CPState.ERROR: "error",
CPState.FAULT: "fault",
CPState.INVALID: "invalid",
CPState.NO_EV_CONNECTED: "no_ev_connected",
}

View File

@ -24,6 +24,17 @@
}
}
},
"sensor": {
"cp_state": {
"default": "mdi:ev-plug-type2"
},
"charge_current_limit_source": {
"default": "mdi:arrow-collapse-up"
},
"uptime": {
"default": "mdi:timer"
}
},
"switch": {
"force_single_phase": {
"default": "mdi:power-cycle"

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from peblar import PeblarUserConfiguration
@ -24,8 +25,13 @@ 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 homeassistant.util.dt import utcnow
from .const import DOMAIN
from .const import (
DOMAIN,
PEBLAR_CHARGE_LIMITER_TO_HOME_ASSISTANT,
PEBLAR_CP_STATE_TO_HOME_ASSISTANT,
)
from .coordinator import PeblarConfigEntry, PeblarData, PeblarDataUpdateCoordinator
@ -34,21 +40,37 @@ class PeblarSensorDescription(SensorEntityDescription):
"""Describe a Peblar sensor."""
has_fn: Callable[[PeblarUserConfiguration], bool] = lambda _: True
value_fn: Callable[[PeblarData], int | None]
value_fn: Callable[[PeblarData], datetime | int | str | None]
DESCRIPTIONS: tuple[PeblarSensorDescription, ...] = (
PeblarSensorDescription(
key="current",
key="cp_state",
translation_key="cp_state",
device_class=SensorDeviceClass.ENUM,
options=list(PEBLAR_CP_STATE_TO_HOME_ASSISTANT.values()),
value_fn=lambda x: PEBLAR_CP_STATE_TO_HOME_ASSISTANT[x.ev.cp_state],
),
PeblarSensorDescription(
key="charge_current_limit_source",
translation_key="charge_current_limit_source",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
options=list(PEBLAR_CHARGE_LIMITER_TO_HOME_ASSISTANT.values()),
value_fn=lambda x: PEBLAR_CHARGE_LIMITER_TO_HOME_ASSISTANT[
x.ev.charge_current_limit_source
],
),
PeblarSensorDescription(
key="current_total",
device_class=SensorDeviceClass.CURRENT,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
has_fn=lambda x: x.connected_phases == 1,
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
suggested_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
value_fn=lambda x: x.meter.current_phase_1,
value_fn=lambda x: x.meter.current_total,
),
PeblarSensorDescription(
key="current_phase_1",
@ -193,6 +215,16 @@ DESCRIPTIONS: tuple[PeblarSensorDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda x: x.meter.voltage_phase_3,
),
PeblarSensorDescription(
key="uptime",
translation_key="uptime",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=lambda x: (
utcnow().replace(microsecond=0) - timedelta(seconds=x.system.uptime)
),
),
)
@ -232,6 +264,6 @@ class PeblarSensorEntity(CoordinatorEntity[PeblarDataUpdateCoordinator], SensorE
)
@property
def native_value(self) -> int | None:
def native_value(self) -> datetime | int | str | None:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)

View File

@ -1,8 +1,16 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_serial_number": "The discovered Peblar device did not provide a serial number."
},
"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%]"
},
"step": {
"user": {
"description": "Set up your Peblar EV charger to integrate with Home Assistant.\n\nTo do so, you will need to get the IP address of your Peblar charger and the password you use to log into the Peblar device' web interface.\n\nHome Assistant will automatically configure your Peblar charger for use with Home Assistant.",
"data": {
"host": "[%key:common::config_flow::data::host%]",
"password": "[%key:common::config_flow::data::password%]"
@ -10,26 +18,18 @@
"data_description": {
"host": "The hostname or IP address of your Peblar charger on your home network.",
"password": "The same password as you use to log in to the Peblar device' local web interface."
}
},
"description": "Set up your Peblar EV charger to integrate with Home Assistant.\n\nTo do so, you will need to get the IP address of your Peblar charger and the password you use to log into the Peblar device' web interface.\n\nHome Assistant will automatically configure your Peblar charger for use with Home Assistant."
},
"zeroconf_confirm": {
"description": "Set up your Peblar EV charger to integrate with Home Assistant.\n\nTo do so, you will need the password you use to log into the Peblar device' web interface.\n\nHome Assistant will automatically configure your Peblar charger for use with Home Assistant.",
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "[%key:component::peblar::config::step::user::data_description::password%]"
}
},
"description": "Set up your Peblar EV charger to integrate with Home Assistant.\n\nTo do so, you will need the password you use to log into the Peblar device' web interface.\n\nHome Assistant will automatically configure your Peblar charger for use with Home Assistant."
}
},
"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%]",
"no_serial_number": "The discovered Peblar device did not provide a serial number."
}
},
"entity": {
@ -59,6 +59,38 @@
}
},
"sensor": {
"charge_current_limit_source": {
"name": "Limit source",
"state": {
"charging_cable": "Charging cable",
"current_limiter": "Current limiter",
"dynamic_load_balancing": "Dynamic load balancing",
"external_power_limit": "External power limit",
"group_load_balancing": "Group load balancing",
"hardware_limitation": "Hardware limitation",
"high_temperature": "High temperature",
"household_power_limit": "Household power limit",
"installation_limit": "Installation limit",
"local_modbus_api": "Modbus API",
"local_rest_api": "REST API",
"ocpp_smart_charging": "OCPP smart charging",
"overcurrent_protection": "Overcurrent protection",
"phase_imbalance": "Phase imbalance",
"power_factor": "Power factor",
"solar_charging": "Solar charging"
}
},
"cp_state": {
"name": "State",
"state": {
"charging": "Charging",
"error": "Error",
"fault": "Fault",
"invalid": "Invalid",
"no_ev_connected": "No EV connected",
"suspended": "Suspended"
}
},
"current_phase_1": {
"name": "Current phase 1"
},
@ -83,6 +115,9 @@
"power_phase_3": {
"name": "Power phase 3"
},
"uptime": {
"name": "Uptime"
},
"voltage_phase_1": {
"name": "Voltage phase 1"
},

View File

@ -1,4 +1,61 @@
# serializer version: 1
# name: test_entities[sensor][sensor.peblar_ev_charger_current-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.peblar_ev_charger_current',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor': dict({
'suggested_display_precision': 1,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
}),
}),
'original_device_class': <SensorDeviceClass.CURRENT: 'current'>,
'original_icon': None,
'original_name': 'Current',
'platform': 'peblar',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '23-45-A4O-MOF_current_total',
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_current-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'current',
'friendly_name': 'Peblar EV Charger Current',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfElectricCurrent.AMPERE: 'A'>,
}),
'context': <ANY>,
'entity_id': 'sensor.peblar_ev_charger_current',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '14.242',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_current_phase_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -227,6 +284,92 @@
'state': '880.703',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_limit_source-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'charging_cable',
'current_limiter',
'dynamic_load_balancing',
'external_power_limit',
'group_load_balancing',
'hardware_limitation',
'high_temperature',
'household_power_limit',
'installation_limit',
'local_modbus_api',
'local_rest_api',
'local_scheduled',
'ocpp_smart_charging',
'overcurrent_protection',
'phase_imbalance',
'power_factor',
'solar_charging',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.peblar_ev_charger_limit_source',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Limit source',
'platform': 'peblar',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'charge_current_limit_source',
'unique_id': '23-45-A4O-MOF_charge_current_limit_source',
'unit_of_measurement': None,
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_limit_source-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Peblar EV Charger Limit source',
'options': list([
'charging_cable',
'current_limiter',
'dynamic_load_balancing',
'external_power_limit',
'group_load_balancing',
'hardware_limitation',
'high_temperature',
'household_power_limit',
'installation_limit',
'local_modbus_api',
'local_rest_api',
'local_scheduled',
'ocpp_smart_charging',
'overcurrent_protection',
'phase_imbalance',
'power_factor',
'solar_charging',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.peblar_ev_charger_limit_source',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'current_limiter',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_power-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -488,6 +631,119 @@
'state': '0.381',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'suspended',
'charging',
'charging',
'error',
'fault',
'invalid',
'no_ev_connected',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.peblar_ev_charger_state',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'State',
'platform': 'peblar',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'cp_state',
'unique_id': '23-45-A4O-MOF_cp_state',
'unit_of_measurement': None,
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_state-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Peblar EV Charger State',
'options': list([
'suspended',
'charging',
'charging',
'error',
'fault',
'invalid',
'no_ev_connected',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.peblar_ev_charger_state',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'charging',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_uptime-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.peblar_ev_charger_uptime',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'original_icon': None,
'original_name': 'Uptime',
'platform': 'peblar',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'uptime',
'unique_id': '23-45-A4O-MOF_uptime',
'unit_of_measurement': None,
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_uptime-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'timestamp',
'friendly_name': 'Peblar EV Charger Uptime',
}),
'context': <ANY>,
'entity_id': 'sensor.peblar_ev_charger_uptime',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2024-12-18T04:16:46+00:00',
})
# ---
# name: test_entities[sensor][sensor.peblar_ev_charger_voltage_phase_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@ -11,6 +11,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.freeze_time("2024-12-21 21:45:00")
@pytest.mark.parametrize("init_integration", [Platform.SENSOR], indirect=True)
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_entities(