Add slot list service to ohme (#134170)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Dan Raper 2024-12-29 17:07:12 +00:00 committed by GitHub
parent 65147f8d4c
commit 88d366b0c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 202 additions and 8 deletions

View File

@ -8,9 +8,14 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, PLATFORMS
from .coordinator import OhmeAdvancedSettingsCoordinator, OhmeChargeSessionCoordinator
from .services import async_setup_services
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
type OhmeConfigEntry = ConfigEntry[OhmeRuntimeData]
@ -23,6 +28,13 @@ class OhmeRuntimeData:
advanced_settings_coordinator: OhmeAdvancedSettingsCoordinator
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Ohme integration."""
async_setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: OhmeConfigEntry) -> bool:
"""Set up Ohme from a config entry."""

View File

@ -19,5 +19,10 @@
"default": "mdi:gauge"
}
}
},
"services": {
"list_charge_slots": {
"service": "mdi:clock-start"
}
}
}

View File

@ -1,19 +1,13 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
This integration has no custom actions.
action-setup: done
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration has no custom actions.
docs-actions: done
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done

View File

@ -0,0 +1,75 @@
"""Ohme services."""
from typing import Final
from ohme import OhmeApiClient
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import selector
from .const import DOMAIN
SERVICE_LIST_CHARGE_SLOTS = "list_charge_slots"
ATTR_CONFIG_ENTRY: Final = "config_entry"
SERVICE_SCHEMA: Final = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): selector.ConfigEntrySelector(
{
"integration": DOMAIN,
}
),
}
)
def __get_client(call: ServiceCall) -> OhmeApiClient:
"""Get the client from the config entry."""
entry_id: str = call.data[ATTR_CONFIG_ENTRY]
entry: ConfigEntry | None = call.hass.config_entries.async_get_entry(entry_id)
if not entry:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_config_entry",
translation_placeholders={
"config_entry": entry_id,
},
)
if entry.state != ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unloaded_config_entry",
translation_placeholders={
"config_entry": entry.title,
},
)
return entry.runtime_data.charge_session_coordinator.client
def async_setup_services(hass: HomeAssistant) -> None:
"""Register services."""
async def list_charge_slots(
service_call: ServiceCall,
) -> ServiceResponse:
"""List of charge slots."""
client = __get_client(service_call)
return {"slots": client.slots}
hass.services.async_register(
DOMAIN,
SERVICE_LIST_CHARGE_SLOTS,
list_charge_slots,
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.ONLY,
)

View File

@ -0,0 +1,7 @@
list_charge_slots:
fields:
config_entry:
required: true
selector:
config_entry:
integration: ohme

View File

@ -32,6 +32,18 @@
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"services": {
"list_charge_slots": {
"name": "List charge slots",
"description": "Return a list of charge slots.",
"fields": {
"config_entry": {
"name": "Ohme account",
"description": "The Ohme config entry for which to return charge slots."
}
}
}
},
"entity": {
"button": {
"approve": {
@ -62,6 +74,12 @@
},
"api_failed": {
"message": "Error communicating with Ohme API"
},
"invalid_config_entry": {
"message": "Invalid config entry provided. Got {config_entry}"
},
"unloaded_config_entry": {
"message": "Invalid config entry provided. {config_entry} is not loaded."
}
}
}

View File

@ -53,6 +53,7 @@ def mock_client():
client.async_login.return_value = True
client.status = ChargerStatus.CHARGING
client.power = ChargerPower(0, 0, 0, 0)
client.serial = "chargerid"
client.ct_connected = True
client.energy = 1000

View File

@ -0,0 +1,12 @@
# serializer version: 1
# name: test_list_charge_slots
dict({
'slots': list([
dict({
'end': '2024-12-30T04:30:39+00:00',
'energy': 2.042,
'start': '2024-12-30T04:00:00+00:00',
}),
]),
})
# ---

View File

@ -0,0 +1,70 @@
"""Tests for services."""
from unittest.mock import MagicMock
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.ohme.const import DOMAIN
from homeassistant.components.ohme.services import (
ATTR_CONFIG_ENTRY,
SERVICE_LIST_CHARGE_SLOTS,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from . import setup_integration
from tests.common import MockConfigEntry
async def test_list_charge_slots(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Test list charge slots service."""
await setup_integration(hass, mock_config_entry)
mock_client.slots = [
{
"start": "2024-12-30T04:00:00+00:00",
"end": "2024-12-30T04:30:39+00:00",
"energy": 2.042,
}
]
assert snapshot == await hass.services.async_call(
DOMAIN,
"list_charge_slots",
{
ATTR_CONFIG_ENTRY: mock_config_entry.entry_id,
},
blocking=True,
return_response=True,
)
async def test_list_charge_slots_exception(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_client: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Test list charge slots service."""
await setup_integration(hass, mock_config_entry)
# Test error
with pytest.raises(
ServiceValidationError, match="Invalid config entry provided. Got invalid"
):
await hass.services.async_call(
DOMAIN,
SERVICE_LIST_CHARGE_SLOTS,
{ATTR_CONFIG_ENTRY: "invalid"},
blocking=True,
return_response=True,
)