Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].

## [0.1.4] - 2025-07-10

### Changed

Fix compatibility with frozen dataclasses for Home Assistant 2024.x

- Refactor entity descriptions to use dataclasses.replace instead of direct attribute assignment
- Prevent FrozenInstanceError on Sensor and BinarySensor entities
- Clean up legacy code and ensure compatibility with latest Home Assistant core

## [0.1.3] - 2023-04-20

### Changed
Expand Down
9 changes: 7 additions & 2 deletions custom_components/mobile_alerts/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from mobilealerts import Gateway, Measurement, MeasurementType, Sensor

from dataclasses import replace

from .base import MobileAlertesBaseCoordinator, MobileAlertesEntity
from .const import BINARY_MEASUREMENT_TYPES, DOMAIN, LAST_RAIN_PERIOD
from .sensor import MobileAlertesSensor
Expand Down Expand Up @@ -76,7 +78,7 @@ def __init__(
"""Initialize the sensor."""
super().__init__()
self._gateway = gateway
description.translation_key = description.key
#description.translation_key = description.key
self.entity_description = description
self._attr_has_entity_name = True
self._attr_device_class = None
Expand Down Expand Up @@ -112,8 +114,11 @@ def __init__(
measurement.name.lower().replace(" ", "_").replace("/", "_")
)

#if description is not None and description.translation_key is None:
# description.translation_key = description.key

if description is not None and description.translation_key is None:
description.translation_key = description.key
description = replace(description, translation_key=description.key)

_LOGGER.debug("translation_key %s", description.translation_key)

Expand Down
113 changes: 16 additions & 97 deletions custom_components/mobile_alerts/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from mobilealerts import Gateway, Measurement, MeasurementError, MeasurementType, Sensor

from dataclasses import replace

from .base import MobileAlertesBaseCoordinator, MobileAlertesEntity
from .const import (
BINARY_MEASUREMENT_TYPES,
Expand Down Expand Up @@ -188,21 +190,27 @@ def __init__(
super().__init__(coordinator, sensor, measurement)
if description is None and measurement is not None:
description = copy.deepcopy(descriptions[measurement.type])
description.name = measurement.name
description.key = (
measurement.name.lower().replace(" ", "_").replace("/", "_")
# Erzeuge neue Instanz mit name und key
description = replace(
description,
name=measurement.name,
key=measurement.name.lower().replace(" ", "_").replace("/", "_"),
)
# Icon ggf. setzen (mehrfaches replace möglich)
if description.device_class == SensorDeviceClass.TEMPERATURE:
if measurement.prefix:
icon = None
if measurement.prefix == "Pool":
description.icon = "mdi:pool-thermometer"
icon = "mdi:pool-thermometer"
else:
description.icon = "mdi:home-thermometer"
icon = "mdi:home-thermometer"
description = replace(description, icon=icon)

if description is not None and description.translation_key is None:
description.translation_key = description.key
# translation_key setzen, falls nötig
if description is not None and getattr(description, "translation_key", None) is None:
description = replace(description, translation_key=description.key)

_LOGGER.debug("translation_key %s", description.translation_key)
_LOGGER.debug("translation_key %s", getattr(description, "translation_key", None))

self.entity_description = description

Expand Down Expand Up @@ -234,95 +242,6 @@ def update_data_from_last_state(self) -> None:
_LOGGER.debug("update_data_from_last_state %s",
self._attr_native_value)

'''
class MobileAlertesRainSensor(MobileAlertesSensor):
"""Representation of a MobileAlertes general rain sensor."""

def __init__(
self,
coordinator: MobileAlertesBaseCoordinator,
sensor: Sensor,
description: SensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, sensor, None, description)
self._value_is_calculated = True
self._attr_should_poll = True
self._last_update: float = 0.0
self._rain_sensor: MobileAlertesSensor | None = None

def _get_rain_sensor(self) -> MobileAlertesSensor | None:
if self._rain_sensor is None:
rain_entity = self._coordinator.get_entity(
self._sensor.measurements[1])
if type(rain_entity) == MobileAlertesSensor:
self._rain_sensor = rain_entity
self._rain_sensor.add_dependent(self)

return self._rain_sensor

def _get_last_rain_value(self) -> float | None:
rain_sensor = self._get_rain_sensor()
if rain_sensor is not None:
curr_value = float(rain_sensor._attr_native_value)
prior_value = rain_sensor.prior_value
rain_sensor.last_update
_LOGGER.debug(
"last_rain curr_value %s prior_value %s self._last_update %s rain_sensor.last_update %s",
curr_value,
prior_value,
datetime.fromtimestamp(self._last_update).isoformat(),
datetime.fromtimestamp(rain_sensor.last_update).isoformat(),
)
if prior_value >= 0 and rain_sensor.last_update > self._last_update:
return curr_value - prior_value
else:
return None
else:
return None

def update_data_from_last_state(self) -> None:
"""Update data from stored last state."""
super().update_data_from_last_state()
_LOGGER.debug("rain update_data_from_last_state")
if self._last_extra_data is not None:
_LOGGER.debug(
"rain update_data_from_last_state self._last_extra_data is not None")
extra_state_attributes = self._last_extra_data.as_dict()
if extra_state_attributes is not None:
last_update_iso = extra_state_attributes.get(
STATE_ATTR_LAST_UPDATED, None)
if last_update_iso is not None:
_LOGGER.debug(
"rain update_data_from_last_state last_update_iso %s", last_update_iso)
self._last_update = datetime.fromisoformat(
last_update_iso).timestamp()
self._last_extra_data = None

@property
def available(self) -> bool:
"""Return if entity is available."""
rain_sensor = self._get_rain_sensor()
return rain_sensor is not None and rain_sensor.available


class MobileAlertesLastRainSensor(MobileAlertesRainSensor):
"""Representation of a MobileAlertes last rain sensor."""

def update_data_from_sensor(self) -> None:
"""Update data from the sensor."""
self._attr_native_value = self._get_last_rain_value()
if self._attr_native_value is None:
self._attr_native_value = 0.0
_LOGGER.debug("rain update_data_from_sensor %s",
self._attr_native_value)
attr: dict[str, Any] = {
STATE_ATTR_LAST_UPDATED:
datetime.fromtimestamp(self._last_update).isoformat()
}
self._attr_extra_state_attributes = attr
'''

class MobileAlertesPeriodRainSensor(MobileAlertesSensor):
"""Representation of a MobileAlertes rain by period sensor."""

Expand Down