diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 41ceb1481c541..b176633526ca9 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -128,19 +128,23 @@ jobs: name: Build Core wheels ${{ matrix.abi }} for ${{ matrix.arch }} (musllinux_1_2) if: github.repository_owner == 'home-assistant' needs: init - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: &matrix-build abi: ["cp313", "cp314"] arch: ${{ fromJson(needs.init.outputs.architectures) }} + include: + - os: ubuntu-latest + - arch: aarch64 + os: ubuntu-24.04-arm exclude: - - abi: "cp314" - arch: "armv7" - - abi: "cp314" - arch: "armhf" - - abi: "cp314" - arch: "i386" + - abi: cp314 + arch: armv7 + - abi: cp314 + arch: armhf + - abi: cp314 + arch: i386 steps: - *checkout @@ -187,7 +191,7 @@ jobs: name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }} if: github.repository_owner == 'home-assistant' needs: init - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: *matrix-build diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index e47fdfe08147f..34c6f45de72f4 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -17,7 +17,7 @@ "mqtt": ["esphome/discover/#"], "quality_scale": "platinum", "requirements": [ - "aioesphomeapi==42.1.0", + "aioesphomeapi==42.2.0", "esphome-dashboard-api==1.3.0", "bleak-esphome==3.4.0" ], diff --git a/homeassistant/components/nightscout/config_flow.py b/homeassistant/components/nightscout/config_flow.py index 0c0e8b296cd00..c5797e3f8f184 100644 --- a/homeassistant/components/nightscout/config_flow.py +++ b/homeassistant/components/nightscout/config_flow.py @@ -63,7 +63,12 @@ async def async_step_user( return self.async_create_entry(title=info["title"], data=user_input) return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors + step_id="user", + data_schema=DATA_SCHEMA, + errors=errors, + description_placeholders={ + "example_url": "https://myhomeassistant.duckdns.org:5423", + }, ) diff --git a/homeassistant/components/nightscout/strings.json b/homeassistant/components/nightscout/strings.json index fd1d81e1273f1..1f13ed17054a1 100644 --- a/homeassistant/components/nightscout/strings.json +++ b/homeassistant/components/nightscout/strings.json @@ -3,10 +3,13 @@ "step": { "user": { "title": "Enter your Nightscout server information.", - "description": "- URL: the address of your nightscout instance. I.e.: https://myhomeassistant.duckdns.org:5423\n- API Key (optional): Only use if your instance is protected (auth_default_roles != readable).", "data": { "url": "[%key:common::config_flow::data::url%]", "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "data_description": { + "url": "The address of your Nightscout instance. For example: {example_url}.", + "api_key": "Optional; only use it if your instance is protected (auth_default_roles != readable)." } } }, diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 7295a41da0f45..175fdfe63cd17 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["opower"], "quality_scale": "bronze", - "requirements": ["opower==0.15.7"] + "requirements": ["opower==0.15.8"] } diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index f9eb51c2ac8d7..b350407f117ce 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -323,7 +323,6 @@ class RpcEntityDescription(EntityDescription): use_polling_coordinator: bool = False supported: Callable = lambda _: False unit: Callable[[dict], str | None] | None = None - options_fn: Callable[[dict], list[str]] | None = None entity_class: Callable | None = None role: str | None = None models: set[str] | None = None diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ec1abd415ae69..c4ee99faa9170 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -104,7 +104,10 @@ def __init__( super().__init__(coordinator, key, attribute, description) if self.option_map: - self._attr_options = list(self.option_map.values()) + if description.role == ROLE_GENERIC: + self._attr_options = list(self.option_map.values()) + else: + self._attr_options = list(self.option_map) @property def native_value(self) -> StateType: @@ -117,7 +120,10 @@ def native_value(self) -> StateType: if not isinstance(attribute_value, str): return None - return self.option_map[attribute_value] + if self.entity_description.role == ROLE_GENERIC: + return self.option_map[attribute_value] + + return attribute_value class RpcEnergyConsumedSensor(RpcSensor): @@ -1442,7 +1448,6 @@ def __init__( removal_condition=lambda config, _, key: not is_view_for_platform( config, key, SENSOR_PLATFORM ), - options_fn=lambda config: config["options"], device_class=SensorDeviceClass.ENUM, role=ROLE_GENERIC, ), @@ -1532,21 +1537,11 @@ def __init__( state_class=SensorStateClass.MEASUREMENT, role="water_temperature", ), - "number_work_state": RpcSensorDescription( - key="number", + "enum_work_state": RpcSensorDescription( + key="enum", sub_key="value", translation_key="charger_state", device_class=SensorDeviceClass.ENUM, - options=[ - "charger_charging", - "charger_end", - "charger_fault", - "charger_free", - "charger_free_fault", - "charger_insert", - "charger_pause", - "charger_wait", - ], role="work_state", ), "number_energy_charge": RpcSensorDescription( diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 2f6c76995f15c..681d80ec49eb2 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -143,6 +143,7 @@ class RpcSwitchDescription(RpcEntityDescription, SwitchEntityDescription): "boolean_start_charging": RpcSwitchDescription( key="boolean", sub_key="value", + name="Charging", is_on=lambda status: bool(status["value"]), method_on="boolean_set", method_off="boolean_set", diff --git a/requirements_all.txt b/requirements_all.txt index 874e06233b9a9..11da225da1d2c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -256,7 +256,7 @@ aioelectricitymaps==1.1.1 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==42.1.0 +aioesphomeapi==42.2.0 # homeassistant.components.flo aioflo==2021.11.0 @@ -1664,7 +1664,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.15.7 +opower==0.15.8 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 020072a36d4ed..e51d6fbb57f9b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -244,7 +244,7 @@ aioelectricitymaps==1.1.1 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==42.1.0 +aioesphomeapi==42.2.0 # homeassistant.components.flo aioflo==2021.11.0 @@ -1423,7 +1423,7 @@ openrgb-python==0.3.5 openwebifpy==4.3.1 # homeassistant.components.opower -opower==0.15.7 +opower==0.15.8 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/tests/components/bluesound/conftest.py b/tests/components/bluesound/conftest.py index 4a79396764539..8baa75d0ed8d6 100644 --- a/tests/components/bluesound/conftest.py +++ b/tests/components/bluesound/conftest.py @@ -1,9 +1,11 @@ """Common fixtures for the Bluesound tests.""" +from __future__ import annotations + from collections.abc import AsyncGenerator, Generator from dataclasses import dataclass import ipaddress -from typing import Any +from typing import Any, Self from unittest.mock import AsyncMock, patch from pyblu import Input, Player, Preset, Status, SyncStatus @@ -27,8 +29,8 @@ class PlayerMockData: status_long_polling_mock: LongPollingMock[Status] sync_status_long_polling_mock: LongPollingMock[SyncStatus] - @staticmethod - async def generate(host: str) -> "PlayerMockData": + @classmethod + async def generate(cls, host: str) -> Self: """Generate player mock data.""" host_ip = ipaddress.ip_address(host) assert host_ip.version == 4 @@ -110,7 +112,7 @@ async def generate(host: str) -> "PlayerMockData": ] ) - return PlayerMockData( + return cls( host, player, status_long_polling_mock, sync_status_long_polling_mock ) diff --git a/tests/components/remote_calendar/test_config_flow.py b/tests/components/remote_calendar/test_config_flow.py index 9bea46ab27e64..3619251654d27 100644 --- a/tests/components/remote_calendar/test_config_flow.py +++ b/tests/components/remote_calendar/test_config_flow.py @@ -83,7 +83,7 @@ async def test_form_import_webcal(hass: HomeAssistant, ics_content: str) -> None ], ) @respx.mock -async def test_form_inavild_url( +async def test_form_invalid_url( hass: HomeAssistant, side_effect: Exception, ics_content: str, diff --git a/tests/components/shelly/snapshots/test_sensor.ambr b/tests/components/shelly/snapshots/test_sensor.ambr index 11892f586a661..93be3c9103e71 100644 --- a/tests/components/shelly/snapshots/test_sensor.ambr +++ b/tests/components/shelly/snapshots/test_sensor.ambr @@ -399,7 +399,7 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'charger_state', - 'unique_id': '123456789ABC-number:200-number_work_state', + 'unique_id': '123456789ABC-enum:200-enum_work_state', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 640d7439eb9f3..3aace4a88170d 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1693,7 +1693,7 @@ async def test_rpc_shelly_ev_sensors( ) -> None: """Test Shelly EV sensors.""" config = deepcopy(mock_rpc_device.config) - config["number:200"] = { + config["enum:200"] = { "name": "Charger state", "meta": { "ui": { @@ -1711,14 +1711,14 @@ async def test_rpc_shelly_ev_sensors( } }, "options": [ - "charger_free", - "charger_insert", - "charger_free_fault", - "charger_wait", "charger_charging", - "charger_pause", "charger_end", "charger_fault", + "charger_free", + "charger_free_fault", + "charger_insert", + "charger_pause", + "charger_wait", ], "role": "work_state", } @@ -1735,7 +1735,7 @@ async def test_rpc_shelly_ev_sensors( monkeypatch.setattr(mock_rpc_device, "config", config) status = deepcopy(mock_rpc_device.status) - status["number:200"] = {"value": "charger_charging"} + status["enum:200"] = {"value": "charger_charging"} status["number:201"] = {"value": 5.0} status["number:202"] = {"value": 60} monkeypatch.setattr(mock_rpc_device, "status", status) diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py index 9daf557cf4527..babb15b112304 100644 --- a/tests/components/shelly/test_switch.py +++ b/tests/components/shelly/test_switch.py @@ -14,6 +14,7 @@ from homeassistant.components.shelly.const import ( DOMAIN, ENTRY_RELOAD_COOLDOWN, + MODEL_TOP_EV_CHARGER_EVE01, MODEL_WALL_DISPLAY, MOTION_MODELS, ) @@ -954,3 +955,33 @@ async def test_cury_switch_availability( assert (state := hass.states.get(entity_id)) assert state.state == STATE_ON + + +async def test_rpc_ev_charging_switch( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, + entity_registry: EntityRegistry, +) -> None: + """Test the charging switch for EV charger.""" + config = deepcopy(mock_rpc_device.config) + config["boolean:200"] = { + "name": "Start Charging", + "meta": {"ui": {"view": "toggle"}}, + "role": "start_charging", + } + monkeypatch.setattr(mock_rpc_device, "config", config) + + status = deepcopy(mock_rpc_device.status) + status["boolean:200"] = {"value": False} + monkeypatch.setattr(mock_rpc_device, "status", status) + + entity_id = "switch.test_name_charging" + + await init_integration(hass, 3, model=MODEL_TOP_EV_CHARGER_EVE01) + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_OFF + + assert (entry := entity_registry.async_get(entity_id)) + assert entry.unique_id == "123456789ABC-boolean:200-boolean_start_charging" diff --git a/tests/components/todoist/test_calendar.py b/tests/components/todoist/test_calendar.py index e342df5f6b79e..cfd8f55df2d2e 100644 --- a/tests/components/todoist/test_calendar.py +++ b/tests/components/todoist/test_calendar.py @@ -7,6 +7,7 @@ import urllib import zoneinfo +from freezegun import freeze_time from freezegun.api import FrozenDateTimeFactory import pytest from todoist_api_python.models import Due @@ -38,6 +39,13 @@ TIMEZONE = zoneinfo.ZoneInfo(TZ_NAME) +@pytest.fixture(autouse=True) +def freeze_the_time(): + """Freeze the time.""" + with freeze_time("2024-05-24 12:00:00"): + yield + + @pytest.fixture(autouse=True) def platforms() -> list[Platform]: """Override platforms."""