Skip to content

Commit

Permalink
Merge pull request #131 from h4de5/feature/normalize-fan-swing-mode-n…
Browse files Browse the repository at this point in the history
…ames

Feature/normalize fan swing mode names
  • Loading branch information
h4de5 authored Jun 18, 2023
2 parents e7691e0 + deeecd2 commit 0ff75f4
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 39 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Linters

on:
pull_request:
push: [main]
push:
branches: [main]

jobs:
lint:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ repos:
- mccabe==0.6.1
files: ^(custom_components)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/adrienverge/yamllint.git
Expand Down
3 changes: 2 additions & 1 deletion custom_components/toshiba_ac/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async def async_set_fan_mode(self, fan_mode):
else:
if not self.is_on:
await self._device.set_ac_status(ToshibaAcStatus.ON)

fan_mode = fan_mode.title().replace("_", " ")
feature_list_id = get_feature_by_name(list(ToshibaAcFanMode), fan_mode)
if feature_list_id is not None:
await self._device.set_ac_fan_mode(feature_list_id)
Expand All @@ -192,6 +192,7 @@ def fan_mode(self) -> str | None:

async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
swing_mode = swing_mode.title().replace("_", " ")
feature_list_id = get_feature_by_name(list(ToshibaAcSwingMode), swing_mode)
if feature_list_id is not None:
await self._device.set_ac_swing_mode(feature_list_id)
Expand Down
9 changes: 7 additions & 2 deletions custom_components/toshiba_ac/entity.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Base classes for Toshiba AC entities."""

from __future__ import annotations

import logging

from toshiba_ac.device import ToshibaAcDevice

from homeassistant.helpers.entity import DeviceInfo, Entity

from .const import DOMAIN
Expand Down Expand Up @@ -39,17 +41,20 @@ def available(self) -> bool:


class ToshibaAcStateEntity(ToshibaAcEntity):
"""Base class for entities that subscribe to the device's state_changed callback"""
"""Base class for entities that subscribe to the device's state_changed callback."""

async def async_added_to_hass(self) -> None:
"""Subscribe to the device's state_changed callback."""
self._device.on_state_changed_callback.add(self._state_changed)

async def async_will_remove_from_hass(self) -> None:
"""Call when device is removed from HA."""
self._device.on_state_changed_callback.remove(self._state_changed)

def update_attrs(self) -> None:
"""Called when the Toshiba AC device state changes"""
"""Call when the Toshiba AC device state changes."""

def _state_changed(self, _device: ToshibaAcDevice) -> None:
"""Call when the Toshiba AC device state changes."""
self.update_attrs()
self.async_write_ha_state()
14 changes: 8 additions & 6 deletions custom_components/toshiba_ac/entity_description.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Contains the base classes of entity descriptions"""
"""Contain the base classes of entity descriptions."""

from enum import Enum
from logging import getLogger
Expand All @@ -11,13 +11,15 @@


class ToshibaAcEnumEntityDescriptionMixin(Generic[TEnum]):
"""Mixes in async_set_attr and get_attr helpers to dynamically set enum values"""
"""Mix in async_set_attr and get_attr helpers to dynamically set enum values."""

ac_attr_name: str
ac_attr_setter: str

async def async_set_attr(self, device: ToshibaAcDevice, value: TEnum | None) -> None:
"""Sets the provided option enum value"""
async def async_set_attr(
self, device: ToshibaAcDevice, value: TEnum | None
) -> None:
"""Set the provided option enum value."""
if not self.ac_attr_setter and not self.ac_attr_name:
return
if value is None:
Expand All @@ -27,13 +29,13 @@ async def async_set_attr(self, device: ToshibaAcDevice, value: TEnum | None) ->
await getattr(device, setter)(value)

def get_device_attr(self, device: ToshibaAcDevice) -> TEnum | None:
"""Returns the current option enum value"""
"""Return the current option enum value."""
if self.ac_attr_name:
return getattr(device, self.ac_attr_name)
return None

def get_features_attr(self, features: ToshibaAcFeatures) -> list[TEnum]:
"""Returns the supported enum values"""
"""Return the supported enum values."""
if self.ac_attr_name:
return getattr(features, self.ac_attr_name)
return []
2 changes: 1 addition & 1 deletion custom_components/toshiba_ac/feature_list.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Helpers for converting enums to strings"""
"""Include helpers for converting enums to strings."""

