Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8e6b9c0
Bump venstarcolortouch to 0.21 (#148152)
mlfreeman2 Jul 4, 2025
bb1e263
Remove cv.SUN_CONDITION_SCHEMA (#148158)
emontnemery Jul 4, 2025
0b2db25
Support translating number selector UoM (#148162)
karwosts Jul 4, 2025
c61cd42
Delete stale icon translation in Husqvarna Automower (#148168)
Thomas55555 Jul 4, 2025
70624f7
Additional icon translation for Husqvarna Automower (#148167)
Thomas55555 Jul 4, 2025
b6b6de2
Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in cambrid…
emontnemery Jul 4, 2025
bfccee1
Wallbox, Improve test setup (#148036)
hesselonline Jul 4, 2025
f5b51c6
Add serial_numbers to device_info of inverters, encharge and enpower …
Wesley-Vos Jul 4, 2025
6e607ff
Add reconfigure flow to eheimdigital (#147930)
autinerd Jul 4, 2025
470baa7
Add zeroconf discovery to philips_js (#147913)
elupus Jul 4, 2025
6a7f495
Fix media selector validation (#147855)
balloob Jul 4, 2025
c0368f2
Add weekdays to time trigger (#147505)
frenck Jul 4, 2025
57c04f3
Bump pysmlight to v0.2.7 (#148101)
tl-sl Jul 4, 2025
22e46d9
Make derivative sensor unavailable when source sensor is unavailable …
karwosts Jul 4, 2025
520d92b
Use brightness stored in hardware device when switching LCN lights (#…
alengwenus Jul 4, 2025
8f24ebe
Remove deprecated support for lock sensors and corresponding actions …
alengwenus Jul 4, 2025
79683c8
Log availability of devices in devolo Home Control (#147091)
Shutgun Jul 4, 2025
be77359
Sonos remove unneeded mocking from test (#147064)
PeteRager Jul 4, 2025
9a5cbe4
Remove obsolete string unit_system in here_travel_time (#146656)
eifinger Jul 4, 2025
ca85ffc
Add Deadlock (SecureMode) support to the Yale Access Bluetooth integr…
michaelp1742 Jul 4, 2025
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
2 changes: 1 addition & 1 deletion homeassistant/components/august/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==8.10.0", "yalexs-ble==2.6.0"]
"requirements": ["yalexs==8.10.0", "yalexs-ble==3.0.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/cambridge_audio/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def state(self) -> MediaPlayerState:
"""Return the state of the device."""
media_state = self.client.play_state.state
if media_state == "NETWORK":
return MediaPlayerState.STANDBY
return MediaPlayerState.OFF
if self.client.state.power:
if media_state == "play":
return MediaPlayerState.PLAYING
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/derivative/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ async def _get_options_dict(handler: SchemaCommonFlowHandler | None) -> dict:
max=6,
mode=selector.NumberSelectorMode.BOX,
unit_of_measurement="decimals",
translation_key="round",
),
),
vol.Required(CONF_TIME_WINDOW): selector.DurationSelector(),
Expand Down
75 changes: 56 additions & 19 deletions homeassistant/components/derivative/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ def __init__(
self._attr_native_value = round(Decimal(0), round_digits)
# List of tuples with (timestamp_start, timestamp_end, derivative)
self._state_list: list[tuple[datetime, datetime, Decimal]] = []
self._last_valid_state_time: tuple[str, datetime] | None = None

self._attr_name = name if name is not None else f"{source_entity} derivative"
self._attr_extra_state_attributes = {ATTR_SOURCE_ID: source_entity}
Expand Down Expand Up @@ -242,6 +243,25 @@ def _prune_state_list(self, current_time: datetime) -> None:
if (current_time - time_end).total_seconds() < self._time_window
]

def _handle_invalid_source_state(self, state: State | None) -> bool:
# Check the source state for unknown/unavailable condition. If unusable, write unknown/unavailable state and return false.
if not state or state.state == STATE_UNAVAILABLE:
self._attr_available = False
self.async_write_ha_state()
return False
if not _is_decimal_state(state.state):
self._attr_available = True
self._write_native_value(None)
return False
self._attr_available = True
return True

def _write_native_value(self, derivative: Decimal | None) -> None:
self._attr_native_value = (
None if derivative is None else round(derivative, self._round_digits)
)
self.async_write_ha_state()

async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
Expand All @@ -255,8 +275,8 @@ async def async_added_to_hass(self) -> None:
Decimal(restored_data.native_value), # type: ignore[arg-type]
self._round_digits,
)
except SyntaxError as err:
_LOGGER.warning("Could not restore last state: %s", err)
except (InvalidOperation, TypeError):
self._attr_native_value = None

def schedule_max_sub_interval_exceeded(source_state: State | None) -> None:
"""Schedule calculation using the source state and max_sub_interval.
Expand All @@ -280,9 +300,7 @@ def _calc_derivative_on_max_sub_interval_exceeded_callback(

self._prune_state_list(now)
derivative = self._calc_derivative_from_state_list(now)
self._attr_native_value = round(derivative, self._round_digits)

self.async_write_ha_state()
self._write_native_value(derivative)

# If derivative is now zero, don't schedule another timeout callback, as it will have no effect
if derivative != 0:
Expand All @@ -299,36 +317,46 @@ def on_state_reported(event: Event[EventStateReportedData]) -> None:
"""Handle constant sensor state."""
self._cancel_max_sub_interval_exceeded_callback()
new_state = event.data["new_state"]
if not self._handle_invalid_source_state(new_state):
return

assert new_state
if self._attr_native_value == Decimal(0):
# If the derivative is zero, and the source sensor hasn't
# changed state, then we know it will still be zero.
return
schedule_max_sub_interval_exceeded(new_state)
new_state = event.data["new_state"]
if new_state is not None:
calc_derivative(
new_state, new_state.state, event.data["old_last_reported"]
)
calc_derivative(new_state, new_state.state, event.data["old_last_reported"])

@callback
def on_state_changed(event: Event[EventStateChangedData]) -> None:
"""Handle changed sensor state."""
self._cancel_max_sub_interval_exceeded_callback()
new_state = event.data["new_state"]
if not self._handle_invalid_source_state(new_state):
return

assert new_state
schedule_max_sub_interval_exceeded(new_state)
old_state = event.data["old_state"]
if new_state is not None and old_state is not None:
if old_state is not None:
calc_derivative(new_state, old_state.state, old_state.last_reported)
else:
# On first state change from none, update availability
self.async_write_ha_state()

def calc_derivative(
new_state: State, old_value: str, old_last_reported: datetime
) -> None:
"""Handle the sensor state changes."""
if old_value in (STATE_UNKNOWN, STATE_UNAVAILABLE) or new_state.state in (
STATE_UNKNOWN,
STATE_UNAVAILABLE,
):
return
if not _is_decimal_state(old_value):
if self._last_valid_state_time:
old_value = self._last_valid_state_time[0]
old_last_reported = self._last_valid_state_time[1]
else:
# Sensor becomes valid for the first time, just keep the restored value
self.async_write_ha_state()
return

if self.native_unit_of_measurement is None:
unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
Expand Down Expand Up @@ -373,6 +401,10 @@ def calc_derivative(
self._state_list.append(
(old_last_reported, new_state.last_reported, new_derivative)
)
self._last_valid_state_time = (
new_state.state,
new_state.last_reported,
)

# If outside of time window just report derivative (is the same as modeling it in the window),
# otherwise take the weighted average with the previous derivatives
Expand All @@ -382,11 +414,16 @@ def calc_derivative(
derivative = self._calc_derivative_from_state_list(
new_state.last_reported
)
self._attr_native_value = round(derivative, self._round_digits)
self.async_write_ha_state()
self._write_native_value(derivative)

source_state = self.hass.states.get(self._sensor_source_id)
if source_state is None or source_state.state in [
STATE_UNAVAILABLE,
STATE_UNKNOWN,
]:
self._attr_available = False

if self._max_sub_interval is not None:
source_state = self.hass.states.get(self._sensor_source_id)
schedule_max_sub_interval_exceeded(source_state)

@callback
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/derivative/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
"h": "Hours",
"d": "Days"
}
},
"round": {
"unit_of_measurement": {
"decimals": "decimals"
}
}
}
}
17 changes: 16 additions & 1 deletion homeassistant/components/devolo_home_control/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,22 @@ def _generic_message(self, message: tuple) -> None:
self._value = message[1]
elif len(message) == 3 and message[2] == "status":
# Maybe the API wants to tell us, that the device went on- or offline.
self._attr_available = self._device_instance.is_online()
state = self._device_instance.is_online()
if state != self.available and not state:
_LOGGER.info(
"Device %s is unavailable",
self._device_instance.settings_property[
"general_device_settings"
].name,
)
if state != self.available and state:
_LOGGER.info(
"Device %s is back online",
self._device_instance.settings_property[
"general_device_settings"
].name,
)
self._attr_available = state
elif message[1] == "del" and self.platform.config_entry:
device_registry = dr.async_get(self.hass)
device = device_registry.async_get_device(
Expand Down
56 changes: 55 additions & 1 deletion homeassistant/components/eheimdigital/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
from eheimdigital.hub import EheimDigitalHub
import voluptuous as vol

from homeassistant.config_entries import SOURCE_USER, ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
SOURCE_USER,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_HOST
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand Down Expand Up @@ -126,3 +131,52 @@ async def async_step_user(
data_schema=CONFIG_SCHEMA,
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the config entry."""
if user_input is None:
return self.async_show_form(
step_id=SOURCE_RECONFIGURE, data_schema=CONFIG_SCHEMA
)

self._async_abort_entries_match(user_input)
errors: dict[str, str] = {}
hub = EheimDigitalHub(
host=user_input[CONF_HOST],
session=async_get_clientsession(self.hass),
loop=self.hass.loop,
main_device_added_event=self.main_device_added_event,
)

try:
await hub.connect()

async with asyncio.timeout(2):
# This event gets triggered when the first message is received from
# the device, it contains the data necessary to create the main device.
# This removes the race condition where the main device is accessed
# before the response from the device is parsed.
await self.main_device_added_event.wait()
if TYPE_CHECKING:
# At this point the main device is always set
assert isinstance(hub.main, EheimDigitalDevice)
await self.async_set_unique_id(hub.main.mac_address)
await hub.close()
except (ClientError, TimeoutError):
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
errors["base"] = "unknown"
LOGGER.exception("Unknown exception occurred")
else:
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=user_input,
)
return self.async_show_form(
step_id=SOURCE_RECONFIGURE,
data_schema=CONFIG_SCHEMA,
errors=errors,
)
2 changes: 1 addition & 1 deletion homeassistant/components/eheimdigital/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: todo
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues: todo
stale-devices: done

Expand Down
12 changes: 11 additions & 1 deletion homeassistant/components/eheimdigital/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
"discovery_confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "[%key:component::eheimdigital::config::step::user::data_description::host%]"
}
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
Expand All @@ -15,7 +23,9 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]"
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "The identifier does not match the previous identifier"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/enphase_envoy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def __init__(
name=f"Encharge {serial_number}",
sw_version=str(encharge_inventory[self._serial_number].firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=serial_number,
)

@property
Expand Down Expand Up @@ -158,6 +159,7 @@ def __init__(
name=f"Enpower {enpower.serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=enpower.serial_number,
)

@property
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/enphase_envoy/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def __init__(
name=f"Enpower {self._serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=self._serial_number,
)
else:
# If no enpower device assign numbers to Envoy itself
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/enphase_envoy/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ def __init__(
name=f"Enpower {self._serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=self._serial_number,
)
else:
# If no enpower device assign selects to Envoy itself
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/enphase_envoy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,7 @@ def __init__(
manufacturer="Enphase",
model="Inverter",
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=serial_number,
)

@property
Expand Down Expand Up @@ -1356,6 +1357,7 @@ def __init__(
name=f"Encharge {serial_number}",
sw_version=str(encharge_inventory[self._serial_number].firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=serial_number,
)


Expand Down Expand Up @@ -1420,6 +1422,7 @@ def __init__(
name=f"Enpower {enpower_data.serial_number}",
sw_version=str(enpower_data.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=enpower_data.serial_number,
)

@property
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/enphase_envoy/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def __init__(
name=f"Enpower {self._serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=self._serial_number,
)

@property
Expand Down Expand Up @@ -235,6 +236,7 @@ def __init__(
name=f"Enpower {self._serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
serial_number=self._serial_number,
)
else:
# If no enpower device assign switches to Envoy itself
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/here_travel_time/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@
"init": {
"data": {
"traffic_mode": "Traffic mode",
"route_mode": "Route mode",
"unit_system": "Unit system"
"route_mode": "Route mode"
}
},
"time_menu": {
Expand Down
Loading
Loading