Skip to content

[pull] dev from home-assistant:dev #999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 9, 2025
Merged
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
2 changes: 2 additions & 0 deletions CODEOWNERS

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

2 changes: 1 addition & 1 deletion homeassistant/components/bosch_alarm/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"name": "Battery missing"
},
"panel_fault_ac_fail": {
"name": "AC Failure"
"name": "AC failure"
},
"panel_fault_parameter_crc_fail_in_pif": {
"name": "CRC failure in panel configuration"
Expand Down
43 changes: 37 additions & 6 deletions homeassistant/components/bsblan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

import dataclasses

from bsblan import BSBLAN, BSBLANConfig, Device, Info, StaticState
from bsblan import (
BSBLAN,
BSBLANAuthError,
BSBLANConfig,
BSBLANConnectionError,
BSBLANError,
Device,
Info,
StaticState,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand All @@ -13,9 +22,14 @@
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryError,
ConfigEntryNotReady,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CONF_PASSKEY
from .const import CONF_PASSKEY, DOMAIN
from .coordinator import BSBLanUpdateCoordinator

PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
Expand Down Expand Up @@ -54,10 +68,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) -> bo
coordinator = BSBLanUpdateCoordinator(hass, entry, bsblan)
await coordinator.async_config_entry_first_refresh()

# Fetch all required data concurrently
device = await bsblan.device()
info = await bsblan.info()
static = await bsblan.static_values()
try:
# Fetch all required data sequentially
device = await bsblan.device()
info = await bsblan.info()
static = await bsblan.static_values()
except BSBLANConnectionError as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="setup_connection_error",
translation_placeholders={"host": entry.data[CONF_HOST]},
) from err
except BSBLANAuthError as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="setup_auth_error",
) from err
except BSBLANError as err:
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="setup_general_error",
) from err

entry.runtime_data = BSBLanData(
client=bsblan,
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/bsblan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
"passkey": "[%key:component::bsblan::config::step::user::data::passkey%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"passkey": "[%key:component::bsblan::config::step::user::data_description::passkey%]",
"username": "[%key:component::bsblan::config::step::user::data_description::username%]",
"password": "[%key:component::bsblan::config::step::user::data_description::password%]"
}
}
},
Expand All @@ -66,6 +71,15 @@
},
"set_operation_mode_error": {
"message": "An error occurred while setting the operation mode"
},
"setup_connection_error": {
"message": "Failed to retrieve static device data from BSB-Lan device at {host}"
},
"setup_auth_error": {
"message": "Authentication failed while retrieving static device data"
},
"setup_general_error": {
"message": "An unknown error occurred while retrieving static device data"
}
},
"entity": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/freebox/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def __init__(
self.name: str = freebox_config["model_info"]["pretty_name"]
self.mac: str = freebox_config["mac"]
self._sw_v: str = freebox_config["firmware_version"]
self._hw_v: str | None = freebox_config.get("board_name")
self._attrs: dict[str, Any] = {}

self.supports_hosts = True
Expand Down Expand Up @@ -282,7 +283,9 @@ def device_info(self) -> DeviceInfo:
identifiers={(DOMAIN, self.mac)},
manufacturer="Freebox SAS",
name=self.name,
model=self.name,
sw_version=self._sw_v,
hw_version=self._hw_v,
)

@property
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/husqvarna_automower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Platform.BUTTON,
Platform.CALENDAR,
Platform.DEVICE_TRACKER,
Platform.EVENT,
Platform.LAWN_MOWER,
Platform.NUMBER,
Platform.SELECT,
Expand Down
125 changes: 125 additions & 0 deletions homeassistant/components/husqvarna_automower/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,128 @@
MowerStates.WAIT_POWER_UP,
MowerStates.WAIT_UPDATING,
]