from enum import Enum
import logging
Expand Down
40 changes: 27 additions & 13 deletions custom_components/toshiba_ac/select.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
"""Select platform for Toshiba AC integration."""
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum

import logging
from typing import Generic, Sequence, TypeVar

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from toshiba_ac.device import (
ToshibaAcDevice,
ToshibaAcFeatures,
ToshibaAcMeritA,
ToshibaAcMeritB,
ToshibaAcFeatures,
)

from homeassistant.components.select import SelectEntity, SelectEntityDescription

from .const import DOMAIN
from .entity import ToshibaAcStateEntity
from .entity_description import ToshibaAcEnumEntityDescriptionMixin
Expand All @@ -25,25 +26,29 @@

@dataclass(kw_only=True)
class ToshibaAcSelectDescription(SelectEntityDescription):
"""Describes a Toshiba AC select entity type"""
"""Describe a Toshiba AC select entity type."""

icon_mapping: dict[str, str] = field(default_factory=dict)

async def async_select_option_name(self, device: ToshibaAcDevice, name: str):
"""Selects the provided option"""
"""Select the provided option."""
pass

def current_option_name(self, _device: ToshibaAcDevice) -> str | None:
"""Returns the currently selected option"""
"""Return the currently selected option."""
return None

def get_option_names(self, _features: ToshibaAcFeatures) -> list[str]:
"""Returns the available options for given Toshiba AC device features"""
"""Return the available options for given Toshiba AC device features."""
return []

def is_supported(self, _features: ToshibaAcFeatures):
"""Return True if the switch is available. Called to determine
if the switch should be created in the first place, and then
later to determine if it should be available based on the current AC mode"""
"""
Return True if the select is available.
Called to determine if the select should be created in the first place, and then
later to determine if it should be available based on the current AC mode.
"""
return False


Expand All @@ -56,20 +61,22 @@ class ToshibaAcEnumSelectDescription(
ToshibaAcEnumEntityDescriptionMixin[TEnum],
Generic[TEnum],
):
"""Describes a Toshiba AC select entity type based on an enum"""
"""Describe a Toshiba AC select entity type based on an enum."""

ac_attr_name: str = ""
ac_attr_setter: str = ""
off_value: TEnum | None = None
values: list[TEnum] = field(default_factory=list)

async def async_select_option_name(self, device: ToshibaAcDevice, name: str):
"""Select a given option."""
for value in self.values:
if value.name.lower() == name:
await self.async_set_attr(device, value)
return

def current_option_name(self, device: ToshibaAcDevice) -> str | None:
"""Return the currently selected option."""
value = self.get_device_attr(device)
if value and value in self.values:
return value.name.lower()
Expand All @@ -78,14 +85,16 @@ def current_option_name(self, device: ToshibaAcDevice) -> str | None:
return None

def get_option_names(self, features: ToshibaAcFeatures):
"""Return the available options for given Toshiba AC device features."""
return [v.name.lower() for v in self.get_option_values(features)]

def get_option_values(self, features: ToshibaAcFeatures):
"""Returns all the supported option enum values"""
"""Return all the supported option enum values."""
values = self.get_features_attr(features)
return [v for v in self.values if v in values]

def is_supported(self, features: ToshibaAcFeatures):
"""Return True if the switch is available."""
options = self.get_option_values(features)
if self.off_value is not None and self.off_value in options:
options.remove(self.off_value)
Expand Down Expand Up @@ -148,23 +157,26 @@ async def async_setup_entry(hass, config_entry, async_add_devices):


class ToshibaAcSelectEntity(ToshibaAcStateEntity, SelectEntity):
"""Provides a select based on a ToshibaAcSelectDescription"""
"""Provide a select based on a ToshibaAcSelectDescription."""

entity_description: ToshibaAcSelectDescription
_attr_has_entity_name = True

def __init__(
self, device: ToshibaAcDevice, entity_description: ToshibaAcSelectDescription
):
"""Initialize the ToshibaAcSelectEntity."""
super().__init__(device)
self._attr_unique_id = f"{device.ac_unique_id}_{entity_description.key}"
self.entity_description = entity_description
self.update_attrs()

async def async_select_option(self, option: str) -> None:
"""Select a given option."""
await self.entity_description.async_select_option_name(self._device, option)

