hass-core/homeassistant/components/iotty/switch.py
Paolo Burgio c1c5cff993
Add integration for iotty Smart Home (#103073)
* Initial import 0.0.2

* Fixes to URL, and removed commits

* Initial import 0.0.2

* Fixes to URL, and removed commits

* Added first test for iotty

* First release

* Reviewers request #1
- Removed clutter
- Added support for new naming convention for IottySmartSwitch entity

* Removed commmented code

* Some modifications

* Modified REST EP for iotty CloudApi

* Initial import 0.0.2

* Fixes to URL, and removed commits

* Added first test for iotty

* First release

* Rebased and resolved conflicts

* Reviewers request #1
- Removed clutter
- Added support for new naming convention for IottySmartSwitch entity

* Removed commmented code

* Some modifications

* Modified REST EP for iotty CloudApi

* Removed empty entries in manifest.json

* Added test_config_flow

* Fix as requested by @edenhaus

* Added test_init

* Removed comments, added one assert

* Added TEST_CONFIG_FLOW

* Added test for STORE_ENTITY

* Increased code coverage

* Full coverage for api.py

* Added tests for switch component

* Converted INFO logs onto DEBUG logs

* Removed .gitignore from commits

* Modifications to SWITCH.PY

* Initial import 0.0.2

* Fixes to URL, and removed commits

* Added first test for iotty

* First release

* Rebased and resolved conflicts

* Fixed conflicts

* Reviewers request #1
- Removed clutter
- Added support for new naming convention for IottySmartSwitch entity

* Removed commmented code

* Some modifications

* Modified REST EP for iotty CloudApi

* Removed empty entries in manifest.json

* Added test_config_flow

* Some modifications

* Fix as requested by @edenhaus

* Added test_init

* Removed comments, added one assert

* Added TEST_CONFIG_FLOW

* Added test for STORE_ENTITY

* Increased code coverage

* Full coverage for api.py

* Added tests for switch component

* Converted INFO logs onto DEBUG logs

* Removed .gitignore from commits

* Modifications to SWITCH.PY

* Fixed tests for SWITCH

* First working implementation of Coordinator

* Increased code coverage

* Full code coverage

* Missing a line in testing

* Update homeassistant/components/iotty/__init__.py

Co-authored-by: Robert Resch <robert@resch.dev>

* Update homeassistant/components/iotty/__init__.py

Co-authored-by: Robert Resch <robert@resch.dev>

* Modified coordinator as per request by edenhaus

* use coordinator entities for switches

* move platforms to constants

* fix whitespace with ruff-format

* correct iotty entry in application_credentials list

* minor style improvements

* refactor function name

* handle new and deleted devices

* improve code for adding devices after first initialization

* use typed config entry instead of adding known devices to hass.data

* improve iotty entity removal

* test listeners update cycle

* handle iotty as devices and not only as entities

* fix test typing for mock config entry

* test with fewer mocks for an integration test style opposed to the previous unit test style

* remove useless tests and add more integration style tests

* check if device_to_remove is None

* integration style tests for turning switches on and off

* remove redundant coordinator tests

* check device status after issuing command in tests

* remove unused fixtures

* add strict typing for iotty

* additional asserts and named snapshots in tests

* fix mypy issues after enabling strict typing

* upgrade iottycloud version to 0.1.3

* move coordinator to runtime_data

* remove entity name

* fix typing issues

* coding style fixes

* improve tests coding style and assertion targets

* test edge cases when apis are not working

* improve tests comments and assertions

---------

Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Shapour Nemati <shapour.nemati@iotty.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: shapournemati-iotty <130070037+shapournemati-iotty@users.noreply.github.com>
2024-07-19 12:10:39 +02:00

165 lines
5.4 KiB
Python

"""Implement a iotty Light Switch Device."""
from __future__ import annotations
import logging
from typing import Any, cast
from iottycloud.device import Device
from iottycloud.lightswitch import LightSwitch
from iottycloud.verbs import LS_DEVICE_TYPE_UID
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import IottyConfigEntry
from .api import IottyProxy
from .const import DOMAIN
from .coordinator import IottyDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: IottyConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Activate the iotty LightSwitch component."""
_LOGGER.debug("Setup SWITCH entry id is %s", config_entry.entry_id)
coordinator = config_entry.runtime_data.coordinator
entities = [
IottyLightSwitch(
coordinator=coordinator, iotty_cloud=coordinator.iotty, iotty_device=d
)
for d in coordinator.data.devices
if d.device_type == LS_DEVICE_TYPE_UID
if (isinstance(d, LightSwitch))
]
_LOGGER.debug("Found %d LightSwitches", len(entities))
async_add_entities(entities)
known_devices: set = config_entry.runtime_data.known_devices
for known_device in coordinator.data.devices:
if known_device.device_type == LS_DEVICE_TYPE_UID:
known_devices.add(known_device)
@callback
def async_update_data() -> None:
"""Handle updated data from the API endpoint."""
if not coordinator.last_update_success:
return None
devices = coordinator.data.devices
entities = []
known_devices: set = config_entry.runtime_data.known_devices
# Add entities for devices which we've not yet seen
for device in devices:
if (
any(d.device_id == device.device_id for d in known_devices)
or device.device_type != LS_DEVICE_TYPE_UID
):
continue
iotty_entity = IottyLightSwitch(
coordinator=coordinator,
iotty_cloud=coordinator.iotty,
iotty_device=LightSwitch(
device.device_id,
device.serial_number,
device.device_type,
device.device_name,
),
)
entities.extend([iotty_entity])
known_devices.add(device)
async_add_entities(entities)
# Add a subscriber to the coordinator to discover new devices
coordinator.async_add_listener(async_update_data)
class IottyLightSwitch(SwitchEntity, CoordinatorEntity[IottyDataUpdateCoordinator]):
"""Haas entity class for iotty LightSwitch."""
_attr_has_entity_name = True
_attr_name = None
_attr_entity_category = None
_attr_device_class = SwitchDeviceClass.SWITCH
_iotty_cloud: IottyProxy
_iotty_device: LightSwitch
def __init__(
self,
coordinator: IottyDataUpdateCoordinator,
iotty_cloud: IottyProxy,
iotty_device: LightSwitch,
) -> None:
"""Initialize the LightSwitch device."""
super().__init__(coordinator=coordinator)
_LOGGER.debug(
"Creating new SWITCH (%s) %s",
iotty_device.device_type,
iotty_device.device_id,
)
self._iotty_cloud = iotty_cloud
self._iotty_device = iotty_device
self._attr_unique_id = iotty_device.device_id
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
identifiers={(DOMAIN, cast(str, self._attr_unique_id))},
name=self._iotty_device.name,
manufacturer="iotty",
)
@property
def is_on(self) -> bool:
"""Return true if the LightSwitch is on."""
_LOGGER.debug(
"Retrieve device status for %s ? %s",
self._iotty_device.device_id,
self._iotty_device.is_on,
)
return self._iotty_device.is_on
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the LightSwitch on."""
_LOGGER.debug("[%s] Turning on", self._iotty_device.device_id)
await self._iotty_cloud.command(
self._iotty_device.device_id, self._iotty_device.cmd_turn_on()
)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the LightSwitch off."""
_LOGGER.debug("[%s] Turning off", self._iotty_device.device_id)
await self._iotty_cloud.command(
self._iotty_device.device_id, self._iotty_device.cmd_turn_off()
)
await self.coordinator.async_request_refresh()
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
device: Device = next(
device
for device in self.coordinator.data.devices
if device.device_id == self._iotty_device.device_id
)
if isinstance(device, LightSwitch):
self._iotty_device.is_on = device.is_on
self.async_write_ha_state()