diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e0341e4ed38a3..4ec4edb2be034 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: category: "/language:python" diff --git a/homeassistant/components/cync/manifest.json b/homeassistant/components/cync/manifest.json index b61a3165a1db2..99088ad457689 100644 --- a/homeassistant/components/cync/manifest.json +++ b/homeassistant/components/cync/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_push", "quality_scale": "bronze", - "requirements": ["pycync==0.4.1"] + "requirements": ["pycync==0.4.2"] } diff --git a/homeassistant/components/nextdns/strings.json b/homeassistant/components/nextdns/strings.json index a9bf635673aef..2d12fb066d9a7 100644 --- a/homeassistant/components/nextdns/strings.json +++ b/homeassistant/components/nextdns/strings.json @@ -320,7 +320,7 @@ "name": "Block WhatsApp" }, "block_xboxlive": { - "name": "Block Xbox Live" + "name": "Block Xbox Network" }, "block_youtube": { "name": "Block YouTube" diff --git a/homeassistant/components/nintendo_parental_controls/__init__.py b/homeassistant/components/nintendo_parental_controls/__init__.py index 54ed6e2f28f6f..7c846ccdea44a 100644 --- a/homeassistant/components/nintendo_parental_controls/__init__.py +++ b/homeassistant/components/nintendo_parental_controls/__init__.py @@ -16,7 +16,12 @@ from .const import CONF_SESSION_TOKEN, DOMAIN from .coordinator import NintendoParentalControlsConfigEntry, NintendoUpdateCoordinator -_PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.TIME, Platform.SWITCH] +_PLATFORMS: list[Platform] = [ + Platform.SENSOR, + Platform.TIME, + Platform.SWITCH, + Platform.NUMBER, +] async def async_setup_entry( diff --git a/homeassistant/components/nintendo_parental_controls/number.py b/homeassistant/components/nintendo_parental_controls/number.py new file mode 100644 index 0000000000000..eede275aa7a61 --- /dev/null +++ b/homeassistant/components/nintendo_parental_controls/number.py @@ -0,0 +1,91 @@ +"""Number platform for Nintendo Parental controls.""" + +from __future__ import annotations + +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +from enum import StrEnum +from typing import Any + +from homeassistant.components.number import ( + NumberEntity, + NumberEntityDescription, + NumberMode, +) +from homeassistant.const import UnitOfTime +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .coordinator import NintendoParentalControlsConfigEntry, NintendoUpdateCoordinator +from .entity import Device, NintendoDevice + +PARALLEL_UPDATES = 0 + + +class NintendoParentalNumber(StrEnum): + """Store keys for Nintendo Parental numbers.""" + + TODAY_MAX_SCREENTIME = "today_max_screentime" + + +@dataclass(kw_only=True, frozen=True) +class NintendoParentalControlsNumberEntityDescription(NumberEntityDescription): + """Description for Nintendo Parental number entities.""" + + value_fn: Callable[[Device], int | float | None] + set_native_value_fn: Callable[[Device, float], Coroutine[Any, Any, None]] + + +NUMBER_DESCRIPTIONS: tuple[NintendoParentalControlsNumberEntityDescription, ...] = ( + NintendoParentalControlsNumberEntityDescription( + key=NintendoParentalNumber.TODAY_MAX_SCREENTIME, + translation_key=NintendoParentalNumber.TODAY_MAX_SCREENTIME, + native_min_value=-1, + native_step=1, + native_max_value=360, + native_unit_of_measurement=UnitOfTime.MINUTES, + mode=NumberMode.BOX, + set_native_value_fn=lambda device, value: device.update_max_daily_playtime( + minutes=value + ), + value_fn=lambda device: device.limit_time, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: NintendoParentalControlsConfigEntry, + async_add_devices: AddConfigEntryEntitiesCallback, +) -> None: + """Set up number platform.""" + async_add_devices( + NintendoParentalControlsNumberEntity(entry.runtime_data, device, entity) + for device in entry.runtime_data.api.devices.values() + for entity in NUMBER_DESCRIPTIONS + ) + + +class NintendoParentalControlsNumberEntity(NintendoDevice, NumberEntity): + """Represent a Nintendo Parental number entity.""" + + entity_description: NintendoParentalControlsNumberEntityDescription + + def __init__( + self, + coordinator: NintendoUpdateCoordinator, + device: Device, + description: NintendoParentalControlsNumberEntityDescription, + ) -> None: + """Initialize the time entity.""" + super().__init__(coordinator=coordinator, device=device, key=description.key) + self.entity_description = description + + @property + def native_value(self) -> float | None: + """Return the state of the entity.""" + return self.entity_description.value_fn(self._device) + + async def async_set_native_value(self, value: float) -> None: + """Update entity state.""" + await self.entity_description.set_native_value_fn(self._device, value) diff --git a/homeassistant/components/nintendo_parental_controls/strings.json b/homeassistant/components/nintendo_parental_controls/strings.json index 2617a6464bd55..42801a7f37316 100644 --- a/homeassistant/components/nintendo_parental_controls/strings.json +++ b/homeassistant/components/nintendo_parental_controls/strings.json @@ -48,6 +48,11 @@ "suspend_software": { "name": "Suspend software" } + }, + "number": { + "today_max_screentime": { + "name": "Max screentime today" + } } }, "exceptions": { diff --git a/requirements_all.txt b/requirements_all.txt index 586ab04ac7239..c425b65dd833c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1945,7 +1945,7 @@ pycsspeechtts==1.0.8 # pycups==2.0.4 # homeassistant.components.cync -pycync==0.4.1 +pycync==0.4.2 # homeassistant.components.daikin pydaikin==2.17.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9ac5f8111a053..4bc252c9b1989 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1638,7 +1638,7 @@ pycsspeechtts==1.0.8 # pycups==2.0.4 # homeassistant.components.cync -pycync==0.4.1 +pycync==0.4.2 # homeassistant.components.daikin pydaikin==2.17.1 diff --git a/tests/components/nextdns/snapshots/test_switch.ambr b/tests/components/nextdns/snapshots/test_switch.ambr index d2a78a61127e9..74bf7e27b437f 100644 --- a/tests/components/nextdns/snapshots/test_switch.ambr +++ b/tests/components/nextdns/snapshots/test_switch.ambr @@ -2735,7 +2735,7 @@ 'state': 'on', }) # --- -# name: test_switch[switch.fake_profile_block_xbox_live-entry] +# name: test_switch[switch.fake_profile_block_xbox_network-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2748,7 +2748,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': , - 'entity_id': 'switch.fake_profile_block_xbox_live', + 'entity_id': 'switch.fake_profile_block_xbox_network', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2760,7 +2760,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Block Xbox Live', + 'original_name': 'Block Xbox Network', 'platform': 'nextdns', 'previous_unique_id': None, 'suggested_object_id': None, @@ -2770,13 +2770,13 @@ 'unit_of_measurement': None, }) # --- -# name: test_switch[switch.fake_profile_block_xbox_live-state] +# name: test_switch[switch.fake_profile_block_xbox_network-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Fake Profile Block Xbox Live', + 'friendly_name': 'Fake Profile Block Xbox Network', }), 'context': , - 'entity_id': 'switch.fake_profile_block_xbox_live', + 'entity_id': 'switch.fake_profile_block_xbox_network', 'last_changed': , 'last_reported': , 'last_updated': , diff --git a/tests/components/nintendo_parental_controls/conftest.py b/tests/components/nintendo_parental_controls/conftest.py index a75e29d39e724..eb026ff878756 100644 --- a/tests/components/nintendo_parental_controls/conftest.py +++ b/tests/components/nintendo_parental_controls/conftest.py @@ -36,6 +36,7 @@ def mock_nintendo_device() -> Device: mock.today_playing_time = 110 mock.bedtime_alarm = time(hour=19) mock.set_bedtime_alarm.return_value = None + mock.update_max_daily_playtime.return_value = None mock.forced_termination_mode = True return mock diff --git a/tests/components/nintendo_parental_controls/snapshots/test_number.ambr b/tests/components/nintendo_parental_controls/snapshots/test_number.ambr new file mode 100644 index 0000000000000..6b9b4c01513fa --- /dev/null +++ b/tests/components/nintendo_parental_controls/snapshots/test_number.ambr @@ -0,0 +1,59 @@ +# serializer version: 1 +# name: test_number[number.home_assistant_test_max_screentime_today-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 360, + 'min': -1, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': None, + 'entity_id': 'number.home_assistant_test_max_screentime_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Max screentime today', + 'platform': 'nintendo_parental_controls', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': 'testdevid_today_max_screentime', + 'unit_of_measurement': , + }) +# --- +# name: test_number[number.home_assistant_test_max_screentime_today-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home Assistant Test Max screentime today', + 'max': 360, + 'min': -1, + 'mode': , + 'step': 1, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.home_assistant_test_max_screentime_today', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '120', + }) +# --- diff --git a/tests/components/nintendo_parental_controls/test_number.py b/tests/components/nintendo_parental_controls/test_number.py new file mode 100644 index 0000000000000..2d2cfe7f27951 --- /dev/null +++ b/tests/components/nintendo_parental_controls/test_number.py @@ -0,0 +1,58 @@ +"""Test number platform for Nintendo Parental Controls.""" + +from unittest.mock import AsyncMock, patch + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +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_number( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_nintendo_client: AsyncMock, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test number platform.""" + with patch( + "homeassistant.components.nintendo_parental_controls._PLATFORMS", + [Platform.NUMBER], + ): + await setup_integration(hass, mock_config_entry) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_set_number( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_nintendo_client: AsyncMock, + mock_nintendo_device: AsyncMock, +) -> None: + """Test number platform service.""" + with patch( + "homeassistant.components.nintendo_parental_controls._PLATFORMS", + [Platform.NUMBER], + ): + await setup_integration(hass, mock_config_entry) + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + service_data={ATTR_VALUE: "120"}, + target={ATTR_ENTITY_ID: "number.home_assistant_test_max_screentime_today"}, + blocking=True, + ) + assert len(mock_nintendo_device.update_max_daily_playtime.mock_calls) == 1