def update_attrs(self):
"""Update the entity's attributes."""
features = self._device.supported.for_ac_mode(self._device.ac_mode)
self._attr_options = self.entity_description.get_option_names(features)
self._attr_current_option = self.entity_description.current_option_name(
Expand All @@ -173,11 +185,13 @@ def update_attrs(self):

@property
def available(self) -> bool:
"""Return True if the entity is available."""
features = self._device.supported.for_ac_mode(self._device.ac_mode)
return super().available and self.entity_description.is_supported(features)

@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
if not self.current_option:
return self.entity_description.icon
mapped = self.entity_description.icon_mapping.get(self.current_option)
Expand Down
41 changes: 27 additions & 14 deletions custom_components/toshiba_ac/switch.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
"""Switch platform for the Toshiba AC integration."""
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum

import logging
from typing import Any, Generic, Sequence, TypeVar

from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from toshiba_ac.device import (
ToshibaAcAirPureIon,
ToshibaAcDevice,
Expand All @@ -19,6 +14,12 @@
ToshibaAcStatus,
)

from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)

from .const import DOMAIN
from .entity import ToshibaAcStateEntity
from .entity_description import ToshibaAcEnumEntityDescriptionMixin
Expand All @@ -28,25 +29,28 @@

@dataclass(kw_only=True)
class ToshibaAcSwitchDescription(SwitchEntityDescription):
"""Describes a Toshiba AC switch entity type"""
"""Describe a Toshiba AC switch entity type."""

device_class = SwitchDeviceClass.SWITCH
off_icon: str | None = None

async def async_turn_on(self, _device: ToshibaAcDevice):
"""Turns the switch on"""
"""Turn the switch on."""

async def async_turn_off(self, _device: ToshibaAcDevice):
"""Turns the switch off"""
"""Turn the switch off."""

def is_on(self, _device: ToshibaAcDevice):
"""Return True if the switch is on"""
"""Return True if the switch is on."""
return False

def is_supported(self, _features: ToshibaAcFeatures):
"""Return True if the switch is available. Called to determine
if the switch should be created in the first place, and then
later to determine if it should be available based on the current AC mode"""
"""
Return True if the switch is available.
Called to determine if the switch should be created in the first place, and then
later to determine if it should be available based on the current AC mode.
"""
return False


Expand All @@ -59,23 +63,27 @@ class ToshibaAcEnumSwitchDescription(
ToshibaAcEnumEntityDescriptionMixin[TEnum],
Generic[TEnum],
):
"""Describes a Toshiba AC switch that is controlled using an enum flag"""
"""Describe a Toshiba AC switch that is controlled using an enum flag."""

ac_on_value: TEnum | None = None
ac_off_value: TEnum | None = None
ac_attr_name: str = ""
ac_attr_setter: str = ""

async def async_turn_off(self, device: ToshibaAcDevice):
"""Turn the switch off."""
await self.async_set_attr(device, self.ac_off_value)

async def async_turn_on(self, device: ToshibaAcDevice):
"""Turn the switch on."""
await self.async_set_attr(device, self.ac_on_value)

def is_on(self, device: ToshibaAcDevice):
"""Return True if the switch is on."""
return self.get_device_attr(device) == self.ac_on_value

def is_supported(self, features: ToshibaAcFeatures):
"""Return True if the switch is available."""
return self.ac_on_value in self.get_features_attr(features)


Expand Down Expand Up @@ -152,6 +160,7 @@ def __init__(

@property
def available(self):
"""Return True if entity is available."""
return (
super().available
and self._device.ac_status == ToshibaAcStatus.ON
Expand All @@ -162,16 +171,20 @@ def available(self):

@property
def icon(self):
"""Return the icon."""
if self.entity_description.off_icon and not self.is_on:
return self.entity_description.off_icon
return super().icon

@property
def is_on(self) -> bool | None:
"""Return True if the switch is on."""
return self.entity_description.is_on(self._device)

async def async_turn_off(self, **kwargs: Any):
"""Turn the switch off."""
await self.entity_description.async_turn_off(self._device)

async def async_turn_on(self, **kwargs: Any):
"""Turn the switch on."""
await self.entity_description.async_turn_on(self._device)

0 comments on commit 0ff75f4

Please sign in to comment.