Add select platform to Ohme (#136536)

* Add select platform

* Formatting

* Add parallel updates to select

* Remove comments
This commit is contained in:
Dan Raper 2025-01-26 12:52:01 +00:00 committed by GitHub
parent 93a231fb19
commit 7044771876
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 216 additions and 0 deletions

View File

@ -6,6 +6,7 @@ DOMAIN = "ohme"
PLATFORMS = [ PLATFORMS = [
Platform.BUTTON, Platform.BUTTON,
Platform.NUMBER, Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
Platform.TIME, Platform.TIME,

View File

@ -10,6 +10,11 @@
"default": "mdi:battery-heart" "default": "mdi:battery-heart"
} }
}, },
"select": {
"charge_mode": {
"default": "mdi:play-box"
}
},
"sensor": { "sensor": {
"status": { "status": {
"default": "mdi:car", "default": "mdi:car",

View File

@ -0,0 +1,70 @@
"""Platform for Ohme selects."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Final
from ohme import ApiException, ChargerMode, OhmeApiClient
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import OhmeConfigEntry
from .const import DOMAIN
from .entity import OhmeEntity, OhmeEntityDescription
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class OhmeSelectDescription(OhmeEntityDescription, SelectEntityDescription):
"""Class to describe an Ohme select entity."""
select_fn: Callable[[OhmeApiClient, Any], Awaitable[None]]
current_option_fn: Callable[[OhmeApiClient], str | None]
SELECT_DESCRIPTION: Final[OhmeSelectDescription] = OhmeSelectDescription(
key="charge_mode",
translation_key="charge_mode",
select_fn=lambda client, mode: client.async_set_mode(mode),
options=[e.value for e in ChargerMode],
current_option_fn=lambda client: client.mode.value if client.mode else None,
available_fn=lambda client: client.mode is not None,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: OhmeConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Ohme selects."""
coordinator = config_entry.runtime_data.charge_session_coordinator
async_add_entities([OhmeSelect(coordinator, SELECT_DESCRIPTION)])
class OhmeSelect(OhmeEntity, SelectEntity):
"""Ohme select entity."""
entity_description: OhmeSelectDescription
async def async_select_option(self, option: str) -> None:
"""Handle the selection of an option."""
try:
await self.entity_description.select_fn(self.coordinator.client, option)
except ApiException as e:
raise HomeAssistantError(
translation_key="api_failed", translation_domain=DOMAIN
) from e
await self.coordinator.async_request_refresh()
@property
def current_option(self) -> str | None:
"""Return the current selected option."""
return self.entity_description.current_option_fn(self.coordinator.client)

View File

@ -55,6 +55,16 @@
"name": "Target percentage" "name": "Target percentage"
} }
}, },
"select": {
"charge_mode": {
"name": "Charge mode",
"state": {
"smart_charge": "Smart charge",
"max_charge": "Max charge",
"paused": "Paused"
}
}
},
"sensor": { "sensor": {
"status": { "status": {
"name": "Status", "name": "Status",

View File

@ -0,0 +1,58 @@
# serializer version: 1
# name: test_selects[select.ohme_home_pro_charge_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'smart_charge',
'max_charge',
'paused',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.ohme_home_pro_charge_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Charge mode',
'platform': 'ohme',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'charge_mode',
'unique_id': 'chargerid_charge_mode',
'unit_of_measurement': None,
})
# ---
# name: test_selects[select.ohme_home_pro_charge_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Ohme Home Pro Charge mode',
'options': list([
'smart_charge',
'max_charge',
'paused',
]),
}),
'context': <ANY>,
'entity_id': 'select.ohme_home_pro_charge_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---

View File

@ -0,0 +1,72 @@
"""Tests for selects."""
from unittest.mock import AsyncMock, MagicMock, patch
from ohme import ChargerMode
from syrupy import SnapshotAssertion
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_selects(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
) -> None:
"""Test the Ohme selects."""
with patch("homeassistant.components.ohme.PLATFORMS", [Platform.SELECT]):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_select_option(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
) -> None:
"""Test selecting an option in the Ohme select entity."""
mock_client.mode = ChargerMode.SMART_CHARGE
mock_client.async_set_mode = AsyncMock()
await setup_integration(hass, mock_config_entry)
state = hass.states.get("select.ohme_home_pro_charge_mode")
assert state is not None
assert state.state == "smart_charge"
await hass.services.async_call(
"select",
"select_option",
{
"entity_id": "select.ohme_home_pro_charge_mode",
"option": "max_charge",
},
blocking=True,
)
mock_client.async_set_mode.assert_called_once_with("max_charge")
assert state.state == "smart_charge"
async def test_select_unavailable(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
) -> None:
"""Test that the select entity shows as unavailable when no mode is set."""
mock_client.mode = None
await setup_integration(hass, mock_config_entry)
state = hass.states.get("select.ohme_home_pro_charge_mode")
assert state is not None
assert state.state == STATE_UNAVAILABLE