ERROR_KEYS = [
"alarm_mower_in_motion",
"alarm_mower_lifted",
"alarm_mower_stopped",
"alarm_mower_switched_off",
"alarm_mower_tilted",
"alarm_outside_geofence",
"angular_sensor_problem",
"battery_problem",
"battery_restriction_due_to_ambient_temperature",
"can_error",
"charging_current_too_high",
"charging_station_blocked",
"charging_system_problem",
"collision_sensor_defect",
"collision_sensor_error",
"collision_sensor_problem_front",
"collision_sensor_problem_rear",
"com_board_not_available",
"communication_circuit_board_sw_must_be_updated",
"complex_working_area",
"connection_changed",
"connection_not_changed",
"connectivity_problem",
"connectivity_settings_restored",
"cutting_drive_motor_1_defect",
"cutting_drive_motor_2_defect",
"cutting_drive_motor_3_defect",
"cutting_height_blocked",
"cutting_height_problem",
"cutting_height_problem_curr",
"cutting_height_problem_dir",
"cutting_height_problem_drive",
"cutting_motor_problem",
"cutting_stopped_slope_too_steep",
"cutting_system_blocked",
"cutting_system_imbalance_warning",
"cutting_system_major_imbalance",
"destination_not_reachable",
"difficult_finding_home",
"docking_sensor_defect",
"electronic_problem",
"empty_battery",
"folding_cutting_deck_sensor_defect",
"folding_sensor_activated",
"geofence_problem",
"gps_navigation_problem",
"guide_1_not_found",
"guide_2_not_found",
"guide_3_not_found",
"guide_calibration_accomplished",
"guide_calibration_failed",
"high_charging_power_loss",
"high_internal_power_loss",
"high_internal_temperature",
"internal_voltage_error",
"invalid_battery_combination_invalid_combination_of_different_battery_types",
"invalid_sub_device_combination",
"invalid_system_configuration",
"left_brush_motor_overloaded",
"lift_sensor_defect",
"lifted",
"limited_cutting_height_range",
"loop_sensor_defect",
"loop_sensor_problem_front",
"loop_sensor_problem_left",
"loop_sensor_problem_rear",
"loop_sensor_problem_right",
"low_battery",
"memory_circuit_problem",
"mower_lifted",
"mower_tilted",
"no_accurate_position_from_satellites",
"no_confirmed_position",
"no_drive",
"no_loop_signal",
"no_power_in_charging_station",
"no_response_from_charger",
"outside_working_area",
"poor_signal_quality",
"reference_station_communication_problem",
"right_brush_motor_overloaded",
"safety_function_faulty",
"settings_restored",
"sim_card_locked",
"sim_card_not_found",
"sim_card_requires_pin",
"slipped_mower_has_slipped_situation_not_solved_with_moving_pattern",
"slope_too_steep",
"sms_could_not_be_sent",
"stop_button_problem",
"stuck_in_charging_station",
"switch_cord_problem",
"temporary_battery_problem",
"tilt_sensor_problem",
"too_high_discharge_current",
"too_high_internal_current",
"trapped",
"ultrasonic_problem",
"ultrasonic_sensor_1_defect",
"ultrasonic_sensor_2_defect",
"ultrasonic_sensor_3_defect",
"ultrasonic_sensor_4_defect",
"unexpected_cutting_height_adj",
"unexpected_error",
"upside_down",
"weak_gps_signal",
"wheel_drive_problem_left",
"wheel_drive_problem_rear_left",
"wheel_drive_problem_rear_right",
"wheel_drive_problem_right",
"wheel_motor_blocked_left",
"wheel_motor_blocked_rear_left",
"wheel_motor_blocked_rear_right",
"wheel_motor_blocked_right",
"wheel_motor_overloaded_left",
"wheel_motor_overloaded_rear_left",
"wheel_motor_overloaded_rear_right",
"wheel_motor_overloaded_right",
"work_area_not_valid",
"wrong_loop_signal",
"wrong_pin_code",
"zone_generator_problem",
]
108 changes: 108 additions & 0 deletions homeassistant/components/husqvarna_automower/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Creates the event entities for supported mowers."""

from collections.abc import Callable

from aioautomower.model import SingleMessageData

from homeassistant.components.event import (
DOMAIN as EVENT_DOMAIN,
EventEntity,
EventEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import AutomowerConfigEntry
from .const import ERROR_KEYS
from .coordinator import AutomowerDataUpdateCoordinator
from .entity import AutomowerBaseEntity

PARALLEL_UPDATES = 1

ATTR_SEVERITY = "severity"
ATTR_LATITUDE = "latitude"
ATTR_LONGITUDE = "longitude"
ATTR_DATE_TIME = "date_time"


async def async_setup_entry(
hass: HomeAssistant,
config_entry: AutomowerConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Automower message event entities.

Entities are created dynamically based on messages received from the API,
but only for mowers that support message events.
"""
coordinator = config_entry.runtime_data
entity_registry = er.async_get(hass)

restored_mowers = {
entry.unique_id.removesuffix("_message")
for entry in er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
if entry.domain == EVENT_DOMAIN
}

async_add_entities(
AutomowerMessageEventEntity(mower_id, coordinator)
for mower_id in restored_mowers
if mower_id in coordinator.data
)

@callback
def _handle_message(msg: SingleMessageData) -> None:
if msg.id in restored_mowers:
return

restored_mowers.add(msg.id)
async_add_entities([AutomowerMessageEventEntity(msg.id, coordinator)])

coordinator.api.register_single_message_callback(_handle_message)


class AutomowerMessageEventEntity(AutomowerBaseEntity, EventEntity):
"""EventEntity for Automower message events."""

entity_description: EventEntityDescription
_message_cb: Callable[[SingleMessageData], None]
_attr_translation_key = "message"
_attr_event_types = ERROR_KEYS

def __init__(
self,
mower_id: str,
coordinator: AutomowerDataUpdateCoordinator,
) -> None:
"""Initialize Automower message event entity."""
super().__init__(mower_id, coordinator)
self._attr_unique_id = f"{mower_id}_message"

@callback
def _handle(self, msg: SingleMessageData) -> None:
"""Handle a message event from the API and trigger the event entity if it matches the entity's mower ID."""
if msg.id != self.mower_id:
return
message = msg.attributes.message
self._trigger_event(
message.code,
{
ATTR_SEVERITY: message.severity,
ATTR_LATITUDE: message.latitude,
ATTR_LONGITUDE: message.longitude,
ATTR_DATE_TIME: message.time,
},
)
self.async_write_ha_state()

async def async_added_to_hass(self) -> None:
"""Register callback when entity is added to hass."""
await super().async_added_to_hass()
self.coordinator.api.register_single_message_callback(self._handle)

async def async_will_remove_from_hass(self) -> None:
"""Unregister WebSocket callback when entity is removed."""
self.coordinator.api.unregister_single_message_callback(self._handle)
5 changes: 5 additions & 0 deletions homeassistant/components/husqvarna_automower/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"default": "mdi:saw-blade"
}
},
"event": {
"message": {
"default": "mdi:alert-circle-check-outline"
}
},
"number": {
"cutting_height": {
"default": "mdi:grass"
Expand Down
Loading
Loading