Skip to content
Merged
9 changes: 4 additions & 5 deletions homeassistant/components/fronius/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Final, override
from typing import TYPE_CHECKING, Any, override

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand All @@ -20,6 +20,7 @@
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfReactiveEnergy,
UnitOfReactivePower,
UnitOfTemperature,
)
Expand Down Expand Up @@ -59,8 +60,6 @@

PARALLEL_UPDATES = 0

ENERGY_VOLT_AMPERE_REACTIVE_HOUR: Final = "varh"


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -405,14 +404,14 @@ def _modbus_mppt_descriptions(
),
FroniusSensorEntityDescription(
key="energy_reactive_ac_consumed",
native_unit_of_measurement=ENERGY_VOLT_AMPERE_REACTIVE_HOUR,
native_unit_of_measurement=UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
entity_registry_enabled_default=False,
invalid_when_falsy=True,
),
FroniusSensorEntityDescription(
key="energy_reactive_ac_produced",
native_unit_of_measurement=ENERGY_VOLT_AMPERE_REACTIVE_HOUR,
native_unit_of_measurement=UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR,
state_class=SensorStateClass.TOTAL_INCREASING,
entity_registry_enabled_default=False,
invalid_when_falsy=True,
Expand Down
18 changes: 17 additions & 1 deletion homeassistant/components/hikvision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr

Expand Down Expand Up @@ -142,6 +143,21 @@ def fetch_and_inject_nvr_events() -> None:
# Start the event stream
await hass.async_add_executor_job(camera.start_stream)

async def _async_stop_stream(event: Event) -> None:
await hass.async_add_executor_job(camera.disconnect)

# pyHik's stream thread is non-daemonic and publishes straight into hass, so
# it has to be joined before the event loop closes. Starting it yields, so
# the stop event may already have been fired by the time we get here, and
# listening for it now would never hear it.
if hass.is_stopping:
await hass.async_add_executor_job(camera.disconnect)
raise ConfigEntryNotReady("Home Assistant is stopping")

entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_stream)
)

# Register the main device before platforms that use via_device
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hikvision/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["pyhik"],
"requirements": ["pyHik==0.4.4"]
"requirements": ["pyHik==0.4.5"]
}
2 changes: 2 additions & 0 deletions homeassistant/components/miele/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@
"KM7678": 6,
"KM7697": 6,
"KM7699": 5,
"KM7740": 5,
"KM7878": 6,
"KM7897": 6,
"KM7899": 5,
"KMDA7633": 5,
"KMDA7634": 5,
"KMDA7774": 5,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/reolink/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"iot_class": "local_push",
"loggers": ["reolink_aio"],
"quality_scale": "platinum",
"requirements": ["reolink-aio==0.21.13"]
"requirements": ["reolink-aio==0.21.14"]
}
11 changes: 11 additions & 0 deletions homeassistant/components/sofar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
_IDENTITY_ATTEMPTS = 3


def _async_remove_stale_waiting_time(hass: HomeAssistant, serial: str) -> None:
"""Drop the removed waiting-time entity so it doesn't linger unavailable."""
registry = er.async_get(hass)
entity_id = registry.async_get_entity_id(
SENSOR_DOMAIN, DOMAIN, f"{serial}_waiting_time"
)
if entity_id is not None:
registry.async_remove(entity_id)


