-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
Add new Nintendo Parental Controls integration #145343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
pantherale0
wants to merge
31
commits into
home-assistant:dev
Choose a base branch
from
pantherale0:integration/nintento_parental
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+681
−0
Draft
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
7bc7a93
Add nintendo parental integration
pantherale0 792697b
Add nintendo tests
pantherale0 7b019a3
Fix quality scale
pantherale0 2eea933
Update pynintendoparental to version 0.6.7 and refactor imports in Ni…
pantherale0 1c04ab8
Update quality scale
pantherale0 61be9c1
Update homeassistant/components/nintendo_parental/entity.py
pantherale0 3d951bc
Update homeassistant/components/nintendo_parental/entity.py
pantherale0 ba43046
Update homeassistant/components/nintendo_parental/sensor.py
pantherale0 09c078b
Update homeassistant/components/nintendo_parental/sensor.py
pantherale0 44d8502
Update homeassistant/components/nintendo_parental/sensor.py
pantherale0 775dbbe
Update homeassistant/components/nintendo_parental/config_flow.py
pantherale0 43eaf4e
Update homeassistant/components/nintendo_parental/config_flow.py
pantherale0 4ba909e
Update homeassistant/components/nintendo_parental/strings.json
pantherale0 b6a4303
fixes
pantherale0 0214970
Add extra sensor and change entity naming method
pantherale0 c605ae5
update config flow tests
pantherale0 8bc6708
Update homeassistant/components/nintendo_parental/strings.json
pantherale0 4834b5c
Update homeassistant/components/nintendo_parental/strings.json
pantherale0 e988963
Update homeassistant/components/nintendo_parental/config_flow.py
pantherale0 9941506
Update typing for tests
pantherale0 c1a1bc4
Suggestions from review
pantherale0 a89f07f
Fix exceptions structure
pantherale0 77f0e87
Assert timedelta in coordinator
pantherale0 910dd95
Add missing MagicMock typing for mock_request_handler
pantherale0 53edd06
Update homeassistant/components/nintendo_parental/config_flow.py
pantherale0 c3a16f4
Update homeassistant/components/nintendo_parental/entity.py
pantherale0 00883f9
improve error handling, update dependencies, and enhance entity manag…
pantherale0 56ae08b
Mark 'inject-websession' as done in quality_scale.yaml
pantherale0 443e293
Bump pynintendoparental for API v2
pantherale0 93961c5
Bump pynintendoparental
pantherale0 bce8d4a
Update homeassistant/components/nintendo_parental/entity.py
pantherale0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next
Next commit
Add nintendo parental integration
- Loading branch information
commit 7bc7a93c2a74cd65736ab6ea010c5a095f43d7d5
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""The Nintendo Switch Parental Controls integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pynintendoparental import Authenticator | ||
from pynintendoparental.exceptions import ( | ||
InvalidOAuthConfigurationException, | ||
InvalidSessionTokenException, | ||
) | ||
|
||
from homeassistant.config_entries import ConfigEntryAuthFailed, ConfigEntryError | ||
from homeassistant.const import Platform | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .const import CONF_SESSION_TOKEN | ||
from .coordinator import NintendoParentalConfigEntry, NintendoUpdateCoordinator | ||
|
||
_PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, entry: NintendoParentalConfigEntry | ||
) -> bool: | ||
"""Set up Nintendo Switch Parental Controls from a config entry.""" | ||
try: | ||
nintendo_auth = await Authenticator.complete_login( | ||
auth=None, | ||
response_token=entry.data[CONF_SESSION_TOKEN], | ||
is_session_token=True, | ||
) | ||
except InvalidSessionTokenException as err: | ||
raise ConfigEntryAuthFailed(err) from err | ||
except InvalidOAuthConfigurationException as err: | ||
raise ConfigEntryError(err) from err | ||
entry.runtime_data = coordinator = NintendoUpdateCoordinator( | ||
hass=hass, authenticator=nintendo_auth, config_entry=entry | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
await coordinator.async_config_entry_first_refresh() | ||
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry( | ||
hass: HomeAssistant, entry: NintendoParentalConfigEntry | ||
) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what ap
8000
pears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Config flow for the Nintendo Switch Parental Controls integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Any | ||
|
||
from pynintendoparental import Authenticator | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import CONF_API_TOKEN | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers import selector | ||
|
||
from .const import CONF_SESSION_TOKEN, CONF_UPDATE_INTERVAL, DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
STEP_CONFIGURE_DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_UPDATE_INTERVAL, default=60): selector.NumberSelector( | ||
selector.NumberSelectorConfig( | ||
min=30, | ||
step=1, | ||
unit_of_measurement="s", | ||
mode=selector.NumberSelectorMode.BOX, | ||
) | ||
) | ||
} | ||
) | ||
|
||
|
||
class NintendoConfigFlow(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Nintendo Switch Parental Controls.""" | ||
|
||
def __init__(self) -> None: | ||
"""Initialize a new config flow instance.""" | ||
self.auth = None | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle the initial step.""" | ||
if not user_input: | ||
self.auth = Authenticator.generate_login() | ||
return await self.async_step_nintendo_website_auth() | ||
return self.async_show_form(step_id="user") | ||
|
||
async def async_step_nintendo_website_auth( | ||
self, user_input=None | ||
) -> ConfigFlowResult: | ||
"""Begin authentication flow with Nintendo website.""" | ||
if user_input is not None: | ||
await self.auth.complete_login(self.auth, user_input[CONF_API_TOKEN], False) | ||
return await () | ||
return self.async_show_form( | ||
step_id="nintendo_website_auth", | ||
description_placeholders={"link": self.auth.login_url}, | ||
data_schema=vol.Schema({vol.Required(CONF_API_TOKEN): str}), | ||
) | ||
|
||
async def async_step_configure(self, user_input=None) -> ConfigFlowResult: | ||
"""Configure the update interval and create config entry.""" | ||
if user_input is not None: | ||
assert self.auth.account_id | ||
return self.async_create_entry( | ||
title=self.auth.account_id, | ||
data={ | ||
CONF_SESSION_TOKEN: self.auth.get_session_token, | ||
CONF_UPDATE_INTERVAL: user_input[CONF_UPDATE_INTERVAL], | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
) | ||
return self.async_show_form( | ||
step_id="configure", data_schema=STEP_CONFIGURE_DATA_SCHEMA | ||
) | ||
|
||
|
||
class CannotConnect(HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
|
||
|
||
class InvalidAuth(HomeAssistantError): | ||
"""Error to indicate there is invalid auth.""" | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Constants for the Nintendo Switch Parental Controls integration.""" | ||
|
||
DOMAIN = "nintendo_parental" | ||
CONF_UPDATE_INTERVAL = "update_interval" | ||
CONF_SESSION_TOKEN = "session_token" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""Nintendo Parental Controls data coordinator.""" | ||
|
||
from __future__ import annotations | ||
|
||
import asyncio | ||
import contextlib | ||
from datetime import timedelta | ||
import logging | ||
|
||
from pynintendoparental import Authenticator, NintendoParental | ||
from pynintendoparental.exceptions import ( | ||
InvalidOAuthConfigurationException, | ||
InvalidSessionTokenException, | ||
) | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryAuthFailed | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
||
from .const import CONF_UPDATE_INTERVAL, DOMAIN | ||
|
||
type NintendoParentalConfigEntry = ConfigEntry[NintendoUpdateCoordinator] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class NintendoUpdateCoordinator(DataUpdateCoordinator): | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Nintendo data update coordinator.""" | ||
|
||
def __init__( | ||
self, | ||
hass: HomeAssistant, | ||
authenticator: Authenticator, | ||
config_entry: NintendoParentalConfigEntry, | ||
) -> None: | ||
"""Initialize update coordinator.""" | ||
super().__init__( | ||
hass=hass, | ||
logger=_LOGGER, | ||
name=DOMAIN, | ||
update_interval=timedelta(config_entry.data.get(CONF_UPDATE_INTERVAL), 60), | ||
config_entry=config_entry, | ||
) | ||
self.api = NintendoParental( | ||
authenticator, hass.config.time_zone, hass.config.language | ||
) | ||
|
||
async def _async_update_data(self): | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Update data from Nintendo's API.""" | ||
try: | ||
with contextlib.suppress(InvalidSessionTokenException): | ||
async with asyncio.timeout(self.update_interval.total_seconds() - 5): | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return await self.api.update() | ||
except InvalidOAuthConfigurationException as err: | ||
raise ConfigEntryAuthFailed(err) from err | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
except TimeoutError as err: | ||
raise UpdateFailed(err) from err | ||
return False | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""Base entity definition for Nintendo Parental.""" | ||
|
||
from __future__ import annotations | ||
|
||
from pynintendoparental import Device | ||
|
||
import homeassistant.helpers.device_registry as dr | ||
from homeassistant.helpers.entity import DeviceInfo | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import DOMAIN | ||
from .coordinator import NintendoUpdateCoordinator | ||
|
||
|
||
class NintendoDevice(CoordinatorEntity): | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Represent a Nintendo Switch.""" | ||
|
||
def __init__( | ||
self, coordinator: NintendoUpdateCoordinator, device: Device, entity_id: str | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> None: | ||
"""Initialize.""" | ||
super().__init__(coordinator=coordinator) | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._device = device | ||
self._attr_unique_id = f"{device.device_id}_{entity_id}" | ||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, device.device_id)}, | ||
manufacturer="Nintendo", | ||
name=device.name, | ||
entry_type=dr.DeviceEntryType.SERVICE, | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sw_version=device.extra["device"]["firmwareVersion"]["displayedVersion"], | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
async def async_added_to_hass(self) -> None: | ||
"""When entity is loaded.""" | ||
await super().async_added_to_hass() | ||
self._device.add_device_callback(self.async_write_ha_state) | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"domain": "nintendo_parental", | ||
"name": "Nintendo Switch Parental Controls", | ||
"codeowners": [ | ||
"@pantherale0" | ||
], | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/nintendo_parental", | ||
"iot_class": "cloud_polling", | ||
"loggers": [ | ||
"pynintendoparental" | ||
], | ||
"quality_scale": "bronze", | ||
"requirements": [ | ||
"pynintendoparental==0.6.6" | ||
] | ||
} |
60 changes: 60 additions & 0 deletions
60
homeassistant/components/nintendo_parental/quality_scale.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
rules: | ||
# Bronze | ||
action-setup: todo | ||
appropriate-polling: todo | ||
brands: todo | ||
common-modules: todo | ||
config-flow-test-coverage: todo | ||
config-flow: todo | ||
dependency-transparency: todo | ||
docs-actions: todo | ||
docs-high-level-description: todo | ||
docs-installation-instructions: todo | ||
docs-removal-instructions: todo | ||
entity-event-setup: todo | ||
entity-unique-id: todo | ||
has-entity-name: todo | ||
runtime-data: todo | ||
test-before-configure: todo | ||
test-before-setup: todo | ||
unique-config-entry: todo | ||
|
||
# Silver | ||
action-exceptions: todo | ||
config-entry-unloading: todo | ||
docs-configuration-parameters: todo | ||
docs-installation-parameters: todo | ||
entity-unavailable: todo | ||
integration-owner: todo | ||
log-when-unavailable: todo | ||
parallel-updates: todo | ||
reauthentication-flow: todo | ||
test-coverage: todo | ||
|
||
# Gold | ||
devices: todo | ||
diagnostics: todo | ||
discovery-update-info: todo | ||
discovery: todo | ||
docs-data-update: todo | ||
docs-examples: todo | ||
docs-known-limitations: todo | ||
docs-supported-devices: todo | ||
docs-supported-functions: todo | ||
docs-troubleshooting: todo | ||
docs-use-cases: todo | ||
dynamic-devices: todo | ||
entity-category: todo | ||
entity-device-class: todo | ||
entity-disabled-by-default: todo | ||
entity-translations: todo | ||
exception-translations: todo | ||
icon-translations: todo | ||
reconfiguration-flow: todo | ||
repair-issues: todo | ||
stale-devices: todo | ||
|
||
# Platinum | ||
async-dependency: todo | ||
inject-websession: todo | ||
strict-typing: todo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
"""Sensor platform for Nintendo Parental.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Callable, Mapping | ||
from typing import Any | ||
|
||
from homeassistant.components.sensor import ( | ||
SensorDeviceClass, | ||
SensorEntity, | ||
SensorEntityDescription, | ||
SensorStateClass, | ||
) | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback | ||
|
||
from .coordinator import NintendoParentalConfigEntry, NintendoUpdateCoordinator | ||
from .entity import Device, NintendoDevice | ||
|
||
|
||
class NintendoParentalSensorEntityDescription(SensorEntityDescription): | ||
"""Description for Nintendo Parental sensor entities.""" | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
value_fn: Callable[[Device], int | float | None] | ||
state_attributes: str | ||
|
||
|
||
SENSOR_DESCRIPTIONS: tuple[NintendoParentalSensorEntityDescription, ...] = ( | ||
NintendoParentalSensorEntityDescription( | ||
key="playing_time", | ||
native_unit_of_measurement="min", | ||
state_attributes="daily_summaries", | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
device_class=SensorDeviceClass.DURATION, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
value_fn=lambda device: device.today_playing_time, | ||
) | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
entry: NintendoParentalConfigEntry, | ||
async_add_devices: AddConfigEntryEntitiesCallback, | ||
) -> None: | ||
"""Set up the sensor platform.""" | ||
if entry.runtime_data.api.devices is not None: | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
D911 for device in entry.runtime_data.api.devices.values(): | ||
async_add_devices( | ||
NintendoParentalSensor(entry.runtime_data, device, sensor) | ||
for sensor in SENSOR_DESCRIPTIONS | ||
) | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class NintendoParentalSensor(NintendoDevice, SensorEntity): | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Represent a single sensor.""" | ||
|
||
entity_description: NintendoParentalSensorEntityDescription | ||
|
||
def __init__( | ||
self, | ||
coordinator: NintendoUpdateCoordinator, | ||
device: Device, | ||
description: NintendoParentalSensorEntityDescription, | ||
) -> None: | ||
"""Initialize the sensor.""" | ||
super().__init__( | ||
coordinator=coordinator, device=device, entity_id=description.key | ||
) | ||
self.entity_description = description | ||
self._attr_translation_placeholders = {"DEV_NAME": device.name} | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@property | ||
def native_value(self) -> int | float | None: | ||
"""Return the native value.""" | ||
return self.entity_description.value_fn(self._device) | ||
|
||
@property | ||
def extra_state_attributes(self) -> Mapping[str, Any] | None: | ||
"""Return extra state attributes.""" | ||
if self.entity_description.state_attributes == "daily_summaries": | ||
return {"daily": self._device.daily_summaries[0:5]} | ||
return None | ||
pantherale0 marked this conversation as resolved.
Show resolved
Hide resolved
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.