Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
294a3e5
add teltonika integration (#157539)
karlbeecken Feb 18, 2026
f0e22cc
Reconfiguration flow Watts Vision + and platinium level (#163346)
theobld-ww Feb 18, 2026
cabf3b7
Set last_reported timestamp for Satel Integra entities (#163352)
Tommatheussen Feb 18, 2026
8de1e3d
Change lunatone config entry title to only include the URL (#162855)
MoonDevLT Feb 18, 2026
728de32
Add missing data_description for reauth_confirm token in Splunk (#163…
Bre77 Feb 18, 2026
b26483e
Fix remote calendar event handling of events within the same update p…
allenporter Feb 18, 2026
8094cfc
Add coordinator to Proxmox (#161146)
erwindouna Feb 18, 2026
151e075
Do not send empty snapshots in analytics (#163351)
arturpragacz Feb 18, 2026
937b486
Proxmox polish strings & tests (#163361)
erwindouna Feb 18, 2026
7a41ce1
Add clean_area action to vacuum (#149315)
arturpragacz Feb 18, 2026
680f7fa
Fix MySensors battery sensors attachment to correct gateway (#151167)
JochenFriedrich Feb 18, 2026
4dcfd5f
Reconfiguration support for webhook flow helper (#151729)
tr4nt0r Feb 18, 2026
d1a1183
OAuth2.0 token request error handling (#153167)
erwindouna Feb 18, 2026
c5e2614
Add diagnostics to onedrive for business (#163336)
zweckj Feb 18, 2026
60d4b05
Fix Control4 HVAC action mapping for multi-stage and idle states (#16…
davidrecordon Feb 18, 2026
5631170
Fix spelling of reconfigure in strings (#163370)
tr4nt0r Feb 18, 2026
dc553f2
Ecovacs controller pattern optimization (#160895)
erwindouna Feb 18, 2026
bfea04b
Mark onedrive for business as platinum (#163376)
zweckj Feb 18, 2026
68792f0
Fix XMLParsedAsHTMLWarning in scrape integration (#159433)
vaind Feb 18, 2026
3b6a5b2
Fix uses of `reconfigure` and `re-configure` in ZHA (#163377)
puddly Feb 18, 2026
2197891
Mark siren/stt/todo method type hints as mandatory (#163265)
epenet Feb 18, 2026
9c71aea
Refactor extra_state_attributes in xiaomi_aqara (#163299)
epenet Feb 18, 2026
e3c98dc
Use shorthand attributes in wirelesstag (#161214)
epenet Feb 18, 2026
eb7d973
Ignore None keys in meteo_france extra state attributes (#163297)
epenet Feb 18, 2026
0170d56
Add fixture to SmartThings (#163374)
joostlek Feb 18, 2026
428aa31
Update asyncsleepiq to 1.7.0 (#163214)
rhcp011235 Feb 18, 2026
30314ec
Fix 0°C when the temperature is unavailable in HKO API (#162052)
MisterCommand Feb 18, 2026
15cb102
Bump pySmartThings to 3.5.3 (#163375)
joostlek Feb 18, 2026
e9039ce
Add HDFury number platform (#163381)
glenndehaan Feb 18, 2026
8df41dc
Bump Kaleidescape integration dependancy to v1.1.1 (#163384)
SteveEasley Feb 18, 2026
0a734b7
Improve Transmission error handling (#163388)
andrew-codechimp Feb 18, 2026
a9b64a1
Redact Thread dataset and format them as readable dicts in log messag…
agners Feb 18, 2026
558a49c
Fix data update in WebhookFlowHandler to preserve existing entry data…
tr4nt0r Feb 18, 2026
9f2677d
Add Mastodon mute/unmute actions (#163366)
andrew-codechimp Feb 18, 2026
4777972
Replace "the" with "a" in `vacuum` action descriptions (#163409)
NoRi2909 Feb 18, 2026
3e31fbf
Deduplicate strings in Teltonika integration (#163410)
karlbeecken Feb 18, 2026
f7628b8
Add ConfigEntryAuthFailed to Proxmox (#163407)
erwindouna Feb 18, 2026
f74fdd7
Add integration_type service to smhi (#163400)
joostlek Feb 18, 2026
ab9b133
Add integration_type hub to smarttub (#163399)
joostlek Feb 18, 2026
f59f14f
Add integration_type device to sensorpro (#163386)
joostlek Feb 18, 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
2 changes: 2 additions & 0 deletions CODEOWNERS

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

4 changes: 4 additions & 0 deletions homeassistant/components/analytics/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ async def send_snapshot(self, _: datetime | None = None) -> None:

payload = await _async_snapshot_payload(self._hass)

if not payload:
LOGGER.info("Skipping snapshot submission, no data to send")
return

headers = {
"Content-Type": "application/json",
"User-Agent": f"home-assistant/{HA_VERSION}",
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/control4/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,12 @@

HA_TO_C4_HVAC_MODE = {v: k for k, v in C4_TO_HA_HVAC_MODE.items()}

# Map the five known Control4 HVAC states to Home Assistant HVAC actions
# Map Control4 HVAC states to Home Assistant HVAC actions
C4_TO_HA_HVAC_ACTION = {
"off": HVACAction.OFF,
"heat": HVACAction.HEATING,
"cool": HVACAction.COOLING,
"idle": HVACAction.IDLE,
"dry": HVACAction.DRYING,
"fan": HVACAction.FAN,
}
Expand Down Expand Up @@ -292,8 +293,14 @@ def hvac_action(self) -> HVACAction | None:
c4_state = data.get(CONTROL4_HVAC_STATE)
if c4_state is None:
return None
# Convert state to lowercase for mapping
action = C4_TO_HA_HVAC_ACTION.get(str(c4_state).lower())
# Substring match for multi-stage systems that report
# e.g. "Stage 1 Heat", "Stage 2 Cool"
if action is None:
if "heat" in str(c4_state).lower():
action = HVACAction.HEATING
elif "cool" in str(c4_state).lower():
action = HVACAction.COOLING
if action is None:
_LOGGER.debug("Unknown HVAC state received from Control4: %s", c4_state)
return action
Expand Down
44 changes: 37 additions & 7 deletions homeassistant/components/demo/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@

from homeassistant.components.vacuum import (
ATTR_CLEANED_AREA,
Segment,
StateVacuumEntity,
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import event
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import DOMAIN

SUPPORT_MINIMAL_SERVICES = VacuumEntityFeature.TURN_ON | VacuumEntityFeature.TURN_OFF

SUPPORT_BASIC_SERVICES = (
Expand Down Expand Up @@ -45,9 +49,17 @@
| VacuumEntityFeature.LOCATE
| VacuumEntityFeature.MAP
| VacuumEntityFeature.CLEAN_SPOT
| VacuumEntityFeature.CLEAN_AREA
)

FAN_SPEEDS = ["min", "medium", "high", "max"]
DEMO_SEGMENTS = [
Segment(id="living_room", name="Living room"),
Segment(id="kitchen", name="Kitchen"),
Segment(id="bedroom_1", name="Master bedroom", group="Bedrooms"),
Segment(id="bedroom_2", name="Guest bedroom", group="Bedrooms"),
Segment(id="bathroom", name="Bathroom"),
]
DEMO_VACUUM_COMPLETE = "Demo vacuum 0 ground floor"
DEMO_VACUUM_MOST = "Demo vacuum 1 first floor"
DEMO_VACUUM_BASIC = "Demo vacuum 2 second floor"
Expand All @@ -63,25 +75,33 @@ async def async_setup_entry(
"""Set up the Demo config entry."""
async_add_entities(
[
StateDemoVacuum(DEMO_VACUUM_COMPLETE, SUPPORT_ALL_SERVICES),
StateDemoVacuum(DEMO_VACUUM_MOST, SUPPORT_MOST_SERVICES),
StateDemoVacuum(DEMO_VACUUM_BASIC, SUPPORT_BASIC_SERVICES),
StateDemoVacuum(DEMO_VACUUM_MINIMAL, SUPPORT_MINIMAL_SERVICES),
StateDemoVacuum(DEMO_VACUUM_NONE, VacuumEntityFeature(0)),
StateDemoVacuum("vacuum_1", DEMO_VACUUM_COMPLETE, SUPPORT_ALL_SERVICES),
StateDemoVacuum("vacuum_2", DEMO_VACUUM_MOST, SUPPORT_MOST_SERVICES),
StateDemoVacuum("vacuum_3", DEMO_VACUUM_BASIC, SUPPORT_BASIC_SERVICES),
StateDemoVacuum("vacuum_4", DEMO_VACUUM_MINIMAL, SUPPORT_MINIMAL_SERVICES),
StateDemoVacuum("vacuum_5", DEMO_VACUUM_NONE, VacuumEntityFeature(0)),
]
)


class StateDemoVacuum(StateVacuumEntity):
"""Representation of a demo vacuum supporting states."""

_attr_has_entity_name = True
_attr_name = None
_attr_should_poll = False
_attr_translation_key = "model_s"

def __init__(self, name: str, supported_features: VacuumEntityFeature) -> None:
def __init__(
self, unique_id: str, name: str, supported_features: VacuumEntityFeature
) -> None:
"""Initialize the vacuum."""
self._attr_name = name
self._attr_unique_id = unique_id
self._attr_supported_features = supported_features
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=name,
)
self._attr_activity = VacuumActivity.DOCKED
self._fan_speed = FAN_SPEEDS[1]
self._cleaned_area: float = 0
Expand Down Expand Up @@ -163,6 +183,16 @@ async def async_send_command(
self._attr_activity = VacuumActivity.IDLE
self.async_write_ha_state()

async def async_get_segments(self) -> list[Segment]:
"""Get the list of segments."""
return DEMO_SEGMENTS

async def async_clean_segments(self, segment_ids: list[str], **kwargs: Any) -> None:
"""Clean the specified segments."""
self._attr_activity = VacuumActivity.CLEANING
self._cleaned_area += len(segment_ids) * 0.7
self.async_write_ha_state()

def __set_state_to_dock(self, _: datetime) -> None:
self._attr_activity = VacuumActivity.DOCKED
self.schedule_update_ha_state()
5 changes: 5 additions & 0 deletions homeassistant/components/dialogflow/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
"config": {
"abort": {
"cloud_not_connected": "[%key:common::config_flow::abort::cloud_not_connected%]",
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the [webhook service of Dialogflow]({dialogflow_url}) and update the webhook with following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to set up the [webhook service of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"reconfigure": {
"description": "Are you sure you want to reconfigure Dialogflow?",
"title": "Reconfigure Dialogflow webhook"
},
"user": {
"description": "Are you sure you want to set up Dialogflow?",
"title": "Set up the Dialogflow webhook"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/duckdns/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"data_description": {
"access_token": "[%key:component::duckdns::config::step::user::data_description::access_token%]"
},
"title": "Re-configure {name}"
"title": "Reconfigure {name}"
},
"user": {
"data": {
Expand Down
20 changes: 16 additions & 4 deletions homeassistant/components/ecovacs/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import asyncio
from collections.abc import Mapping
from functools import partial
import logging
Expand Down Expand Up @@ -80,11 +81,22 @@ async def initialize(self) -> None:
try:
devices = await self._api_client.get_devices()
credentials = await self._authenticator.authenticate()
for device_info in devices.mqtt:
device = Device(device_info, self._authenticator)

if devices.mqtt:
mqtt = await self._get_mqtt_client()
await device.initialize(mqtt)
self._devices.append(device)
mqtt_devices = [
Device(info, self._authenticator) for info in devices.mqtt
]
async with asyncio.TaskGroup() as tg:

async def _init(device: Device) -> None:
"""Initialize MQTT device."""
await device.initialize(mqtt)
self._devices.append(device)

for device in mqtt_devices:
tg.create_task(_init(device))

for device_config in devices.xmpp:
bot = VacBot(
credentials.user_id,
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/geofency/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
"config": {
"abort": {
"cloud_not_connected": "[%key:common::config_flow::abort::cloud_not_connected%]",
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the webhook feature in Geofency and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to set up the webhook feature in Geofency.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"reconfigure": {
"description": "Are you sure you want to reconfigure the Geofency webhook?",
"title": "Reconfigure Geofency webhook"
},
"user": {
"description": "Are you sure you want to set up the Geofency webhook?",
"title": "Set up the Geofency webhook"
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/gpslogger/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
"config": {
"abort": {
"cloud_not_connected": "[%key:common::config_flow::abort::cloud_not_connected%]",
"reconfigure_successful": "**Reconfiguration was successful**\n\nGo to the webhook feature in GPSLogger and update the webhook with the following settings:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details.",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"webhook_not_internet_accessible": "[%key:common::config_flow::abort::webhook_not_internet_accessible%]"
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to set up the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"reconfigure": {
"description": "Are you sure you want to reconfigure the GPSLogger webhook?",
"title": "Reconfigure GPSLogger webhook"
},
"user": {
"description": "Are you sure you want to set up the GPSLogger webhook?",
"title": "Set up the GPSLogger webhook"
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/hdfury/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

PLATFORMS = [
Platform.BUTTON,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/hdfury/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
"default": "mdi:connection"
}
},
"number": {
"oled_fade": {
"default": "mdi:cellphone-information"
},
"reboot_timer": {
"default": "mdi:timer-refresh"
}
},
"select": {
"opmode": {
"default": "mdi:cogs"
Expand Down
101 changes: 101 additions & 0 deletions homeassistant/components/hdfury/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Number platform for HDFury Integration."""

from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from hdfury import HDFuryAPI, HDFuryError

from homeassistant.components.number import (
NumberDeviceClass,
NumberEntity,
NumberEntityDescription,
NumberMode,
)
from homeassistant.const import EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import DOMAIN
from .coordinator import HDFuryConfigEntry
from .entity import HDFuryEntity

PARALLEL_UPDATES = 1


@dataclass(kw_only=True, frozen=True)
class HDFuryNumberEntityDescription(NumberEntityDescription):
"""Description for HDFury number entities."""

set_value_fn: Callable[[HDFuryAPI, str], Awaitable[None]]


NUMBERS: tuple[HDFuryNumberEntityDescription, ...] = (
HDFuryNumberEntityDescription(
key="oledfade",
translation_key="oled_fade",
mode=NumberMode.BOX,
native_min_value=1,
native_max_value=100,
native_step=1,
device_class=NumberDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_oled_fade(value),
),
HDFuryNumberEntityDescription(
key="reboottimer",
translation_key="reboot_timer",
mode=NumberMode.BOX,
native_min_value=0,
native_max_value=100,
native_step=1,
device_class=NumberDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_reboot_timer(value),
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: HDFuryConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up numbers using the platform schema."""

coordinator = entry.runtime_data

async_add_entities(
HDFuryNumber(coordinator, description)
for description in NUMBERS
if description.key in coordinator.data.config
)


class HDFuryNumber(HDFuryEntity, NumberEntity):
"""Base HDFury Number Class."""

entity_description: HDFuryNumberEntityDescription

@property
def native_value(self) -> float:
"""Return the current number value."""

return float(self.coordinator.data.config[self.entity_description.key])

async def async_set_native_value(self, value: float) -> None:
"""Set Number Value Event."""

try:
await self.entity_description.set_value_fn(
self.coordinator.client, str(int(value))
)
except HDFuryError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
) from error

await self.coordinator.async_request_refresh()
8 changes: 8 additions & 0 deletions homeassistant/components/hdfury/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
"name": "Issue hotplug"
}
},
"number": {
"oled_fade": {
"name": "OLED fade timer"
},
"reboot_timer": {
"name": "Restart timer"
}
},
"select": {
"opmode": {
"name": "Operation mode",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hko/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def _convert_current(self, data: dict[str, Any]) -> dict[str, Any]:
for item in data[API_TEMPERATURE][API_DATA]
if item[API_PLACE] == self.location
),
0,
None,
),
}

Expand Down
Loading
Loading