Skip to content

Commit

Permalink
Restore compatibility with 2024.1.0 (#335)
Browse files Browse the repository at this point in the history
  • Loading branch information
weltenwort authored Jan 7, 2024
1 parent 783ef87 commit 6244670
Show file tree
Hide file tree
Showing 14 changed files with 2,197 additions and 1,522 deletions.
16 changes: 7 additions & 9 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "mcr.microsoft.com/devcontainers/python:3.10",
"image": "mcr.microsoft.com/devcontainers/python:3.11",
"name": "RCT Power integration development",
"appPort": ["8123:8123"],
"customizations": {
Expand All @@ -9,20 +9,18 @@
"ms-python.python",
"github.vscode-pull-request-github",
"ms-python.vscode-pylance",
"bungcip.better-toml",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"ms-python.flake8",
"ms-python.black-formatter",
"tamasfe.even-better-toml"
],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"python.venvPath": "/home/vscode/.cache/pypoetry/virtualenvs",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[python]": {
"editor.defaultFormatter": "ms-python.python"
"editor.defaultFormatter": "ms-python.black-formatter"
}
}
}
Expand All @@ -36,5 +34,5 @@
"containerEnv": {
"HOME": "/home/vscode"
},
"postCreateCommand": "poetry install"
"postCreateCommand": "poetry install --no-root"
}
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- cron: "0 0 * * *"

env:
DEFAULT_PYTHON: "3.10"
DEFAULT_PYTHON: "3.11"

jobs:
pre-commit:
Expand Down
8 changes: 4 additions & 4 deletions custom_components/rct_power/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .lib.context import RctPowerContext
from .lib.domain_data import get_domain_data
from .lib.entities import all_entity_descriptions
from .lib.entity import EntityUpdatePriority
from .lib.entity import EntityUpdatePriority, resolve_object_infos
from .lib.entry import RctPowerConfigEntryData, RctPowerConfigEntryOptions
from .lib.update_coordinator import RctPowerDataUpdateCoordinator

