Add select platform to Nintendo Switch parental controls (#159217)

This commit is contained in:
Jordan Harvey
2025-12-16 18:06:43 +00:00
committed by GitHub
parent 10dd53ffc2
commit 8a10638470
6 changed files with 230 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ _PLATFORMS: list[Platform] = [
Platform.TIME,
Platform.SWITCH,
Platform.NUMBER,
Platform.SELECT,
]
PLATFORM_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

View File

@@ -0,0 +1,95 @@
"""Nintendo Switch Parental Controls select entity definitions."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from pynintendoparental.enum import DeviceTimerMode
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import NintendoParentalControlsConfigEntry, NintendoUpdateCoordinator
from .entity import Device, NintendoDevice
PARALLEL_UPDATES = 1
class NintendoParentalSelect(StrEnum):
"""Store keys for Nintendo Parental Controls select entities."""
TIMER_MODE = "timer_mode"
@dataclass(kw_only=True, frozen=True)
class NintendoParentalControlsSelectEntityDescription(SelectEntityDescription):
"""Description for Nintendo Parental Controls select entities."""
get_option: Callable[[Device], DeviceTimerMode | None]
set_option_fn: Callable[[Device, DeviceTimerMode], Coroutine[Any, Any, None]]
options_enum: type[DeviceTimerMode]
SELECT_DESCRIPTIONS: tuple[NintendoParentalControlsSelectEntityDescription, ...] = (
NintendoParentalControlsSelectEntityDescription(
key=NintendoParentalSelect.TIMER_MODE,
translation_key=NintendoParentalSelect.TIMER_MODE,
get_option=lambda device: device.timer_mode,
set_option_fn=lambda device, option: device.set_timer_mode(option),
options_enum=DeviceTimerMode,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: NintendoParentalControlsConfigEntry,
async_add_devices: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the select platform."""
async_add_devices(
NintendoParentalSelectEntity(
coordinator=entry.runtime_data,
device=device,
description=description,
)
for device in entry.runtime_data.api.devices.values()
for description in SELECT_DESCRIPTIONS
)
class NintendoParentalSelectEntity(NintendoDevice, SelectEntity):
"""Nintendo Parental Controls select entity."""
entity_description: NintendoParentalControlsSelectEntityDescription
def __init__(
self,
coordinator: NintendoUpdateCoordinator,
device: Device,
description: NintendoParentalControlsSelectEntityDescription,
) -> None:
"""Initialize the select entity."""
super().__init__(coordinator=coordinator, device=device, key=description.key)
self.entity_description = description
@property
def current_option(self) -> str | None:
"""Return the current selected option."""
option = self.entity_description.get_option(self._device)
return option.name.lower() if option else None
@property
def options(self) -> list[str]:
"""Return a list of available options."""
return [option.name.lower() for option in self.entity_description.options_enum]
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
enum_option = self.entity_description.options_enum[option.upper()]
await self.entity_description.set_option_fn(self._device, enum_option)
await self.coordinator.async_request_refresh()

View File

@@ -37,6 +37,15 @@
"name": "Max screentime today"
}
},
"select": {
"timer_mode": {
"name": "Restriction mode",
"state": {
"daily": "Same for all days",
"each_day_of_the_week": "Different for each day"
}
}
},
"sensor": {
"playing_time": {
"name": "Used screen time"

View File

@@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from pynintendoparental import NintendoParental
from pynintendoparental.device import Device
from pynintendoparental.enum import DeviceTimerMode
import pytest
from homeassistant.components.nintendo_parental_controls.const import DOMAIN
@@ -39,9 +40,11 @@ def mock_nintendo_device() -> Device:
mock.today_playing_time = 110
mock.today_time_remaining = 10
mock.bedtime_alarm = time(hour=19)
mock.timer_mode = DeviceTimerMode.DAILY
mock.add_extra_time.return_value = None
mock.set_bedtime_alarm.return_value = None
mock.update_max_daily_playtime.return_value = None
mock.set_timer_mode.return_value = None
mock.forced_termination_mode = True
mock.model = "Test Model"
mock.generation = "P00"

View File

@@ -0,0 +1,58 @@
# serializer version: 1
# name: test_select[select.home_assistant_test_restriction_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'daily',
'each_day_of_the_week',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.home_assistant_test_restriction_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': 'Restriction mode',
'platform': 'nintendo_parental_controls',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': <NintendoParentalSelect.TIMER_MODE: 'timer_mode'>,
'unique_id': 'testdevid_timer_mode',
'unit_of_measurement': None,
})
# ---
# name: test_select[select.home_assistant_test_restriction_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Home Assistant Test Restriction mode',
'options': list([
'daily',
'each_day_of_the_week',
]),
}),
'context': <ANY>,
'entity_id': 'select.home_assistant_test_restriction_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'daily',
})
# ---

View File

@@ -0,0 +1,64 @@
"""Tests for Nintendo Switch Parental Controls select platform."""
from unittest.mock import AsyncMock, patch
from pynintendoparental.enum import DeviceTimerMode
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.select import (
ATTR_OPTION,
DOMAIN as SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
)
from homeassistant.const import ATTR_ENTITY_ID, 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_select(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_nintendo_client: AsyncMock,
mock_nintendo_device: AsyncMock,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test select platform."""
with patch(
"homeassistant.components.nintendo_parental_controls._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_nintendo_client: AsyncMock,
mock_nintendo_device: AsyncMock,
) -> None:
"""Test select option service."""
with patch(
"homeassistant.components.nintendo_parental_controls._PLATFORMS",
[Platform.SELECT],
):
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SELECT_DOMAIN,
SERVICE_SELECT_OPTION,
{
ATTR_ENTITY_ID: "select.home_assistant_test_restriction_mode",
ATTR_OPTION: DeviceTimerMode.EACH_DAY_OF_THE_WEEK.name.lower(),
},
blocking=True,
)
mock_nintendo_device.set_timer_mode.assert_awaited_once_with(
DeviceTimerMode.EACH_DAY_OF_THE_WEEK
)