mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 17:04:04 -04:00
Add icons, state translations and enum mapping for OpenEVSE status sensor (#180329)
Co-authored-by: Colin L Rice <colin@daedrum.net>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"divert_active": {
|
||||
"default": "mdi:call-split"
|
||||
},
|
||||
"has_limit": {
|
||||
"default": "mdi:car-speed-limiter"
|
||||
},
|
||||
"shaper_active": {
|
||||
"default": "mdi:tune-variant"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"gfi_trip_count": {
|
||||
"default": "mdi:counter"
|
||||
},
|
||||
"no_gnd_trip_count": {
|
||||
"default": "mdi:counter"
|
||||
},
|
||||
"service_level": {
|
||||
"default": "mdi:home-lightning-bolt-outline"
|
||||
},
|
||||
"status": {
|
||||
"default": "mdi:ev-station",
|
||||
"state": {
|
||||
"charging": "mdi:battery-charging",
|
||||
"connected": "mdi:power-plug",
|
||||
"diode_check_failed": "mdi:alert-circle",
|
||||
"disabled": "mdi:stop-circle-outline",
|
||||
"gfci_fault": "mdi:alert-circle",
|
||||
"gfci_self_test_failure": "mdi:alert-circle",
|
||||
"no_ground": "mdi:alert",
|
||||
"not_connected": "mdi:power-plug-off",
|
||||
"over_temperature": "mdi:thermometer-alert",
|
||||
"sleeping": "mdi:sleep",
|
||||
"stuck_relay": "mdi:alert",
|
||||
"vent_required": "mdi:fan-alert"
|
||||
}
|
||||
},
|
||||
"stuck_relay_trip_count": {
|
||||
"default": "mdi:counter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ from homeassistant.helpers.entity_platform import (
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import DOMAIN, INTEGRATION_TITLE
|
||||
from .coordinator import OpenEVSEConfigEntry, OpenEVSEDataUpdateCoordinator
|
||||
@@ -53,6 +54,29 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
STATUS_OPTIONS: list[str] = [
|
||||
"charging",
|
||||
"connected",
|
||||
"diode_check_failed",
|
||||
"disabled",
|
||||
"gfci_fault",
|
||||
"gfci_self_test_failure",
|
||||
"no_ground",
|
||||
"not_connected",
|
||||
"over_temperature",
|
||||
"sleeping",
|
||||
"stuck_relay",
|
||||
"vent_required",
|
||||
]
|
||||
|
||||
|
||||
def _map_status(status: str | None) -> str | None:
|
||||
"""Map raw status string to enum option."""
|
||||
if status is not None and (slug := slugify(status)) in STATUS_OPTIONS:
|
||||
return slug
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class OpenEVSESensorDescription(SensorEntityDescription):
|
||||
"""Describes an OpenEVSE sensor entity."""
|
||||
@@ -65,7 +89,9 @@ SENSOR_TYPES: tuple[OpenEVSESensorDescription, ...] = (
|
||||
OpenEVSESensorDescription(
|
||||
key="status",
|
||||
translation_key="status",
|
||||
value_fn=lambda ev: ev.status,
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=STATUS_OPTIONS,
|
||||
value_fn=lambda ev: _map_status(ev.status),
|
||||
),
|
||||
OpenEVSESensorDescription(
|
||||
key="service_level",
|
||||
|
||||
@@ -172,7 +172,21 @@
|
||||
"name": "Smoothed available current"
|
||||
},
|
||||
"status": {
|
||||
"name": "Charging status"
|
||||
"name": "Charging status",
|
||||
"state": {
|
||||
"charging": "Charging",
|
||||
"connected": "Connected",
|
||||
"diode_check_failed": "Diode check failed",
|
||||
"disabled": "Disabled",
|
||||
"gfci_fault": "GFCI fault",
|
||||
"gfci_self_test_failure": "GFCI self-test failure",
|
||||
"no_ground": "No ground",
|
||||
"not_connected": "Not connected",
|
||||
"over_temperature": "Over temperature",
|
||||
"sleeping": "Sleeping",
|
||||
"stuck_relay": "Stuck relay",
|
||||
"vent_required": "Vent required"
|
||||
}
|
||||
},
|
||||
"stuck_relay_trip_count": {
|
||||
"name": "Stuck relay trip count"
|
||||
|
||||
@@ -246,7 +246,22 @@
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'charging',
|
||||
'connected',
|
||||
'diode_check_failed',
|
||||
'disabled',
|
||||
'gfci_fault',
|
||||
'gfci_self_test_failure',
|
||||
'no_ground',
|
||||
'not_connected',
|
||||
'over_temperature',
|
||||
'sleeping',
|
||||
'stuck_relay',
|
||||
'vent_required',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -265,7 +280,7 @@
|
||||
'object_id_base': 'Charging status',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Charging status',
|
||||
'platform': 'openevse',
|
||||
@@ -280,14 +295,29 @@
|
||||
# name: test_entities[sensor.openevse_mock_config_charging_status-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'enum',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'openevse_mock_config Charging status',
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'charging',
|
||||
'connected',
|
||||
'diode_check_failed',
|
||||
'disabled',
|
||||
'gfci_fault',
|
||||
'gfci_self_test_failure',
|
||||
'no_ground',
|
||||
'not_connected',
|
||||
'over_temperature',
|
||||
'sleeping',
|
||||
'stuck_relay',
|
||||
'vent_required',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.openevse_mock_config_charging_status',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Charging',
|
||||
'state': 'charging',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.openevse_mock_config_charging_voltage-entry]
|
||||
|
||||
@@ -77,7 +77,7 @@ async def test_missing_sensor_graceful_handling(
|
||||
# Other sensors should still work
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state is not None
|
||||
assert state.state == "Charging"
|
||||
assert state.state == "charging"
|
||||
|
||||
|
||||
async def test_websocket_callback_updates_entities(
|
||||
@@ -92,7 +92,7 @@ async def test_websocket_callback_updates_entities(
|
||||
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state
|
||||
assert state.state == "Charging"
|
||||
assert state.state == "charging"
|
||||
|
||||
mock_charger.status = "Sleeping"
|
||||
await mock_charger.callback()
|
||||
@@ -100,7 +100,7 @@ async def test_websocket_callback_updates_entities(
|
||||
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state
|
||||
assert state.state == "Sleeping"
|
||||
assert state.state == "sleeping"
|
||||
|
||||
|
||||
async def test_sensor_unavailable_on_coordinator_timeout(
|
||||
@@ -187,3 +187,46 @@ async def test_yaml_import_already_configured(
|
||||
issue = issue_registry.async_get_issue("homeassistant", "deprecated_yaml")
|
||||
assert issue is not None
|
||||
assert issue.issue_domain == DOMAIN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw_status", "expected_state"),
|
||||
[
|
||||
pytest.param("not connected", "not_connected", id="not_connected"),
|
||||
pytest.param("connected", "connected", id="connected"),
|
||||
pytest.param("charging", "charging", id="charging"),
|
||||
pytest.param("vent required", "vent_required", id="vent_required"),
|
||||
pytest.param(
|
||||
"diode check failed", "diode_check_failed", id="diode_check_failed"
|
||||
),
|
||||
pytest.param("gfci fault", "gfci_fault", id="gfci_fault"),
|
||||
pytest.param("no ground", "no_ground", id="no_ground"),
|
||||
pytest.param("stuck relay", "stuck_relay", id="stuck_relay"),
|
||||
pytest.param(
|
||||
"gfci self-test failure",
|
||||
"gfci_self_test_failure",
|
||||
id="gfci_self_test_failure",
|
||||
),
|
||||
pytest.param("over temperature", "over_temperature", id="over_temperature"),
|
||||
pytest.param("sleeping", "sleeping", id="sleeping"),
|
||||
pytest.param("disabled", "disabled", id="disabled"),
|
||||
pytest.param("unknown", STATE_UNKNOWN, id="unknown"),
|
||||
pytest.param("unrecognized_raw_status", STATE_UNKNOWN, id="fallback_unknown"),
|
||||
pytest.param(None, STATE_UNKNOWN, id="none_status"),
|
||||
],
|
||||
)
|
||||
async def test_status_sensor_mapping(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_charger: MagicMock,
|
||||
raw_status: str | None,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test status sensor mapping to enum options."""
|
||||
mock_charger.status = raw_status
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state is not None
|
||||
assert state.state == expected_state
|
||||
|
||||
Reference in New Issue
Block a user