Expand Down Expand Up @@ -54,7 +54,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
object_info.object_id
for entity_description in all_entity_descriptions
if entity_description.update_priority == EntityUpdatePriority.FREQUENT
for object_info in entity_description.object_infos
for object_info in resolve_object_infos(entity_description)
}
)
frequent_update_coordinator = RctPowerDataUpdateCoordinator(
Expand All @@ -71,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
object_info.object_id
for entity_description in all_entity_descriptions
if entity_description.update_priority == EntityUpdatePriority.INFREQUENT
for object_info in entity_description.object_infos
for object_info in resolve_object_infos(entity_description)
}
)
infrequent_update_coordinator = RctPowerDataUpdateCoordinator(
Expand All @@ -90,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
object_info.object_id
for entity_description in all_entity_descriptions
if entity_description.update_priority == EntityUpdatePriority.STATIC
for object_info in entity_description.object_infos
for object_info in resolve_object_infos(entity_description)
}
)
static_update_coordinator = RctPowerDataUpdateCoordinator(
Expand Down
84 changes: 41 additions & 43 deletions custom_components/rct_power/lib/entity.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional
from datetime import date, datetime
from decimal import Decimal
from functools import cached_property
from typing import Any, Callable, List, Mapping, Optional

from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.typing import StateType, UndefinedType
from rctclient.registry import REGISTRY, ObjectInfo

from .api import (
Expand All @@ -23,6 +26,7 @@

class RctPowerEntity(MultiCoordinatorEntity):
entity_description: "RctPowerEntityDescription"
object_infos: List[ObjectInfo]

def __init__(
self,
Expand All @@ -32,7 +36,10 @@ def __init__(
):
super().__init__(coordinators)
self.config_entry = config_entry
self.entity_description = entity_description
self.entity_description = ( # pyright: ignore [reportIncompatibleVariableOverride]
entity_description
)
self.object_infos = resolve_object_infos(self.entity_description)

def get_api_response_by_id(
self, object_id: int, default: Optional[ApiResponse] = None
Expand Down Expand Up @@ -66,31 +73,23 @@ def get_valid_api_response_value_by_name(
self.get_api_response_by_name(object_name, None), default
)

@property
def object_infos(self):
return self.entity_description.object_infos

@property
def object_ids(self):
return [object_info.object_id for object_info in self.object_infos]

@property
def config_entry_data(self):
return RctPowerConfigEntryData.from_config_entry(self.config_entry)

@property
def unique_id(self):
@cached_property
def unique_id(self) -> str | None:
"""Return a unique ID to use for this entity."""
# this allows for keeping the entity identity stable for existing
# sensors when the algorithm below changes
if uid := self.entity_description.unique_id:
return f"{self.config_entry.entry_id}-{uid}"

object_ids = [str(object_id) for object_id in self.object_ids]
object_ids = [str(object_info.object_id) for object_info in self.object_infos]
return "-".join([self.config_entry.entry_id, *object_ids])

@property
def name(self):
@cached_property
def name(self) -> str | UndefinedType | None:
"""Return the name of the entity."""
entity_name = self.entity_description.name or slugify_entity_name(
self.object_infos[0].name
Expand All @@ -101,30 +100,32 @@ def name(self):
@property
def available(self) -> bool:
return all(
isinstance(self.get_api_response_by_id(object_id), ValidApiResponse)
for object_id in self.object_ids
isinstance(
self.get_api_response_by_id(object_info.object_id), ValidApiResponse
)
for object_info in self.object_infos
)

@property
@cached_property
def unit_of_measurement(self):
if unit_of_measurement := super().unit_of_measurement:
return unit_of_measurement

return self.object_infos[0].unit

@property
def extra_state_attributes(self) -> Dict[str, Any]:
@cached_property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
return {}

@property
@cached_property
def device_info(self):
return self.entity_description.get_device_info(self)


class RctPowerSensorEntity(SensorEntity, RctPowerEntity):
entity_description: "RctPowerSensorEntityDescription"
entity_description: "RctPowerSensorEntityDescription" # pyright: ignore [reportIncompatibleVariableOverride]

@property
@cached_property
def device_class(self):
"""Return the device class of the sensor."""
if device_class := super().device_class:
Expand All @@ -136,14 +137,14 @@ def device_class(self):
return None

@property
def native_value(self):
def native_value(self) -> StateType | date | datetime | Decimal:
values = [
self.get_valid_api_response_value_by_id(object_id, None)
for object_id in self.object_ids
self.get_valid_api_response_value_by_id(object_info.object_id, None)
for object_info in self.object_infos
]
return self.entity_description.get_native_value(self, values)

@property
@cached_property
def native_unit_of_measurement(self):
if native_unit_of_measurement := super().native_unit_of_measurement:
return native_unit_of_measurement
Expand All @@ -155,8 +156,8 @@ class RctPowerFaultSensorEntity(RctPowerSensorEntity):
@property
def fault_bitmasks(self):
return [
self.get_valid_api_response_value_by_id(object_id, 0)
for object_id in self.object_ids
self.get_valid_api_response_value_by_id(object_info.object_id, 0)
for object_info in self.object_infos
]

@property
Expand All @@ -168,7 +169,7 @@ def native_value(self):

return None

@property
@cached_property
def native_unit_of_measurement(self):
return None

Expand All @@ -180,25 +181,17 @@ def extra_state_attributes(self):
}


@dataclass
@dataclass(frozen=True, kw_only=True)
class RctPowerEntityDescription(EntityDescription):
icon: Optional[str] = ICON
object_infos: List[ObjectInfo] = field(init=False)
object_names: List[str] = field(default_factory=list)
icon: Optional[str] = field(default=ICON)
object_names: Optional[List[str]] = None
# to allow for stable enitity identities even if the object ids change
unique_id: Optional[str] = None
update_priority: EntityUpdatePriority = EntityUpdatePriority.FREQUENT
get_device_info: Callable[[RctPowerEntity], Optional[DeviceInfo]] = lambda e: None

def __post_init__(self):
if not self.object_names:
self.object_names = [self.key]
self.object_infos = [
REGISTRY.get_by_name(object_name) for object_name in self.object_names
]


@dataclass
@dataclass(frozen=True, kw_only=True)
class RctPowerSensorEntityDescription(
RctPowerEntityDescription, SensorEntityDescription
):
Expand All @@ -211,6 +204,11 @@ def slugify_entity_name(name: str):
return name.replace(".", "_").replace("[", "_").replace("]", "_").replace("?", "_")


def resolve_object_infos(entity_description: RctPowerEntityDescription):
object_names = entity_description.object_names or [entity_description.key]
return [REGISTRY.get_by_name(object_name) for object_name in object_names]


known_faults = [
"TRAP occurred",
"RTC can't be configured",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/rct_power/lib/update_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
)

def get_latest_response(self, object_id: int):
if self.data is not None:
if self.data is not None: # pyright: ignore [reportUnnecessaryComparison]
return self.data.get(object_id)

def get_valid_value_or(self, object_id: int, default_value: ApiResponseValue):
Expand Down
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "RCT Power",
"hacs": "1.6.0",
"homeassistant": "2021.12.0"
"homeassistant": "2024.1.2"
}
Loading

0 comments on commit 6244670

Please sign in to comment.