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
6a3bace
Use shorthand attributes in hp_ilo (#163282)
epenet Feb 17, 2026
889467e
Use shorthand attributes in openhardwaremonitor (#163284)
epenet Feb 17, 2026
3b3c081
Use shorthand attributes in sigfox (#163286)
epenet Feb 17, 2026
7168e2d
Use shorthand attributes in repetier (#163291)
epenet Feb 17, 2026
398a622
Remove deprecated starline state attribute (#163289)
epenet Feb 17, 2026
d298eb0
Use shorthand attributes in vasttrafik (#163285)
epenet Feb 17, 2026
1313960
Use shorthand attributes in skybeacon (#163295)
epenet Feb 17, 2026
f775268
Use shorthand attributes in sony_projector (#163293)
epenet Feb 17, 2026
413e297
Use shorthand attributes in tank_utility (#163288)
epenet Feb 17, 2026
0b8312d
Use shorthand attributes in serial (#163287)
epenet Feb 17, 2026
9f551f3
Improve derivative units and auto-device_class (#157369)
karwosts Feb 17, 2026
2fc9ded
Add sensors to onedrive_for_business (#163135)
zweckj Feb 17, 2026
4af60ef
Show progress indicator during backup stage of Core/App update (#162683)
hbludworth Feb 17, 2026
654e132
ADS Light Color Temperature Support (#153913)
christian9712 Feb 17, 2026
58ac3d2
Type fixture in Fritz tests (#163271)
chemelli74 Feb 17, 2026
65cf615
Add Miele dishwasher program code (#163308)
astrandb Feb 17, 2026
551a711
Bump Idasen Desk dependency (#163309)
abmantis Feb 17, 2026
d50d914
Update quality scale of Namecheap DynamicDNS integration to platinum …
tr4nt0r Feb 17, 2026
479cb7f
Allow Gemini CLI and Anti-gravity SKILL discovery (#163194)
allenporter Feb 17, 2026
19f6340
Bump victron-ble-ha-parser to 0.4.10 (#163310)
JamieMagee Feb 17, 2026
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
1 change: 1 addition & 0 deletions .agent/skills
1 change: 1 addition & 0 deletions .gemini/skills
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ homeassistant.components.my.*
homeassistant.components.mysensors.*
homeassistant.components.myuplink.*
homeassistant.components.nam.*
homeassistant.components.namecheapdns.*
homeassistant.components.nasweb.*
homeassistant.components.neato.*
homeassistant.components.nest.*
Expand Down
79 changes: 73 additions & 6 deletions homeassistant/components/ads/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
DEFAULT_MAX_KELVIN,
DEFAULT_MIN_KELVIN,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
filter_supported_color_modes,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
Expand All @@ -24,13 +28,20 @@
from .hub import AdsHub

CONF_ADS_VAR_BRIGHTNESS = "adsvar_brightness"
CONF_ADS_VAR_COLOR_TEMP_KELVIN = "adsvar_color_temp_kelvin"
CONF_MIN_COLOR_TEMP_KELVIN = "min_color_temp_kelvin"
CONF_MAX_COLOR_TEMP_KELVIN = "max_color_temp_kelvin"
STATE_KEY_BRIGHTNESS = "brightness"
STATE_KEY_COLOR_TEMP_KELVIN = "color_temp_kelvin"

DEFAULT_NAME = "ADS Light"
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ADS_VAR): cv.string,
vol.Optional(CONF_ADS_VAR_BRIGHTNESS): cv.string,
vol.Optional(CONF_ADS_VAR_COLOR_TEMP_KELVIN): cv.string,
vol.Optional(CONF_MIN_COLOR_TEMP_KELVIN): cv.positive_int,
vol.Optional(CONF_MAX_COLOR_TEMP_KELVIN): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
Expand All @@ -47,9 +58,24 @@ def setup_platform(

ads_var_enable: str = config[CONF_ADS_VAR]
ads_var_brightness: str | None = config.get(CONF_ADS_VAR_BRIGHTNESS)
ads_var_color_temp_kelvin: str | None = config.get(CONF_ADS_VAR_COLOR_TEMP_KELVIN)
min_color_temp_kelvin: int | None = config.get(CONF_MIN_COLOR_TEMP_KELVIN)
max_color_temp_kelvin: int | None = config.get(CONF_MAX_COLOR_TEMP_KELVIN)
name: str = config[CONF_NAME]

add_entities([AdsLight(ads_hub, ads_var_enable, ads_var_brightness, name)])
add_entities(
[
AdsLight(
ads_hub,
ads_var_enable,
ads_var_brightness,
ads_var_color_temp_kelvin,
min_color_temp_kelvin,
max_color_temp_kelvin,
name,
)
]
)


class AdsLight(AdsEntity, LightEntity):
Expand All @@ -60,18 +86,40 @@ def __init__(
ads_hub: AdsHub,
ads_var_enable: str,
ads_var_brightness: str | None,
ads_var_color_temp_kelvin: str | None,
min_color_temp_kelvin: int | None,
max_color_temp_kelvin: int | None,
name: str,
) -> None:
"""Initialize AdsLight entity."""
super().__init__(ads_hub, name, ads_var_enable)
self._state_dict[STATE_KEY_BRIGHTNESS] = None
self._state_dict[STATE_KEY_COLOR_TEMP_KELVIN] = None
self._ads_var_brightness = ads_var_brightness
self._ads_var_color_temp_kelvin = ads_var_color_temp_kelvin

# Determine supported color modes
color_modes = {ColorMode.ONOFF}
if ads_var_brightness is not None:
self._attr_color_mode = ColorMode.BRIGHTNESS
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
else:
self._attr_color_mode = ColorMode.ONOFF
self._attr_supported_color_modes = {ColorMode.ONOFF}
color_modes.add(ColorMode.BRIGHTNESS)
if ads_var_color_temp_kelvin is not None:
color_modes.add(ColorMode.COLOR_TEMP)

self._attr_supported_color_modes = filter_supported_color_modes(color_modes)
self._attr_color_mode = next(iter(self._attr_supported_color_modes))

# Set color temperature range (static config values take precedence over defaults)
if ads_var_color_temp_kelvin is not None:
self._attr_min_color_temp_kelvin = (
min_color_temp_kelvin
if min_color_temp_kelvin is not None
else DEFAULT_MIN_KELVIN
)
self._attr_max_color_temp_kelvin = (
max_color_temp_kelvin
if max_color_temp_kelvin is not None
else DEFAULT_MAX_KELVIN
)

async def async_added_to_hass(self) -> None:
"""Register device notification."""
Expand All @@ -84,11 +132,23 @@ async def async_added_to_hass(self) -> None:
STATE_KEY_BRIGHTNESS,
)

if self._ads_var_color_temp_kelvin is not None:
await self.async_initialize_device(
self._ads_var_color_temp_kelvin,
pyads.PLCTYPE_UINT,
STATE_KEY_COLOR_TEMP_KELVIN,
)

@property
def brightness(self) -> int | None:
"""Return the brightness of the light (0..255)."""
return self._state_dict[STATE_KEY_BRIGHTNESS]

@property
def color_temp_kelvin(self) -> int | None:
"""Return the color temperature in Kelvin."""
return self._state_dict[STATE_KEY_COLOR_TEMP_KELVIN]

@property
def is_on(self) -> bool:
"""Return True if the entity is on."""
Expand All @@ -97,13 +157,20 @@ def is_on(self) -> bool:
def turn_on(self, **kwargs: Any) -> None:
"""Turn the light on or set a specific dimmer value."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
color_temp = kwargs.get(ATTR_COLOR_TEMP_KELVIN)

self._ads_hub.write_by_name(self._ads_var, True, pyads.PLCTYPE_BOOL)

if self._ads_var_brightness is not None and brightness is not None:
self._ads_hub.write_by_name(
self._ads_var_brightness, brightness, pyads.PLCTYPE_UINT
)

if self._ads_var_color_temp_kelvin is not None and color_temp is not None:
self._ads_hub.write_by_name(
self._ads_var_color_temp_kelvin, color_temp, pyads.PLCTYPE_UINT
)

def turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
self._ads_hub.write_by_name(self._ads_var, False, pyads.PLCTYPE_BOOL)
71 changes: 64 additions & 7 deletions homeassistant/components/derivative/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@

from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
DEVICE_CLASS_UNITS,
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
RestoreSensor,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_UNIT_OF_MEASUREMENT,
CONF_NAME,
CONF_SOURCE,
Expand Down Expand Up @@ -83,6 +86,17 @@
UnitOfTime.DAYS: 24 * 60 * 60,
}

DERIVED_CLASS = {
SensorDeviceClass.ENERGY: SensorDeviceClass.POWER,
SensorDeviceClass.ENERGY_STORAGE: SensorDeviceClass.POWER,
SensorDeviceClass.DATA_SIZE: SensorDeviceClass.DATA_RATE,
SensorDeviceClass.DISTANCE: SensorDeviceClass.SPEED,
SensorDeviceClass.WATER: SensorDeviceClass.VOLUME_FLOW_RATE,
SensorDeviceClass.GAS: SensorDeviceClass.VOLUME_FLOW_RATE,
SensorDeviceClass.VOLUME: SensorDeviceClass.VOLUME_FLOW_RATE,
SensorDeviceClass.VOLUME_STORAGE: SensorDeviceClass.VOLUME_FLOW_RATE,
}

DEFAULT_ROUND = 3
DEFAULT_TIME_WINDOW = 0

Expand Down Expand Up @@ -203,10 +217,11 @@ def __init__(

self._attr_name = name if name is not None else f"{source_entity} derivative"
self._attr_extra_state_attributes = {ATTR_SOURCE_ID: source_entity}
self._unit_template: str | None = None
self._string_unit_prefix: str | None = None
self._string_unit_time: str | None = None
if unit_of_measurement is None:
final_unit_prefix = "" if unit_prefix is None else unit_prefix
self._unit_template = f"{final_unit_prefix}{{}}/{unit_time}"
self._string_unit_prefix = "" if unit_prefix is None else unit_prefix
self._string_unit_time = unit_time
# we postpone the definition of unit_of_measurement to later
self._attr_native_unit_of_measurement = None
else:
Expand All @@ -225,12 +240,40 @@ def __init__(
)

def _derive_and_set_attributes_from_state(self, source_state: State | None) -> None:
if self._unit_template and source_state:
if not source_state:
return

source_class_raw = source_state.attributes.get(ATTR_DEVICE_CLASS)
source_class: SensorDeviceClass | None = None
if isinstance(source_class_raw, str):
try:
source_class = SensorDeviceClass(source_class_raw)
except ValueError:
source_class = None
if self._string_unit_prefix is not None and self._string_unit_time is not None:
original_unit = self._attr_native_unit_of_measurement
source_unit = source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
self._attr_native_unit_of_measurement = self._unit_template.format(
"" if source_unit is None else source_unit
)
if (
(
source_class
in (SensorDeviceClass.ENERGY, SensorDeviceClass.ENERGY_STORAGE)
)
and self._string_unit_time == UnitOfTime.HOURS
and source_unit
and source_unit.endswith("Wh")
):
self._attr_native_unit_of_measurement = (
f"{self._string_unit_prefix}{source_unit[:-1]}"
)

else:
unit_template = (
f"{self._string_unit_prefix}{{}}/{self._string_unit_time}"
)
self._attr_native_unit_of_measurement = unit_template.format(
"" if source_unit is None else source_unit
)

if original_unit != self._attr_native_unit_of_measurement:
_LOGGER.debug(
"%s: Derivative sensor switched UoM from %s to %s, resetting state to 0",
Expand All @@ -241,6 +284,16 @@ def _derive_and_set_attributes_from_state(self, source_state: State | None) -> N
self._state_list = []
self._attr_native_value = round(Decimal(0), self._round_digits)

self._attr_device_class = None
if source_class:
derived_class = DERIVED_CLASS.get(source_class)
if (
derived_class
and self._attr_native_unit_of_measurement
in DEVICE_CLASS_UNITS[derived_class]
):
self._attr_device_class = derived_class

def _calc_derivative_from_state_list(self, current_time: datetime) -> Decimal:
def calculate_weight(start: datetime, end: datetime, now: datetime) -> float:
window_start = now - timedelta(seconds=self._time_window)
Expand Down Expand Up @@ -309,6 +362,10 @@ async def _handle_restore(self) -> None:
except InvalidOperation, TypeError:
self._attr_native_value = None

last_state = await self.async_get_last_state()
if last_state:
self._attr_device_class = last_state.attributes.get(ATTR_DEVICE_CLASS)

async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/hassio/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ async def async_install(
**kwargs: Any,
) -> None:
"""Install an update."""
self._attr_in_progress = True
self.async_write_ha_state()
await update_addon(
self.hass, self._addon_slug, backup, self.title, self.installed_version
)
Expand Down Expand Up @@ -308,6 +310,8 @@ async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
self._attr_in_progress = True
self.async_write_ha_state()
await update_core(self.hass, version, backup)

@callback
Expand Down
32 changes: 3 additions & 29 deletions homeassistant/components/hp_ilo/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ def setup_platform(
devices = []
for monitored_variable in monitored_variables:
new_device = HpIloSensor(
hass=hass,
hp_ilo_data=hp_ilo_data,
sensor_name=f"{config[CONF_NAME]} {monitored_variable[CONF_NAME]}",
sensor_type=monitored_variable[CONF_SENSOR_TYPE],
Expand All @@ -118,46 +117,21 @@ class HpIloSensor(SensorEntity):

def __init__(
self,
hass,
hp_ilo_data,
sensor_type,
sensor_name,
sensor_value_template,
unit_of_measurement,
):
"""Initialize the HP iLO sensor."""
self._hass = hass
self._name = sensor_name
self._unit_of_measurement = unit_of_measurement
self._attr_name = sensor_name
self._attr_native_unit_of_measurement = unit_of_measurement
self._ilo_function = SENSOR_TYPES[sensor_type][1]
self.hp_ilo_data = hp_ilo_data
self._sensor_value_template = sensor_value_template

self._state = None
self._state_attributes = None

_LOGGER.debug("Created HP iLO sensor %r", self)

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def native_unit_of_measurement(self):
"""Return the unit of measurement of the sensor."""
return self._unit_of_measurement

@property
def native_value(self):
"""Return the state of the sensor."""
return self._state

@property
def extra_state_attributes(self):
"""Return the device state attributes."""
return self._state_attributes

def update(self) -> None:
"""Get the latest data from HP iLO and updates the states."""
# Call the API for new data. Each sensor will re-trigger this
Expand All @@ -171,7 +145,7 @@ def update(self) -> None:
ilo_data=ilo_data, parse_result=False
)

self._state = ilo_data
self._attr_native_value = ilo_data


class HpIloData:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/idasen_desk/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "bronze",
"requirements": ["idasen-ha==2.6.3"]
"requirements": ["idasen-ha==2.6.4"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/miele/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ class DishWasherProgramId(MieleEnum, missing_to_none=True):
no_program = 0, -1
intensive = 1, 26, 205
maintenance = 2, 27, 214
eco = 3, 28, 200
eco = 3, 22, 28, 200
automatic = 6, 7, 31, 32, 202
solar_save = 9, 34
gentle = 10, 35, 210
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/namecheapdns/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/namecheapdns",
"integration_type": "service",
"iot_class": "cloud_push",
"quality_scale": "platinum",
"requirements": []
}
Loading
Loading