async def _async_read_identity(entry: SofarConfigEntry, device: SofarInverter) -> None:
"""Read identity once, retrying a few times against a transient blip."""
for attempt in range(_IDENTITY_ATTEMPTS):
Expand Down Expand Up @@ -77,6 +87,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SofarConfigEntry) -> boo
"""Set up Sofar Inverter Modbus from a config entry."""
serial = entry.unique_id
assert serial is not None
_async_remove_stale_waiting_time(hass, serial)
inverter_type, model = identify(serial)
if not inverter_type:
raise ConfigEntryError(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/sofar/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["sofar-modbus==0.6.0"]
"requirements": ["sofar-modbus==0.7.0"]
}
62 changes: 3 additions & 59 deletions homeassistant/components/sofar/sensor.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""Support for Sofar sensors."""

from collections.abc import Callable, Mapping
from collections.abc import Mapping
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from datetime import date
from enum import IntEnum
from typing import cast, override

from sofar_modbus.modern.device import SofarInverter
from sofar_modbus.modern.enums import SystemState

from homeassistant.components.sensor import (
RestoreSensor,
Expand All @@ -31,27 +30,12 @@
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from homeassistant.util.variance import ignore_variance

# Aliased: a module-level SCAN_INTERVAL would set the platform's poll.
from .const import SCAN_INTERVAL as _POLL_INTERVAL
from .coordinator import SofarConfigEntry, SofarRuntimeData
from .coordinator import SofarConfigEntry
from .entity import SofarEntity, SofarEntityDescription

PARALLEL_UPDATES = 0

# Two polls of slack, so jitter does not republish a steady countdown.
_COUNTDOWN_VARIANCE = timedelta(seconds=_POLL_INTERVAL * 2)


def _deadline_filter() -> Callable[[int], datetime]:
"""Turn remaining seconds into a deadline, holding it steady."""
return ignore_variance(
lambda seconds: dt_util.utcnow() + timedelta(seconds=seconds),
_COUNTDOWN_VARIANCE,
)


async def async_setup_entry(
hass: HomeAssistant,
Expand Down Expand Up @@ -114,8 +98,6 @@ def _sensor_class(
description: SofarSensorDescription,
) -> type[SofarSensor | SofarTotalSensor]:
"""Pick the entity class a description's semantics ask for."""
if description.device_class is SensorDeviceClass.TIMESTAMP:
return SofarCountdownSensor
if description.state_class in (
SensorStateClass.TOTAL,
SensorStateClass.TOTAL_INCREASING,
Expand All @@ -140,37 +122,6 @@ def native_value(self) -> str | int | float | date | None:
return cast(str | int | float | date | None, value)


class SofarCountdownSensor(SofarSensor):
"""Defines a Sofar countdown, published as the moment it runs out."""

# A positive register alone doesn't mean the countdown is active.
_ACTIVE_STATES = (SystemState.WAITING, SystemState.CHECKING)

def __init__(
self,
runtime_data: SofarRuntimeData,
entity_description: SofarSensorDescription,
) -> None:
"""Initialize the entity."""
super().__init__(runtime_data, entity_description)
self._deadline = _deadline_filter()

@property
@override
def native_value(self) -> datetime | None:
component = getattr(self.coordinator.device, self.entity_description.component)
seconds = getattr(component, self.entity_description.key)
if (
not isinstance(seconds, int)
or seconds <= 0
or component.system_state not in self._ACTIVE_STATES
):
# A restart must not land inside the finished countdown's slack.
self._deadline = _deadline_filter()
return None
return self._deadline(seconds)


class SofarTotalSensor(SofarEntity, RestoreSensor):
"""Defines a Sofar cumulative total sensor."""

Expand Down Expand Up @@ -381,13 +332,6 @@ def _part_sensors(
"self_charging",
],
),
SofarSensorDescription(
key="waiting_time",
component="state",
translation_key="waiting_ends",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
SofarSensorDescription(
key="inverter_temperature_1",
component="state",
Expand Down
3 changes: 0 additions & 3 deletions homeassistant/components/sofar/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,6 @@
},
"voltage_phase_l2n": {
"name": "Voltage phase L2N"
},
"waiting_ends": {
"name": "Waiting ends"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/tuya/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@
"name": "Indicator light mode",
"state": {
"none": "[%key:common::state::off%]",
"on": "[%key:common::state::on%]",
"pos": "Indicate inverted switch state",
"relay": "Indicate switch on/off state"
}
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/wiim/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"iot_class": "local_push",
"loggers": ["wiim.sdk", "async_upnp_client"],
"quality_scale": "platinum",
"requirements": ["async-upnp-client==0.48.1", "wiim==0.1.6"],
"requirements": ["async-upnp-client==0.48.1", "wiim==0.1.7"],
"zeroconf": ["_linkplay._tcp.local."]
}
8 changes: 4 additions & 4 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions tests/components/fronius/snapshots/test_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -1823,15 +1823,15 @@
'supported_features': 0,
'translation_key': 'energy_reactive_ac_consumed',
'unique_id': '1234567890-energy_reactive_ac_consumed',
'unit_of_measurement': 'varh',
'unit_of_measurement': <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
})
# ---
# name: test_gen24[sensor.smart_meter_ts_65a_3_reactive_energy_consumed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart Meter TS 65A-3 Reactive energy consumed',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'varh',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.smart_meter_ts_65a_3_reactive_energy_consumed',
Expand Down Expand Up @@ -1877,15 +1877,15 @@
'supported_features': 0,
'translation_key': 'energy_reactive_ac_produced',
'unique_id': '1234567890-energy_reactive_ac_produced',
'unit_of_measurement': 'varh',
'unit_of_measurement': <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
})
# ---
# name: test_gen24[sensor.smart_meter_ts_65a_3_reactive_energy_produced-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart Meter TS 65A-3 Reactive energy produced',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'varh',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.smart_meter_ts_65a_3_reactive_energy_produced',
Expand Down Expand Up @@ -6008,15 +6008,15 @@
'supported_features': 0,
'translation_key': 'energy_reactive_ac_consumed',
'unique_id': '1234567890-energy_reactive_ac_consumed',
'unit_of_measurement': 'varh',
'unit_of_measurement': <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
})
# ---
# name: test_gen24_storage[sensor.smart_meter_ts_65a_3_reactive_energy_consumed-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart Meter TS 65A-3 Reactive energy consumed',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'varh',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.smart_meter_ts_65a_3_reactive_energy_consumed',
Expand Down Expand Up @@ -6062,15 +6062,15 @@
'supported_features': 0,
'translation_key': 'energy_reactive_ac_produced',
'unique_id': '1234567890-energy_reactive_ac_produced',
'unit_of_measurement': 'varh',
'unit_of_measurement': <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
})
# ---
# name: test_gen24_storage[sensor.smart_meter_ts_65a_3_reactive_energy_produced-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Smart Meter TS 65A-3 Reactive energy produced',
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'varh',
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfReactiveEnergy.VOLT_AMPERE_REACTIVE_HOUR: 'varh'>,
}),
'context': <ANY>,
'entity_id': 'sensor.smart_meter_ts_65a_3_reactive_energy_produced',
Expand Down
Loading
Loading