Add switch platform to IronOS integration (#133691)
* Add switch platform * Add tests * prevent switch bouncing * some changes * icons * update tests * changespull/134173/head
parent
645f2e44b9
commit
adb1fbbbc4
|
@ -30,6 +30,7 @@ PLATFORMS: list[Platform] = [
|
|||
Platform.NUMBER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from pynecil import (
|
||||
CharSetting,
|
||||
CommunicationError,
|
||||
DeviceInfoResponse,
|
||||
IronOSUpdate,
|
||||
|
@ -19,6 +21,7 @@ from pynecil import (
|
|||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
|
@ -147,3 +150,21 @@ class IronOSSettingsCoordinator(IronOSBaseCoordinator[SettingsDataResponse]):
|
|||
_LOGGER.debug("Failed to fetch settings", exc_info=e)
|
||||
|
||||
return self.data or SettingsDataResponse()
|
||||
|
||||
async def write(self, characteristic: CharSetting, value: bool) -> None:
|
||||
"""Write value to the settings characteristic."""
|
||||
|
||||
try:
|
||||
await self.device.write(characteristic, value)
|
||||
except CommunicationError as e:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="submit_setting_failed",
|
||||
) from e
|
||||
|
||||
# prevent switch bouncing while waiting for coordinator to finish refresh
|
||||
self.data.update(
|
||||
cast(SettingsDataResponse, {characteristic.name.lower(): value})
|
||||
)
|
||||
self.async_update_listeners()
|
||||
await self.async_request_refresh()
|
||||
|
|
|
@ -149,6 +149,44 @@
|
|||
"estimated_power": {
|
||||
"default": "mdi:flash"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"animation_loop": {
|
||||
"default": "mdi:play-box",
|
||||
"state": {
|
||||
"on": "mdi:animation-play"
|
||||
}
|
||||
},
|
||||
"calibrate_cjc": {
|
||||
"default": "mdi:tune-vertical"
|
||||
},
|
||||
"cooling_temp_blink": {
|
||||
"default": "mdi:alarm-light-outline",
|
||||
"state": {
|
||||
"off": "mdi:alarm-light-off-outline"
|
||||
}
|
||||
},
|
||||
"display_invert": {
|
||||
"default": "mdi:invert-colors"
|
||||
},
|
||||
"invert_buttons": {
|
||||
"default": "mdi:plus-minus-variant"
|
||||
},
|
||||
"usb_pd_mode": {
|
||||
"default": "mdi:meter-electric-outline"
|
||||
},
|
||||
"idle_screen_details": {
|
||||
"default": "mdi:card-bulleted-outline",
|
||||
"state": {
|
||||
"off": "mdi:card-bulleted-off-outline"
|
||||
}
|
||||
},
|
||||
"solder_screen_details": {
|
||||
"default": "mdi:card-bulleted-outline",
|
||||
"state": {
|
||||
"off": "mdi:card-bulleted-off-outline"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,7 @@ rules:
|
|||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions:
|
||||
status: exempt
|
||||
comment: Integration does not have actions
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
|
|
|
@ -214,6 +214,32 @@
|
|||
"estimated_power": {
|
||||
"name": "Estimated power"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"animation_loop": {
|
||||
"name": "Animation loop"
|
||||
},
|
||||
"cooling_temp_blink": {
|
||||
"name": "Cool down screen flashing"
|
||||
},
|
||||
"idle_screen_details": {
|
||||
"name": "Detailed idle screen"
|
||||
},
|
||||
"solder_screen_details": {
|
||||
"name": "Detailed solder screen"
|
||||
},
|
||||
"invert_buttons": {
|
||||
"name": "Swap +/- buttons"
|
||||
},
|
||||
"display_invert": {
|
||||
"name": "Invert screen"
|
||||
},
|
||||
"calibrate_cjc": {
|
||||
"name": "Calibrate CJC"
|
||||
},
|
||||
"usb_pd_mode": {
|
||||
"name": "Power Delivery 3.1 EPR"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
"""Switch platform for IronOS integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from pynecil import CharSetting, SettingsDataResponse
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import IronOSConfigEntry
|
||||
from .coordinator import IronOSCoordinators
|
||||
from .entity import IronOSBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class IronOSSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""Describes IronOS switch entity."""
|
||||
|
||||
is_on_fn: Callable[[SettingsDataResponse], bool | None]
|
||||
characteristic: CharSetting
|
||||
|
||||
|
||||
class IronOSSwitch(StrEnum):
|
||||
"""Switch controls for IronOS device."""
|
||||
|
||||
ANIMATION_LOOP = "animation_loop"
|
||||
COOLING_TEMP_BLINK = "cooling_temp_blink"
|
||||
IDLE_SCREEN_DETAILS = "idle_screen_details"
|
||||
SOLDER_SCREEN_DETAILS = "solder_screen_details"
|
||||
INVERT_BUTTONS = "invert_buttons"
|
||||
DISPLAY_INVERT = "display_invert"
|
||||
CALIBRATE_CJC = "calibrate_cjc"
|
||||
USB_PD_MODE = "usb_pd_mode"
|
||||
|
||||
|
||||
SWITCH_DESCRIPTIONS: tuple[IronOSSwitchEntityDescription, ...] = (
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.ANIMATION_LOOP,
|
||||
translation_key=IronOSSwitch.ANIMATION_LOOP,
|
||||
characteristic=CharSetting.ANIMATION_LOOP,
|
||||
is_on_fn=lambda x: x.get("animation_loop"),
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.COOLING_TEMP_BLINK,
|
||||
translation_key=IronOSSwitch.COOLING_TEMP_BLINK,
|
||||
characteristic=CharSetting.COOLING_TEMP_BLINK,
|
||||
is_on_fn=lambda x: x.get("cooling_temp_blink"),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.IDLE_SCREEN_DETAILS,
|
||||
translation_key=IronOSSwitch.IDLE_SCREEN_DETAILS,
|
||||
characteristic=CharSetting.IDLE_SCREEN_DETAILS,
|
||||
is_on_fn=lambda x: x.get("idle_screen_details"),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.SOLDER_SCREEN_DETAILS,
|
||||
translation_key=IronOSSwitch.SOLDER_SCREEN_DETAILS,
|
||||
characteristic=CharSetting.SOLDER_SCREEN_DETAILS,
|
||||
is_on_fn=lambda x: x.get("solder_screen_details"),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.INVERT_BUTTONS,
|
||||
translation_key=IronOSSwitch.INVERT_BUTTONS,
|
||||
characteristic=CharSetting.INVERT_BUTTONS,
|
||||
is_on_fn=lambda x: x.get("invert_buttons"),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.DISPLAY_INVERT,
|
||||
translation_key=IronOSSwitch.DISPLAY_INVERT,
|
||||
characteristic=CharSetting.DISPLAY_INVERT,
|
||||
is_on_fn=lambda x: x.get("display_invert"),
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.CALIBRATE_CJC,
|
||||
translation_key=IronOSSwitch.CALIBRATE_CJC,
|
||||
characteristic=CharSetting.CALIBRATE_CJC,
|
||||
is_on_fn=lambda x: x.get("calibrate_cjc"),
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
IronOSSwitchEntityDescription(
|
||||
key=IronOSSwitch.USB_PD_MODE,
|
||||
translation_key=IronOSSwitch.USB_PD_MODE,
|
||||
characteristic=CharSetting.USB_PD_MODE,
|
||||
is_on_fn=lambda x: x.get("usb_pd_mode"),
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: IronOSConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up switches from a config entry."""
|
||||
|
||||
coordinators = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
IronOSSwitchEntity(coordinators, description)
|
||||
for description in SWITCH_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class IronOSSwitchEntity(IronOSBaseEntity, SwitchEntity):
|
||||
"""Representation of a IronOS Switch."""
|
||||
|
||||
entity_description: IronOSSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinators: IronOSCoordinators,
|
||||
entity_description: IronOSSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the switch entity."""
|
||||
super().__init__(coordinators.live_data, entity_description)
|
||||
|
||||
self.settings = coordinators.settings
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the device."""
|
||||
return self.entity_description.is_on_fn(
|
||||
self.settings.data,
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self.settings.write(self.entity_description.characteristic, True)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
await self.settings.write(self.entity_description.characteristic, False)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added to hass."""
|
||||
|
||||
await super().async_added_to_hass()
|
||||
self.async_on_remove(
|
||||
self.settings.async_add_listener(
|
||||
self._handle_coordinator_update, self.entity_description.characteristic
|
||||
)
|
||||
)
|
||||
await self.settings.async_request_refresh()
|
|
@ -183,6 +183,14 @@ def mock_pynecil() -> Generator[AsyncMock]:
|
|||
desc_scroll_speed=ScrollSpeed.FAST,
|
||||
logo_duration=LogoDuration.LOOP,
|
||||
locking_mode=LockingMode.FULL_LOCKING,
|
||||
animation_loop=True,
|
||||
cooling_temp_blink=True,
|
||||
idle_screen_details=True,
|
||||
solder_screen_details=True,
|
||||
invert_buttons=True,
|
||||
display_invert=True,
|
||||
calibrate_cjc=True,
|
||||
usb_pd_mode=True,
|
||||
)
|
||||
client.get_live_data.return_value = LiveDataResponse(
|
||||
live_temp=298,
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
# serializer version: 1
|
||||
# name: test_switch_platform[switch.pinecil_animation_loop-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_animation_loop',
|
||||
'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': 'Animation loop',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.ANIMATION_LOOP: 'animation_loop'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_animation_loop',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_animation_loop-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Animation loop',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_animation_loop',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_calibrate_cjc-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_calibrate_cjc',
|
||||
'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': 'Calibrate CJC',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.CALIBRATE_CJC: 'calibrate_cjc'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_calibrate_cjc',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_calibrate_cjc-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Calibrate CJC',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_calibrate_cjc',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_cool_down_screen_flashing-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_cool_down_screen_flashing',
|
||||
'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': 'Cool down screen flashing',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.COOLING_TEMP_BLINK: 'cooling_temp_blink'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_cooling_temp_blink',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_cool_down_screen_flashing-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Cool down screen flashing',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_cool_down_screen_flashing',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_detailed_idle_screen-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_detailed_idle_screen',
|
||||
'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': 'Detailed idle screen',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.IDLE_SCREEN_DETAILS: 'idle_screen_details'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_idle_screen_details',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_detailed_idle_screen-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Detailed idle screen',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_detailed_idle_screen',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_detailed_solder_screen-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_detailed_solder_screen',
|
||||
'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': 'Detailed solder screen',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.SOLDER_SCREEN_DETAILS: 'solder_screen_details'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_solder_screen_details',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_detailed_solder_screen-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Detailed solder screen',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_detailed_solder_screen',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_invert_screen-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_invert_screen',
|
||||
'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': 'Invert screen',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.DISPLAY_INVERT: 'display_invert'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_display_invert',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_invert_screen-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Invert screen',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_invert_screen',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_power_delivery_3_1_epr-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_power_delivery_3_1_epr',
|
||||
'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': 'Power Delivery 3.1 EPR',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.USB_PD_MODE: 'usb_pd_mode'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_usb_pd_mode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_power_delivery_3_1_epr-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Power Delivery 3.1 EPR',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_power_delivery_3_1_epr',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_swap_buttons-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.pinecil_swap_buttons',
|
||||
'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': 'Swap +/- buttons',
|
||||
'platform': 'iron_os',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <IronOSSwitch.INVERT_BUTTONS: 'invert_buttons'>,
|
||||
'unique_id': 'c0:ff:ee:c0:ff:ee_invert_buttons',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_switch_platform[switch.pinecil_swap_buttons-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Swap +/- buttons',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.pinecil_swap_buttons',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
|
@ -0,0 +1,146 @@
|
|||
"""Tests for the IronOS switch platform."""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pynecil import CharSetting, CommunicationError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
DOMAIN as SWITCH_DOMAIN,
|
||||
SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def switch_only() -> AsyncGenerator[None]:
|
||||
"""Enable only the switch platform."""
|
||||
with patch(
|
||||
"homeassistant.components.iron_os.PLATFORMS",
|
||||
[Platform.SWITCH],
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(
|
||||
"entity_registry_enabled_by_default", "mock_pynecil", "ble_device"
|
||||
)
|
||||
async def test_switch_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test the IronOS switch platform."""
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
freezer.tick(timedelta(seconds=3))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "target"),
|
||||
[
|
||||
("switch.pinecil_animation_loop", CharSetting.ANIMATION_LOOP),
|
||||
("switch.pinecil_calibrate_cjc", CharSetting.CALIBRATE_CJC),
|
||||
("switch.pinecil_cool_down_screen_flashing", CharSetting.COOLING_TEMP_BLINK),
|
||||
("switch.pinecil_detailed_idle_screen", CharSetting.IDLE_SCREEN_DETAILS),
|
||||
("switch.pinecil_detailed_solder_screen", CharSetting.SOLDER_SCREEN_DETAILS),
|
||||
("switch.pinecil_invert_screen", CharSetting.DISPLAY_INVERT),
|
||||
("switch.pinecil_power_delivery_3_1_epr", CharSetting.USB_PD_MODE),
|
||||
("switch.pinecil_swap_buttons", CharSetting.INVERT_BUTTONS),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("service", "value"),
|
||||
[
|
||||
(SERVICE_TOGGLE, False),
|
||||
(SERVICE_TURN_OFF, False),
|
||||
(SERVICE_TURN_ON, True),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "ble_device")
|
||||
async def test_turn_on_off_toggle(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
mock_pynecil: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
service: str,
|
||||
value: bool,
|
||||
entity_id: str,
|
||||
target: CharSetting,
|
||||
) -> None:
|
||||
"""Test the IronOS switch turn on/off, toggle services."""
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
freezer.tick(timedelta(seconds=3))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
service,
|
||||
service_data={ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
assert len(mock_pynecil.write.mock_calls) == 1
|
||||
mock_pynecil.write.assert_called_once_with(target, value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"service",
|
||||
[SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON],
|
||||
)
|
||||
@pytest.mark.usefixtures(
|
||||
"entity_registry_enabled_by_default", "ble_device", "mock_pynecil"
|
||||
)
|
||||
async def test_turn_on_off_toggle_exception(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
mock_pynecil: AsyncMock,
|
||||
service: str,
|
||||
) -> None:
|
||||
"""Test the IronOS switch turn on/off, toggle service exceptions."""
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
mock_pynecil.write.side_effect = CommunicationError
|
||||
|
||||
with pytest.raises(
|
||||
ServiceValidationError,
|
||||
match="Failed to submit setting to device, try again later",
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
service,
|
||||
service_data={ATTR_ENTITY_ID: "switch.pinecil_animation_loop"},
|
||||
blocking=True,
|
||||
)
|
Loading…
Reference in New Issue