Compare commits

..

1 Commits

Author SHA1 Message Date
G Johansson 45aab80b59 Modify query as template in SQL integration 2024-08-23 14:48:50 +00:00
1227 changed files with 11126 additions and 32774 deletions
-1
View File
@@ -61,7 +61,6 @@ components: &components
- homeassistant/components/auth/**
- homeassistant/components/automation/**
- homeassistant/components/backup/**
- homeassistant/components/blueprint/**
- homeassistant/components/bluetooth/**
- homeassistant/components/cloud/**
- homeassistant/components/config/**
-53
View File
@@ -482,56 +482,3 @@ jobs:
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
twine upload dist/* --skip-existing
hassfest-image:
name: Build and test hassfest image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
needs: ["init"]
if: github.repository_owner == 'home-assistant'
env:
HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
load: true
tags: ${{ env.HASSFEST_IMAGE_TAG }}
- name: Run hassfest against core
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
push: true
tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
+2 -2
View File
@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.1.7
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.26.5
uses: github/codeql-action/init@v3.26.4
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.26.5
uses: github/codeql-action/analyze@v3.26.4
with:
category: "/language:python"
+1
View File
@@ -294,6 +294,7 @@ homeassistant.components.london_underground.*
homeassistant.components.lookin.*
homeassistant.components.luftdaten.*
homeassistant.components.madvr.*
homeassistant.components.mailbox.*
homeassistant.components.manual.*
homeassistant.components.map.*
homeassistant.components.mastodon.*
-4
View File
@@ -1493,8 +1493,6 @@ build.json @home-assistant/supervisor
/tests/components/tomorrowio/ @raman325 @lymanepp
/homeassistant/components/totalconnect/ @austinmroczek
/tests/components/totalconnect/ @austinmroczek
/homeassistant/components/touchline_sl/ @jnsgruk
/tests/components/touchline_sl/ @jnsgruk
/homeassistant/components/tplink/ @rytilahti @bdraco @sdb9696
/tests/components/tplink/ @rytilahti @bdraco @sdb9696
/homeassistant/components/tplink_omada/ @MarkGodwin
@@ -1660,8 +1658,6 @@ build.json @home-assistant/supervisor
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG
/homeassistant/components/xiaomi_tv/ @simse
/homeassistant/components/xmpp/ @fabaff @flowolf
/homeassistant/components/yale/ @bdraco
/tests/components/yale/ @bdraco
/homeassistant/components/yale_smart_alarm/ @gjohansson-ST
/tests/components/yale_smart_alarm/ @gjohansson-ST
/homeassistant/components/yalexs_ble/ @bdraco
+1 -2
View File
@@ -42,8 +42,7 @@ WORKDIR /usr/src
# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
&& uv pip install --system -e hass-release/ \
&& chown -R vscode /usr/src/hass-release/data
&& uv pip install --system -e hass-release/
USER vscode
ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "roth",
"name": "Roth",
"integrations": ["touchline", "touchline_sl"]
}
+1 -7
View File
@@ -1,11 +1,5 @@
{
"domain": "yale",
"name": "Yale",
"integrations": [
"august",
"yale_smart_alarm",
"yalexs_ble",
"yale_home",
"yale"
]
"integrations": ["august", "yale_smart_alarm", "yalexs_ble", "yale_home"]
}
+3 -9
View File
@@ -7,14 +7,8 @@
}
},
"services": {
"capture_image": {
"service": "mdi:camera"
},
"change_setting": {
"service": "mdi:cog"
},
"trigger_automation": {
"service": "mdi:play"
}
"capture_image": "mdi:camera",
"change_setting": "mdi:cog",
"trigger_automation": "mdi:play"
}
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/acmeda",
"iot_class": "local_push",
"loggers": ["aiopulse"],
"requirements": ["aiopulse==0.4.6"]
"requirements": ["aiopulse==0.4.4"]
}
+5 -15
View File
@@ -66,20 +66,10 @@
}
},
"services": {
"add_url": {
"service": "mdi:link-plus"
},
"remove_url": {
"service": "mdi:link-off"
},
"enable_url": {
"service": "mdi:link-variant"
},
"disable_url": {
"service": "mdi:link-variant-off"
},
"refresh": {
"service": "mdi:refresh"
}
"add_url": "mdi:link-plus",
"remove_url": "mdi:link-off",
"enable_url": "mdi:link-variant",
"disable_url": "mdi:link-variant-off",
"refresh": "mdi:refresh"
}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"services": {
"write_data_by_name": {
"service": "mdi:pencil"
}
"write_data_by_name": "mdi:pencil"
}
}
@@ -1,7 +1,5 @@
{
"services": {
"set_time_to": {
"service": "mdi:timer-cog"
}
"set_time_to": "mdi:timer-cog"
}
}
@@ -7,11 +7,7 @@
}
},
"services": {
"add_tracking": {
"service": "mdi:package-variant-plus"
},
"remove_tracking": {
"service": "mdi:package-variant-minus"
}
"add_tracking": "mdi:package-variant-plus",
"remove_tracking": "mdi:package-variant-minus"
}
}
+5 -15
View File
@@ -1,19 +1,9 @@
{
"services": {
"start_recording": {
"service": "mdi:record-rec"
},
"stop_recording": {
"service": "mdi:stop"
},
"enable_alerts": {
"service": "mdi:bell-alert"
},
"disable_alerts": {
"service": "mdi:bell-off"
},
"snapshot": {
"service": "mdi:camera"
}
"start_recording": "mdi:record-rec",
"stop_recording": "mdi:stop",
"enable_alerts": "mdi:bell-alert",
"disable_alerts": "mdi:bell-off",
"snapshot": "mdi:camera"
}
}
@@ -92,9 +92,7 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
except AirGradientError:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
current_measures.serial_number, raise_on_progress=False
)
await self.async_set_unique_id(current_measures.serial_number)
self._abort_if_unique_id_configured()
await self.set_configuration_source()
return self.async_create_entry(
@@ -24,5 +24,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
"iot_class": "local_polling",
"requirements": ["airthings-ble==0.9.1"]
"requirements": ["airthings-ble==0.9.0"]
}
@@ -262,7 +262,7 @@ class Airtouch5AC(Airtouch5ClimateEntity):
_LOGGER.debug("Argument `temperature` is missing in set_temperature")
return
await self._control(setpoint=SetpointControl.CHANGE_SETPOINT, temp=temp)
await self._control(temp=temp)
class Airtouch5Zone(Airtouch5ClimateEntity):
@@ -80,9 +80,11 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
"""Initialize."""
self._reauth_entry: ConfigEntry | None = None
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Import a config entry from `airvisual` integration (see #83882)."""
return await self.async_step_user(import_data)
async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
+3 -59
View File
@@ -2,21 +2,16 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Final
from aioairzone.common import GrilleAngle, OperationMode, SleepTimeout
from aioairzone.common import GrilleAngle, SleepTimeout
from aioairzone.const import (
API_COLD_ANGLE,
API_HEAT_ANGLE,
API_MODE,
API_SLEEP,
AZD_COLD_ANGLE,
AZD_HEAT_ANGLE,
AZD_MASTER,
AZD_MODE,
AZD_MODES,
AZD_SLEEP,
AZD_ZONES,
)
@@ -38,9 +33,6 @@ class AirzoneSelectDescription(SelectEntityDescription):
api_param: str
options_dict: dict[str, int]
options_fn: Callable[[dict[str, Any], dict[str, int]], list[str]] = (
lambda zone_data, value: list(value)
)
GRILLE_ANGLE_DICT: Final[dict[str, int]] = {
@@ -50,15 +42,6 @@ GRILLE_ANGLE_DICT: Final[dict[str, int]] = {
"40deg": GrilleAngle.DEG_40,
}
MODE_DICT: Final[dict[str, int]] = {
"cool": OperationMode.COOLING,
"dry": OperationMode.DRY,
"fan": OperationMode.FAN,
"heat": OperationMode.HEATING,
"heat_cool": OperationMode.AUTO,
"stop": OperationMode.STOP,
}
SLEEP_DICT: Final[dict[str, int]] = {
"off": SleepTimeout.SLEEP_OFF,
"30m": SleepTimeout.SLEEP_30,
@@ -67,26 +50,6 @@ SLEEP_DICT: Final[dict[str, int]] = {
}
def main_zone_options(
zone_data: dict[str, Any],
options: dict[str, int],
) -> list[str]:
"""Filter available modes."""
modes = zone_data.get(AZD_MODES, [])
return [k for k, v in options.items() if v in modes]
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_MODE,
key=AZD_MODE,
options_dict=MODE_DICT,
options_fn=main_zone_options,
translation_key="modes",
),
)
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
AirzoneSelectDescription(
api_param=API_COLD_ANGLE,
@@ -132,20 +95,7 @@ async def async_setup_entry(
received_zones = set(zones_data)
new_zones = received_zones - added_zones
if new_zones:
entities: list[AirzoneZoneSelect] = [
AirzoneZoneSelect(
coordinator,
description,
entry,
system_zone_id,
zones_data.get(system_zone_id),
)
for system_zone_id in new_zones
for description in MAIN_ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
and zones_data.get(system_zone_id).get(AZD_MASTER) is True
]
entities += [
async_add_entities(
AirzoneZoneSelect(
coordinator,
description,
@@ -156,8 +106,7 @@ async def async_setup_entry(
for system_zone_id in new_zones
for description in ZONE_SELECT_TYPES
if description.key in zones_data.get(system_zone_id)
]
async_add_entities(entities)
)
added_zones.update(new_zones)
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
@@ -204,11 +153,6 @@ class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
f"{self._attr_unique_id}_{system_zone_id}_{description.key}"
)
self.entity_description = description
self._attr_options = self.entity_description.options_fn(
zone_data, description.options_dict
)
self.values_dict = {v: k for k, v in description.options_dict.items()}
self._async_update_attrs()
@@ -52,17 +52,6 @@
"40deg": "[%key:component::airzone::entity::select::grille_angles::state::40deg%]"
}
},
"modes": {
"name": "Mode",
"state": {
"cool": "[%key:component::climate::entity_component::_::state::cool%]",
"dry": "[%key:component::climate::entity_component::_::state::dry%]",
"fan": "[%key:component::climate::entity_component::_::state::fan_only%]",
"heat": "[%key:component::climate::entity_component::_::state::heat%]",
"heat_cool": "[%key:component::climate::entity_component::_::state::heat_cool%]",
"stop": "Stop"
}
},
"sleep_times": {
"name": "Sleep",
"state": {
@@ -15,26 +15,12 @@
}
},
"services": {
"alarm_arm_away": {
"service": "mdi:shield-lock"
},
"alarm_arm_home": {
"service": "mdi:shield-home"
},
"alarm_arm_night": {
"service": "mdi:shield-moon"
},
"alarm_arm_custom_bypass": {
"service": "mdi:security"
},
"alarm_disarm": {
"service": "mdi:shield-off"
},
"alarm_trigger": {
"service": "mdi:bell-ring"
},
"alarm_arm_vacation": {
"service": "mdi:shield-airplane"
}
"alarm_arm_away": "mdi:shield-lock",
"alarm_arm_home": "mdi:shield-home",
"alarm_arm_night": "mdi:shield-moon",
"alarm_arm_custom_bypass": "mdi:security",
"alarm_disarm": "mdi:shield-off",
"alarm_trigger": "mdi:bell-ring",
"alarm_arm_vacation": "mdi:shield-airplane"
}
}
@@ -7,11 +7,7 @@
}
},
"services": {
"alarm_keypress": {
"service": "mdi:dialpad"
},
"alarm_toggle_chime": {
"service": "mdi:abc"
}
"alarm_keypress": "mdi:dialpad",
"alarm_toggle_chime": "mdi:abc"
}
}
+3 -9
View File
@@ -1,13 +1,7 @@
{
"services": {
"toggle": {
"service": "mdi:bell-ring"
},
"turn_off": {
"service": "mdi:bell-off"
},
"turn_on": {
"service": "mdi:bell-alert"
}
"toggle": "mdi:bell-ring",
"turn_off": "mdi:bell-off",
"turn_on": "mdi:bell-alert"
}
}
+3 -6
View File
@@ -661,12 +661,9 @@ class RemoteCapabilities(AlexaEntity):
def interfaces(self) -> Generator[AlexaCapability]:
"""Yield the supported interfaces."""
yield AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
if activities and supported & remote.RemoteEntityFeature.ACTIVITY:
yield AlexaModeController(
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
)
yield AlexaModeController(
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
)
yield AlexaEndpointHealth(self.hass, self.entity)
yield Alexa(self.entity)
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aioambient"],
"requirements": ["aioambient==2024.08.0"]
"requirements": ["aioambient==2024.01.0"]
}
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["aioambient"],
"requirements": ["aioambient==2024.08.0"]
"requirements": ["aioambient==2024.01.0"]
}
+11 -33
View File
@@ -1,37 +1,15 @@
{
"services": {
"enable_recording": {
"service": "mdi:record-rec"
},
"disable_recording": {
"service": "mdi:stop"
},
"enable_audio": {
"service": "mdi:volume-high"
},
"disable_audio": {
"service": "mdi:volume-off"
},
"enable_motion_recording": {
"service": "mdi:motion-sensor"
},
"disable_motion_recording": {
"service": "mdi:motion-sensor-off"
},
"goto_preset": {
"service": "mdi:pan"
},
"set_color_bw": {
"service": "mdi:palette"
},
"start_tour": {
"service": "mdi:panorama"
},
"stop_tour": {
"service": "mdi:panorama-outline"
},
"ptz_control": {
"service": "mdi:pan"
}
"enable_recording": "mdi:record-rec",
"disable_recording": "mdi:stop",
"enable_audio": "mdi:volume-high",
"disable_audio": "mdi:volume-off",
"enable_motion_recording": "mdi:motion-sensor",
"disable_motion_recording": "mdi:motion-sensor-off",
"goto_preset": "mdi:pan",
"set_color_bw": "mdi:palette",
"start_tour": "mdi:panorama",
"stop_tour": "mdi:panorama-outline",
"ptz_control": "mdi:pan"
}
}
+4 -12
View File
@@ -1,16 +1,8 @@
{
"services": {
"adb_command": {
"service": "mdi:console"
},
"download": {
"service": "mdi:download"
},
"upload": {
"service": "mdi:upload"
},
"learn_sendevent": {
"service": "mdi:remote"
}
"adb_command": "mdi:console",
"download": "mdi:download",
"upload": "mdi:upload",
"learn_sendevent": "mdi:remote"
}
}
@@ -8,6 +8,6 @@
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"quality_scale": "platinum",
"requirements": ["androidtvremote2==0.1.2"],
"requirements": ["androidtvremote2==0.1.1"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}
+1 -1
View File
@@ -8,7 +8,7 @@ LOGGER = logging.getLogger(__package__)
CONF_RECOMMENDED = "recommended"
CONF_PROMPT = "prompt"
CONF_CHAT_MODEL = "chat_model"
RECOMMENDED_CHAT_MODEL = "claude-3-haiku-20240307"
RECOMMENDED_CHAT_MODEL = "claude-3-5-sonnet-20240620"
CONF_MAX_TOKENS = "max_tokens"
RECOMMENDED_MAX_TOKENS = 1024
CONF_TEMPERATURE = "temperature"
+1 -1
View File
@@ -6,4 +6,4 @@ DOMAIN: Final = "apcupsd"
CONNECTION_TIMEOUT: int = 10
# Field name of last self test retrieved from apcupsd.
LAST_S_TEST: Final = "laststest"
LASTSTEST: Final = "laststest"
+6 -5
View File
@@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
STATE_UNKNOWN,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
@@ -25,7 +26,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, LAST_S_TEST
from .const import DOMAIN, LASTSTEST
from .coordinator import APCUPSdCoordinator
PARALLEL_UPDATES = 0
@@ -156,8 +157,8 @@ SENSORS: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
LAST_S_TEST: SensorEntityDescription(
key=LAST_S_TEST,
LASTSTEST: SensorEntityDescription(
key=LASTSTEST,
translation_key="last_self_test",
),
"lastxfer": SensorEntityDescription(
@@ -422,7 +423,7 @@ async def async_setup_entry(
# periodical (or manual) self test since last daemon restart. It might not be available
# when we set up the integration, and we do not know if it would ever be available. Here we
# add it anyway and mark it as unknown initially.
for resource in available_resources | {LAST_S_TEST}:
for resource in available_resources | {LASTSTEST}:
if resource not in SENSORS:
_LOGGER.warning("Invalid resource from APCUPSd: %s", resource.upper())
continue
@@ -483,7 +484,7 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
# performed) and may disappear again after certain event. So we mark the state as "unknown"
# when it becomes unknown after such events.
if key not in self.coordinator.data:
self._attr_native_value = None
self._attr_native_value = STATE_UNKNOWN
return
self._attr_native_value, inferred_unit = infer_unit(self.coordinator.data[key])
@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push",
"loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.15.1"],
"requirements": ["pyatv==0.15.0"],
"zeroconf": [
"_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.",
@@ -36,11 +36,10 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
async def _async_setup(self) -> None:
try:
device_info = await self.api.get_device_info()
max_power = (await self.api.get_device_info()).maxPower
except (ConnectionError, TimeoutError):
raise UpdateFailed from None
self.api.max_power = device_info.maxPower
self.api.min_power = device_info.minPower
self.api.max_power = max_power
async def _async_update_data(self) -> ApSystemsSensorData:
output_data = await self.api.get_output_data()
+1 -1
View File
@@ -26,6 +26,7 @@ async def async_setup_entry(
class ApSystemsMaxOutputNumber(ApSystemsEntity, NumberEntity):
"""Base sensor to be used with description."""
_attr_native_min_value = 30
_attr_native_step = 1
_attr_device_class = NumberDeviceClass.POWER
_attr_mode = NumberMode.BOX
@@ -41,7 +42,6 @@ class ApSystemsMaxOutputNumber(ApSystemsEntity, NumberEntity):
self._api = data.coordinator.api
self._attr_unique_id = f"{data.device_id}_output_limit"
self._attr_native_max_value = data.coordinator.api.max_power
self._attr_native_min_value = data.coordinator.api.min_power
async def async_update(self) -> None:
"""Set the state with the value fetched from the inverter."""
@@ -56,7 +56,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
refresh_token = await api.authenticate(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
)
except (ApiException, TimeoutError):
except ApiException:
errors["base"] = "cannot_connect"
except AuthenticationFailed:
errors["base"] = "invalid_auth"
@@ -56,7 +56,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
so entities can quickly look up their data.
"""
async with asyncio.timeout(30):
async with asyncio.timeout(10):
# Check if the refresh token is expired
expiry_time = (
self.refresh_token_creation_time
@@ -72,7 +72,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
softeners = await self.aquacell_api.get_all_softeners()
except AuthenticationFailed as err:
raise ConfigEntryError from err
except (AquacellApiException, TimeoutError) as err:
except AquacellApiException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
return {softener.dsn: softener for softener in softeners}
@@ -101,7 +101,7 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
@@ -109,10 +109,10 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
return await self.async_step_reauth_confirm(user_input)
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
self, user_input: Mapping | None = None
) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required."""
@@ -5,7 +5,6 @@ from dataclasses import dataclass
import logging
from pymicro_vad import MicroVad
from pyspeex_noise import AudioProcessor
from .const import BYTES_PER_CHUNK
@@ -42,8 +41,8 @@ class AudioEnhancer(ABC):
"""Enhance chunk of PCM audio @ 16Khz with 16-bit mono samples."""
class MicroVadSpeexEnhancer(AudioEnhancer):
"""Audio enhancer that runs microVAD and speex."""
class MicroVadEnhancer(AudioEnhancer):
"""Audio enhancer that just runs microVAD."""
def __init__(
self, auto_gain: int, noise_suppression: int, is_vad_enabled: bool
@@ -51,24 +50,6 @@ class MicroVadSpeexEnhancer(AudioEnhancer):
"""Initialize audio enhancer."""
super().__init__(auto_gain, noise_suppression, is_vad_enabled)
self.audio_processor: AudioProcessor | None = None
# Scale from 0-4
self.noise_suppression = noise_suppression * -15
# Scale from 0-31
self.auto_gain = auto_gain * 300
if (self.auto_gain != 0) or (self.noise_suppression != 0):
self.audio_processor = AudioProcessor(
self.auto_gain, self.noise_suppression
)
_LOGGER.debug(
"Initialized speex with auto_gain=%s, noise_suppression=%s",
self.auto_gain,
self.noise_suppression,
)
self.vad: MicroVad | None = None
self.threshold = 0.5
@@ -80,17 +61,12 @@ class MicroVadSpeexEnhancer(AudioEnhancer):
"""Enhance 10ms chunk of PCM audio @ 16Khz with 16-bit mono samples."""
is_speech: bool | None = None
assert len(audio) == BYTES_PER_CHUNK
if self.vad is not None:
# Run VAD
assert len(audio) == BYTES_PER_CHUNK
speech_prob = self.vad.Process10ms(audio)
is_speech = speech_prob > self.threshold
if self.audio_processor is not None:
# Run noise suppression and auto gain
audio = self.audio_processor.Process10ms(audio).audio
return EnhancedAudioChunk(
audio=audio, timestamp_ms=timestamp_ms, is_speech=is_speech
)
@@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["pymicro-vad==1.0.1", "pyspeex-noise==1.0.2"]
"requirements": ["pymicro-vad==1.0.1"]
}
@@ -49,7 +49,7 @@ from homeassistant.util import (
)
from homeassistant.util.limited_size_dict import LimitedSizeDict
from .audio_enhancer import AudioEnhancer, EnhancedAudioChunk, MicroVadSpeexEnhancer
from .audio_enhancer import AudioEnhancer, EnhancedAudioChunk, MicroVadEnhancer
from .const import (
BYTES_PER_CHUNK,
CONF_DEBUG_RECORDING_DIR,
@@ -589,7 +589,7 @@ class PipelineRun:
# Initialize with audio settings
if self.audio_settings.needs_processor and (self.audio_enhancer is None):
# Default audio enhancer
self.audio_enhancer = MicroVadSpeexEnhancer(
self.audio_enhancer = MicroVadEnhancer(
self.audio_settings.auto_gain_dbfs,
self.audio_settings.noise_suppression_level,
self.audio_settings.is_vad_enabled,
@@ -78,9 +78,6 @@ class VoiceCommandSegmenter:
speech_seconds: float = 0.3
"""Seconds of speech before voice command has started."""
command_seconds: float = 1.0
"""Minimum number of seconds for a voice command."""
silence_seconds: float = 0.7
"""Seconds of silence after voice command has ended."""
@@ -99,9 +96,6 @@ class VoiceCommandSegmenter:
_speech_seconds_left: float = 0.0
"""Seconds left before considering voice command as started."""
_command_seconds_left: float = 0.0
"""Seconds left before voice command could stop."""
_silence_seconds_left: float = 0.0
"""Seconds left before considering voice command as stopped."""
@@ -118,7 +112,6 @@ class VoiceCommandSegmenter:
def reset(self) -> None:
"""Reset all counters and state."""
self._speech_seconds_left = self.speech_seconds
self._command_seconds_left = self.command_seconds - self.speech_seconds
self._silence_seconds_left = self.silence_seconds
self._timeout_seconds_left = self.timeout_seconds
self._reset_seconds_left = self.reset_seconds
@@ -149,9 +142,6 @@ class VoiceCommandSegmenter:
if self._speech_seconds_left <= 0:
# Inside voice command
self.in_command = True
self._command_seconds_left = (
self.command_seconds - self.speech_seconds
)
self._silence_seconds_left = self.silence_seconds
_LOGGER.debug("Voice command started")
else:
@@ -164,8 +154,7 @@ class VoiceCommandSegmenter:
# Silence in command
self._reset_seconds_left = self.reset_seconds
self._silence_seconds_left -= chunk_seconds
self._command_seconds_left -= chunk_seconds
if (self._silence_seconds_left <= 0) and (self._command_seconds_left <= 0):
if self._silence_seconds_left <= 0:
# Command finished successfully
self.reset()
_LOGGER.debug("Voice command finished")
@@ -174,7 +163,6 @@ class VoiceCommandSegmenter:
# Speech in command.
# Reset silence counter if enough speech.
self._reset_seconds_left -= chunk_seconds
self._command_seconds_left -= chunk_seconds
if self._reset_seconds_left <= 0:
self._silence_seconds_left = self.silence_seconds
self._reset_seconds_left = self.reset_seconds
+3 -31
View File
@@ -6,16 +6,15 @@ from pathlib import Path
from typing import cast
from aiohttp import ClientResponseError
from yalexs.const import Brand
from yalexs.exceptions import AugustApiAIOHTTPError
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
from yalexs.manager.gateway import Config as YaleXSConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, issue_registry as ir
from homeassistant.helpers import device_registry as dr
from .const import DOMAIN, PLATFORMS
from .data import AugustData
@@ -25,27 +24,7 @@ from .util import async_create_august_clientsession
type AugustConfigEntry = ConfigEntry[AugustData]
@callback
def _async_create_yale_brand_migration_issue(
hass: HomeAssistant, entry: AugustConfigEntry
) -> None:
"""Create an issue for a brand migration."""
ir.async_create_issue(
hass,
DOMAIN,
"yale_brand_migration",
breaks_in_ha_version="2024.9",
learn_more_url="https://www.home-assistant.io/integrations/yale",
translation_key="yale_brand_migration",
is_fixable=False,
severity=ir.IssueSeverity.CRITICAL,
translation_placeholders={
"migrate_url": "https://my.home-assistant.io/redirect/config_flow_start?domain=yale"
},
)
async def async_setup_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up August from a config entry."""
session = async_create_august_clientsession(hass)
august_gateway = AugustGateway(Path(hass.config.config_dir), session)
@@ -61,11 +40,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bo
return True
async def async_remove_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> None:
"""Remove an August config entry."""
ir.async_delete_issue(hass, DOMAIN, "yale_brand_migration")
async def async_unload_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -77,8 +51,6 @@ async def async_setup_august(
"""Set up the August component."""
config = cast(YaleXSConfig, entry.data)
await august_gateway.async_setup(config)
if august_gateway.api.brand == Brand.YALE_HOME:
_async_create_yale_brand_migration_issue(hass, entry)
await august_gateway.async_authenticate()
await august_gateway.async_refresh_access_token_if_needed()
data = entry.runtime_data = AugustData(hass, august_gateway)
@@ -109,11 +109,12 @@ async def async_setup_entry(
for description in SENSOR_TYPES_DOORBELL
)
entities.extend(
AugustDoorbellBinarySensor(data, doorbell, description)
for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL
for doorbell in data.doorbells
)
for doorbell in data.doorbells:
entities.extend(
AugustDoorbellBinarySensor(data, doorbell, description)
for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL
)
async_add_entities(entities)
+2 -2
View File
@@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AugustConfigEntry
from .entity import AugustEntity
from .entity import AugustEntityMixin
async def async_setup_entry(
@@ -18,7 +18,7 @@ async def async_setup_entry(
async_add_entities(AugustWakeLockButton(data, lock, "wake") for lock in data.locks)
class AugustWakeLockButton(AugustEntity, ButtonEntity):
class AugustWakeLockButton(AugustEntityMixin, ButtonEntity):
"""Representation of an August lock wake button."""
_attr_translation_key = "wake"
+2 -2
View File
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AugustConfigEntry, AugustData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT
from .entity import AugustEntity
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
@@ -38,7 +38,7 @@ async def async_setup_entry(
)
class AugustCamera(AugustEntity, Camera):
class AugustCamera(AugustEntityMixin, Camera):
"""An implementation of an August security camera."""
_attr_translation_key = "camera"
@@ -9,7 +9,7 @@ from typing import Any
import aiohttp
import voluptuous as vol
from yalexs.authenticator_common import ValidationResult
from yalexs.const import BRANDS_WITHOUT_OAUTH, DEFAULT_BRAND, Brand
from yalexs.const import BRANDS, DEFAULT_BRAND
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@@ -28,12 +28,6 @@ from .const import (
from .gateway import AugustGateway
from .util import async_create_august_clientsession
# The Yale Home Brand is not supported by the August integration
# anymore and should migrate to the Yale integration
AVAILABLE_BRANDS = BRANDS_WITHOUT_OAUTH.copy()
del AVAILABLE_BRANDS[Brand.YALE_HOME]
_LOGGER = logging.getLogger(__name__)
@@ -124,7 +118,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Required(
CONF_BRAND,
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
): vol.In(AVAILABLE_BRANDS),
): vol.In(BRANDS),
vol.Required(
CONF_LOGIN_METHOD,
default=self._user_auth_details.get(
@@ -214,7 +208,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Required(
CONF_BRAND,
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
): vol.In(BRANDS_WITHOUT_OAUTH),
): vol.In(BRANDS),
vol.Required(CONF_PASSWORD): str,
}
),
+2 -2
View File
@@ -20,7 +20,7 @@ from .const import MANUFACTURER
DEVICE_TYPES = ["keypad", "lock", "camera", "doorbell", "door", "bell"]
class AugustEntity(Entity):
class AugustEntityMixin(Entity):
"""Base implementation for August device."""
_attr_should_poll = False
@@ -87,7 +87,7 @@ class AugustEntity(Entity):
self._update_from_data()
class AugustDescriptionEntity(AugustEntity):
class AugustDescriptionEntity(AugustEntityMixin):
"""An August entity with a description."""
def __init__(
+17 -11
View File
@@ -63,17 +63,22 @@ async def async_setup_entry(
) -> None:
"""Set up the august event platform."""
data = config_entry.runtime_data
entities: list[AugustEventEntity] = [
AugustEventEntity(data, lock, description)
for description in TYPES_DOORBELL
for lock in data.locks
if (detail := data.get_device_detail(lock.device_id)) and detail.doorbell
]
entities.extend(
AugustEventEntity(data, doorbell, description)
for description in TYPES_DOORBELL + TYPES_VIDEO_DOORBELL
for doorbell in data.doorbells
)
entities: list[AugustEventEntity] = []
for lock in data.locks:
detail = data.get_device_detail(lock.device_id)
if detail.doorbell:
entities.extend(
AugustEventEntity(data, lock, description)
for description in TYPES_DOORBELL
)
for doorbell in data.doorbells:
entities.extend(
AugustEventEntity(data, doorbell, description)
for description in TYPES_DOORBELL + TYPES_VIDEO_DOORBELL
)
async_add_entities(entities)
@@ -81,6 +86,7 @@ class AugustEventEntity(AugustDescriptionEntity, EventEntity):
"""An august event entity."""
entity_description: AugustEventEntityDescription
_attr_has_entity_name = True
_last_activity: Activity | None = None
@callback
+2 -2
View File
@@ -19,7 +19,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.dt as dt_util
from . import AugustConfigEntry, AugustData
from .entity import AugustEntity
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ async def async_setup_entry(
async_add_entities(AugustLock(data, lock) for lock in data.locks)
class AugustLock(AugustEntity, RestoreEntity, LockEntity):
class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
"""Representation of an August lock."""
_attr_name = None
@@ -4,6 +4,10 @@
"codeowners": ["@bdraco"],
"config_flow": true,
"dhcp": [
{
"hostname": "yale-connect-plus",
"macaddress": "00177A*"
},
{
"hostname": "connect",
"macaddress": "D86162*"
@@ -24,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==8.6.4", "yalexs-ble==2.4.3"]
"requirements": ["yalexs==8.1.4", "yalexs-ble==2.4.3"]
}
+10 -11
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, cast
from typing import Any, Generic, TypeVar, cast
from yalexs.activity import ActivityType, LockOperationActivity
from yalexs.doorbell import Doorbell
@@ -42,7 +42,7 @@ from .const import (
OPERATION_METHOD_REMOTE,
OPERATION_METHOD_TAG,
)
from .entity import AugustDescriptionEntity, AugustEntity
from .entity import AugustDescriptionEntity, AugustEntityMixin
def _retrieve_device_battery_state(detail: LockDetail) -> int:
@@ -55,13 +55,14 @@ def _retrieve_linked_keypad_battery_state(detail: KeypadDetail) -> int | None:
return detail.battery_percentage
_T = TypeVar("_T", LockDetail, KeypadDetail)
@dataclass(frozen=True, kw_only=True)
class AugustSensorEntityDescription[T: LockDetail | KeypadDetail](
SensorEntityDescription
):
class AugustSensorEntityDescription(SensorEntityDescription, Generic[_T]):
"""Mixin for required keys."""
value_fn: Callable[[T], int | None]
value_fn: Callable[[_T], int | None]
SENSOR_TYPE_DEVICE_BATTERY = AugustSensorEntityDescription[LockDetail](
@@ -113,7 +114,7 @@ async def async_setup_entry(
async_add_entities(entities)
class AugustOperatorSensor(AugustEntity, RestoreSensor):
class AugustOperatorSensor(AugustEntityMixin, RestoreSensor):
"""Representation of an August lock operation sensor."""
_attr_translation_key = "operator"
@@ -197,12 +198,10 @@ class AugustOperatorSensor(AugustEntity, RestoreSensor):
self._operated_autorelock = last_attrs[ATTR_OPERATION_AUTORELOCK]
class AugustBatterySensor[T: LockDetail | KeypadDetail](
AugustDescriptionEntity, SensorEntity
):
class AugustBatterySensor(AugustDescriptionEntity, SensorEntity, Generic[_T]):
"""Representation of an August sensor."""
entity_description: AugustSensorEntityDescription[T]
entity_description: AugustSensorEntityDescription[_T]
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE
@@ -1,10 +1,4 @@
{
"issues": {
"yale_brand_migration": {
"title": "Yale Home has a new integration",
"description": "Add the [Yale integration]({migrate_url}), and remove the August integration as soon as possible to avoid an interruption in service. The Yale Home brand will stop working with the August integration soon and will be removed in a future release."
}
},
"config": {
"error": {
"unhandled": "Unhandled error: {error}",
+6 -1
View File
@@ -63,11 +63,16 @@ def _activity_time_based(latest: Activity) -> Activity | None:
"""Get the latest state of the sensor."""
start = latest.activity_start_time
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
if start <= datetime.now() <= end:
if start <= _native_datetime() <= end:
return latest
return None
def _native_datetime() -> datetime:
"""Return time in the format august uses without timezone."""
return datetime.now()
def retrieve_online_state(
data: AugustData, detail: DoorbellDetail | LockDetail
) -> bool:
@@ -75,10 +75,11 @@ class AuroraABBConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
def __init__(self) -> None:
def __init__(self):
"""Initialise the config flow."""
self.config = None
self._com_ports_list: list[str] | None = None
self._default_com_port: str | None = None
self._default_com_port = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -22,11 +22,11 @@ class AussieBroadbandConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
def __init__(self) -> None:
def __init__(self):
"""Initialize the config flow."""
self.data: dict = {}
self.options: dict = {CONF_SERVICES: []}
self.services: list[dict[str, Any]] = []
self.services: list[dict[str]] = []
self.client: AussieBB | None = None
self._reauth_username: str | None = None
@@ -99,11 +99,15 @@ class AussieBroadbandConfigFlow(ConfigFlow, domain=DOMAIN):
}
if not (errors := await self.async_auth(data)):
entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
assert entry
return self.async_update_reload_and_abort(entry, data=data)
entry = await self.async_set_unique_id(self._reauth_username.lower())
if entry:
self.hass.config_entries.async_update_entry(
entry,
data=data,
)
await self.hass.config_entries.async_reload(entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=self._reauth_username, data=data)
return self.async_show_form(
step_id="reauth_confirm",
@@ -10,10 +10,8 @@ blueprint:
selector:
entity:
filter:
- device_class: occupancy
domain: binary_sensor
- device_class: motion
domain: binary_sensor
device_class: motion
domain: binary_sensor
light_target:
name: Light
selector:
+5 -15
View File
@@ -9,20 +9,10 @@
}
},
"services": {
"turn_on": {
"service": "mdi:robot"
},
"turn_off": {
"service": "mdi:robot-off"
},
"toggle": {
"service": "mdi:robot"
},
"trigger": {
"service": "mdi:robot"
},
"reload": {
"service": "mdi:reload"
}
"turn_on": "mdi:robot",
"turn_off": "mdi:robot-off",
"toggle": "mdi:robot",
"trigger": "mdi:robot",
"reload": "mdi:reload"
}
}
+5 -2
View File
@@ -1,5 +1,6 @@
"""Config flow for AWS component."""
from collections.abc import Mapping
from typing import Any
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@@ -12,9 +13,11 @@ class AWSFlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
async def async_step_import(
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Import a config entry."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
return self.async_create_entry(title="configuration.yaml", data=import_data)
return self.async_create_entry(title="configuration.yaml", data=user_input)
@@ -6,7 +6,7 @@ import logging
from typing import Final
from aioazuredevops.client import DevOpsClient
from aioazuredevops.models.build import Build
from aioazuredevops.models.builds import Build
from aioazuredevops.models.core import Project
import aiohttp
@@ -2,7 +2,7 @@
from dataclasses import dataclass
from aioazuredevops.models.build import Build
from aioazuredevops.models.builds import Build
from aioazuredevops.models.core import Project
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/azure_devops",
"iot_class": "cloud_polling",
"loggers": ["aioazuredevops"],
"requirements": ["aioazuredevops==2.2.1"]
"requirements": ["aioazuredevops==2.1.1"]
}
@@ -8,7 +8,7 @@ from datetime import datetime
import logging
from typing import Any
from aioazuredevops.models.build import Build
from aioazuredevops.models.builds import Build
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -154,15 +154,17 @@ class AEHConfigFlow(ConfigFlow, domain=DOMAIN):
options=self._options,
)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Import config from configuration.yaml."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
if CONF_SEND_INTERVAL in import_data:
self._options[CONF_SEND_INTERVAL] = import_data.pop(CONF_SEND_INTERVAL)
if CONF_MAX_DELAY in import_data:
self._options[CONF_MAX_DELAY] = import_data.pop(CONF_MAX_DELAY)
self._data = import_data
if CONF_SEND_INTERVAL in import_config:
self._options[CONF_SEND_INTERVAL] = import_config.pop(CONF_SEND_INTERVAL)
if CONF_MAX_DELAY in import_config:
self._options[CONF_MAX_DELAY] = import_config.pop(CONF_MAX_DELAY)
self._data = import_config
errors = await validate_data(self._data)
if errors:
return self.async_abort(reason=errors["base"])
+1 -3
View File
@@ -1,7 +1,5 @@
{
"services": {
"create": {
"service": "mdi:cloud-upload"
}
"create": "mdi:cloud-upload"
}
}
+1 -3
View File
@@ -1,7 +1,5 @@
{
"services": {
"reload": {
"service": "mdi:reload"
}
"reload": "mdi:reload"
}
}
@@ -1,7 +1,5 @@
{
"services": {
"set_all_zones": {
"service": "mdi:home-sound-in"
}
"set_all_zones": "mdi:home-sound-in"
}
}
+17 -23
View File
@@ -35,11 +35,15 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
def host_port(data):
"""Return a list with host and port."""
return (data[CONF_HOST], data[CONF_PORT])
def create_schema(previous_input=None):
"""Create a schema with given values as default."""
if previous_input is not None:
host = previous_input[CONF_HOST]
port = previous_input[CONF_PORT]
host, port = host_port(previous_input)
else:
host = DEFAULT_HOST
port = DEFAULT_PORT
@@ -66,9 +70,9 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
def __init__(self) -> None:
def __init__(self):
"""Initialize the BleBox config flow."""
self.device_config: dict[str, Any] = {}
self.device_config = {}
def handle_step_exception(
self, step, exception, schema, host, port, message_id, log_fn
@@ -142,9 +146,7 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
},
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
async def async_step_user(self, user_input=None):
"""Handle initial user-triggered config step."""
hass = self.hass
schema = create_schema(user_input)
@@ -157,14 +159,14 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
description_placeholders={},
)
host = user_input[CONF_HOST]
port = user_input[CONF_PORT]
addr = host_port(user_input)
username = user_input.get(CONF_USERNAME)
password = user_input.get(CONF_PASSWORD)
for entry in self._async_current_entries():
if host == entry.data[CONF_HOST] and port == entry.data[CONF_PORT]:
if addr == host_port(entry.data):
host, port = addr
return self.async_abort(
reason=ADDRESS_ALREADY_CONFIGURED,
description_placeholders={"address": f"{host}:{port}"},
@@ -172,35 +174,27 @@ class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN):
websession = get_maybe_authenticated_session(hass, password, username)
api_host = ApiHost(
host, port, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER
)
api_host = ApiHost(*addr, DEFAULT_SETUP_TIMEOUT, websession, hass.loop, _LOGGER)
try:
product = await Box.async_from_host(api_host)
except UnsupportedBoxVersion as ex:
return self.handle_step_exception(
"user",
ex,
schema,
host,
port,
UNSUPPORTED_VERSION,
_LOGGER.debug,
"user", ex, schema, *addr, UNSUPPORTED_VERSION, _LOGGER.debug
)
except UnauthorizedRequest as ex:
return self.handle_step_exception(
"user", ex, schema, host, port, CANNOT_CONNECT, _LOGGER.error
"user", ex, schema, *addr, CANNOT_CONNECT, _LOGGER.error
)
except Error as ex:
return self.handle_step_exception(
"user", ex, schema, host, port, CANNOT_CONNECT, _LOGGER.warning
"user", ex, schema, *addr, CANNOT_CONNECT, _LOGGER.warning
)
except RuntimeError as ex:
return self.handle_step_exception(
"user", ex, schema, host, port, UNKNOWN, _LOGGER.error
"user", ex, schema, *addr, UNKNOWN, _LOGGER.error
)
# Check if configured but IP changed since
+6 -4
View File
@@ -60,9 +60,6 @@ COLOR_MODE_MAP = {
class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
"""Representation of BleBox lights."""
_attr_max_mireds = 370 # 1,000,000 divided by 2700 Kelvin = 370 Mireds
_attr_min_mireds = 154 # 1,000,000 divided by 6500 Kelvin = 154 Mireds
def __init__(self, feature: blebox_uniapi.light.Light) -> None:
"""Initialize a BleBox light."""
super().__init__(feature)
@@ -90,7 +87,12 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
Set values to _attr_ibutes if needed.
"""
return COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
color_mode_tmp = COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
if color_mode_tmp == ColorMode.COLOR_TEMP:
self._attr_min_mireds = 1
self._attr_max_mireds = 255
return color_mode_tmp
@property
def supported_color_modes(self):
+5 -15
View File
@@ -12,20 +12,10 @@
}
},
"services": {
"record": {
"service": "mdi:video-box"
},
"trigger_camera": {
"service": "mdi:image-refresh"
},
"save_video": {
"service": "mdi:file-video"
},
"save_recent_clips": {
"service": "mdi:file-video"
},
"send_pin": {
"service": "mdi:two-factor-authentication"
}
"record": "mdi:video-box",
"trigger_camera": "mdi:image-refresh",
"save_video": "mdi:file-video",
"save_recent_clips": "mdi:file-video",
"send_pin": "mdi:two-factor-authentication"
}
}
+4 -12
View File
@@ -1,16 +1,8 @@
{
"services": {
"join": {
"service": "mdi:link-variant"
},
"unjoin": {
"service": "mdi:link-variant-off"
},
"set_sleep_timer": {
"service": "mdi:sleep"
},
"clear_sleep_timer": {
"service": "mdi:sleep-off"
}
"join": "mdi:link-variant",
"unjoin": "mdi:link-variant-off",
"set_sleep_timer": "mdi:sleep",
"clear_sleep_timer": "mdi:sleep-off"
}
}
@@ -309,7 +309,7 @@ class BluesoundPlayer(MediaPlayerEntity):
return True
async def _poll_loop(self):
async def _start_poll_command(self):
"""Loop which polls the status of the player."""
while True:
try:
@@ -335,7 +335,7 @@ class BluesoundPlayer(MediaPlayerEntity):
await super().async_added_to_hass()
self._polling_task = self.hass.async_create_background_task(
self._poll_loop(),
self._start_poll_command(),
name=f"bluesound.polling_{self.host}:{self.port}",
)
@@ -345,9 +345,7 @@ class BluesoundPlayer(MediaPlayerEntity):
assert self._polling_task is not None
if self._polling_task.cancel():
# the sleeps in _poll_loop will raise CancelledError
with suppress(CancelledError):
await self._polling_task
await self._polling_task
self.hass.data[DATA_BLUESOUND].remove(self)
@@ -18,8 +18,8 @@
"bleak-retry-connector==3.5.0",
"bluetooth-adapters==0.19.4",
"bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.20.0",
"dbus-fast==2.24.0",
"habluetooth==3.4.0"
"bluetooth-data-tools==1.19.4",
"dbus-fast==2.23.0",
"habluetooth==3.3.2"
]
}
@@ -1,7 +1,5 @@
{
"services": {
"update": {
"service": "mdi:update"
}
"update": "mdi:update"
}
}
@@ -15,7 +15,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.ssl import get_default_context
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_INTERVALS
@@ -34,7 +33,6 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
entry.data[CONF_PASSWORD],
get_region_from_name(entry.data[CONF_REGION]),
observer_position=GPSPosition(hass.config.latitude, hass.config.longitude),
verify=get_default_context(),
)
self.read_only = entry.options[CONF_READ_ONLY]
self._entry = entry
@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"quality_scale": "platinum",
"requirements": ["bimmer-connected[china]==0.16.3"]
"requirements": ["bimmer-connected[china]==0.16.1"]
}
@@ -148,8 +148,7 @@
"cooling": "Cooling",
"heating": "Heating",
"inactive": "Inactive",
"standby": "Standby",
"ventilation": "Ventilation"
"standby": "Standby"
}
},
"front_left_current_pressure": {
+7 -21
View File
@@ -96,26 +96,12 @@
}
},
"services": {
"set_fan_speed_tracked_state": {
"service": "mdi:fan"
},
"set_switch_power_tracked_state": {
"service": "mdi:toggle-switch-variant"
},
"set_light_power_tracked_state": {
"service": "mdi:lightbulb"
},
"set_light_brightness_tracked_state": {
"service": "mdi:lightbulb-on"
},
"start_increasing_brightness": {
"service": "mdi:brightness-7"
},
"start_decreasing_brightness": {
"service": "mdi:brightness-1"
},
"stop": {
"service": "mdi:stop"
}
"set_fan_speed_tracked_state": "mdi:fan",
"set_switch_power_tracked_state": "mdi:toggle-switch-variant",
"set_light_power_tracked_state": "mdi:lightbulb",
"set_light_brightness_tracked_state": "mdi:lightbulb-on",
"start_increasing_brightness": "mdi:brightness-7",
"start_decreasing_brightness": "mdi:brightness-1",
"stop": "mdi:stop"
}
}
+1 -3
View File
@@ -7,8 +7,6 @@
}
},
"services": {
"send_message": {
"service": "mdi:cellphone-message"
}
"send_message": "mdi:cellphone-message"
}
}
@@ -5,7 +5,7 @@ import errno
from functools import partial
import logging
import socket
from typing import TYPE_CHECKING, Any
from typing import Any
import broadlink as blk
from broadlink.exceptions import (
@@ -39,11 +39,9 @@ class BroadlinkFlowHandler(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the Broadlink flow."""
self.device: blk.Device | None = None
self.device = None
async def async_set_device(
self, device: blk.Device, raise_on_progress: bool = True
) -> None:
async def async_set_device(self, device, raise_on_progress=True):
"""Define a device for the config flow."""
if device.type not in DEVICE_TYPES:
_LOGGER.error(
@@ -92,9 +90,7 @@ class BroadlinkFlowHandler(ConfigFlow, domain=DOMAIN):
await self.async_set_device(device)
return await self.async_step_auth()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
async def async_step_user(self, user_input=None):
"""Handle a flow initiated by the user."""
errors = {}
@@ -131,8 +127,6 @@ class BroadlinkFlowHandler(ConfigFlow, domain=DOMAIN):
)
return await self.async_step_auth()
if TYPE_CHECKING:
assert self.device
if device.mac == self.device.mac:
await self.async_set_device(device, raise_on_progress=False)
return await self.async_step_auth()
@@ -314,10 +308,10 @@ class BroadlinkFlowHandler(ConfigFlow, domain=DOMAIN):
step_id="finish", data_schema=vol.Schema(data_schema), errors=errors
)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
async def async_step_import(self, import_info):
"""Import a device."""
self._async_abort_entries_match({CONF_HOST: import_data[CONF_HOST]})
return await self.async_step_user(import_data)
self._async_abort_entries_match({CONF_HOST: import_info[CONF_HOST]})
return await self.async_step_user(import_info)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
+1 -3
View File
@@ -1,7 +1,5 @@
{
"services": {
"browse_url": {
"service": "mdi:web"
}
"browse_url": "mdi:web"
}
}
@@ -20,8 +20,5 @@ async def async_get_config_entry_diagnostics(
return {
"info": data.info.to_dict(),
"device": data.device.to_dict(),
"coordinator_data": {
"state": data.coordinator.data.state.to_dict(),
},
"static": data.static.to_dict(),
"state": data.coordinator.data.state.to_dict(),
}
+3 -3
View File
@@ -22,10 +22,10 @@ class BSBLanEntity(CoordinatorEntity[BSBLanUpdateCoordinator]):
def __init__(self, coordinator: BSBLanUpdateCoordinator, data: BSBLanData) -> None:
"""Initialize BSBLan entity."""
super().__init__(coordinator, data)
host = coordinator.config_entry.data["host"]
mac = data.device.MAC
host = self.coordinator.config_entry.data["host"]
mac = self.coordinator.config_entry.data["mac"]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mac)},
identifiers={(DOMAIN, data.device.MAC)},
connections={(CONNECTION_NETWORK_MAC, format_mac(mac))},
name=data.device.name,
manufacturer="BSBLAN Inc.",
@@ -7,9 +7,6 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
@@ -46,46 +43,33 @@ TRIGGERS_BY_EVENT_CLASS = {
EVENT_CLASS_DIMMER: {"rotate_left", "rotate_right"},
}
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{vol.Required(CONF_TYPE): str, vol.Required(CONF_SUBTYPE): str}
)
SCHEMA_BY_EVENT_CLASS = {
EVENT_CLASS_BUTTON: DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_BUTTON]),
vol.Required(CONF_SUBTYPE): vol.In(
TRIGGERS_BY_EVENT_CLASS[EVENT_CLASS_BUTTON]
),
}
),
EVENT_CLASS_DIMMER: DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_DIMMER]),
vol.Required(CONF_SUBTYPE): vol.In(
TRIGGERS_BY_EVENT_CLASS[EVENT_CLASS_DIMMER]
),
}
),
}
async def async_validate_trigger_config(
hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate trigger config."""
config = TRIGGER_SCHEMA(config)
event_class = config[CONF_TYPE]
event_type = config[CONF_SUBTYPE]
device_registry = dr.async_get(hass)
device = device_registry.async_get(config[CONF_DEVICE_ID])
assert device is not None
config_entries = [
hass.config_entries.async_get_entry(entry_id)
for entry_id in device.config_entries
]
bthome_config_entry = next(
iter(entry for entry in config_entries if entry and entry.domain == DOMAIN)
return SCHEMA_BY_EVENT_CLASS.get(config[CONF_TYPE], DEVICE_TRIGGER_BASE_SCHEMA)( # type: ignore[no-any-return]
config
)
event_classes: list[str] = bthome_config_entry.data.get(
CONF_DISCOVERED_EVENT_CLASSES, []
)
if event_class not in event_classes:
raise InvalidDeviceAutomationConfig(
f"BTHome trigger {event_class} is not valid for device "
f"{device} ({config[CONF_DEVICE_ID]})"
)
if event_type not in TRIGGERS_BY_EVENT_CLASS.get(event_class.split("_")[0], ()):
raise InvalidDeviceAutomationConfig(
f"BTHome trigger {event_type} is not valid for device "
f"{device} ({config[CONF_DEVICE_ID]})"
)
return config
async def async_get_triggers(
-11
View File
@@ -27,7 +27,6 @@ from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfConductivity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
@@ -357,16 +356,6 @@ SENSOR_DESCRIPTIONS = {
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.TOTAL,
),
# Conductivity (µS/cm)
(
BTHomeSensorDeviceClass.CONDUCTIVITY,
Units.CONDUCTIVITY,
): SensorEntityDescription(
key=f"{BTHomeSensorDeviceClass.CONDUCTIVITY}_{Units.CONDUCTIVITY}",
device_class=SensorDeviceClass.CONDUCTIVITY,
native_unit_of_measurement=UnitOfConductivity.MICROSIEMENS,
state_class=SensorStateClass.MEASUREMENT,
),
}
@@ -2,7 +2,6 @@
DOMAIN = "buienradar"
DEFAULT_TIMEOUT = 60
DEFAULT_TIMEFRAME = 60
DEFAULT_DIMENSION = 700
+8 -8
View File
@@ -1,9 +1,9 @@
"""Shared utilities for different supported platforms."""
from asyncio import timeout
from datetime import datetime, timedelta
from http import HTTPStatus
import logging
from typing import Any
import aiohttp
from buienradar.buienradar import parse_data
@@ -27,12 +27,12 @@ from buienradar.constants import (
from buienradar.urls import JSON_FEED_URL, json_precipitation_forecast_url
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
from .const import DEFAULT_TIMEOUT, SCHEDULE_NOK, SCHEDULE_OK
from .const import SCHEDULE_NOK, SCHEDULE_OK
__all__ = ["BrData"]
_LOGGER = logging.getLogger(__name__)
@@ -59,10 +59,10 @@ class BrData:
load_error_count: int = WARN_THRESHOLD
rain_error_count: int = WARN_THRESHOLD
def __init__(self, hass: HomeAssistant, coordinates, timeframe, devices) -> None:
def __init__(self, hass, coordinates, timeframe, devices):
"""Initialize the data object."""
self.devices = devices
self.data: dict[str, Any] | None = {}
self.data = {}
self.hass = hass
self.coordinates = coordinates
self.timeframe = timeframe
@@ -93,9 +93,9 @@ class BrData:
resp = None
try:
websession = async_get_clientsession(self.hass)
async with websession.get(
url, timeout=aiohttp.ClientTimeout(total=DEFAULT_TIMEOUT)
) as resp:
async with timeout(10):
resp = await websession.get(url)
result[STATUS_CODE] = resp.status
result[CONTENT] = await resp.text()
if resp.status == HTTPStatus.OK:
@@ -130,7 +130,7 @@ class BrWeather(WeatherEntity):
_attr_should_poll = False
_attr_supported_features = WeatherEntityFeature.FORECAST_DAILY
def __init__(self, config, coordinates) -> None:
def __init__(self, config, coordinates):
"""Initialize the platform with a data instance and station name."""
self._stationname = config.get(CONF_NAME, "Buienradar")
self._attr_name = self._stationname or f"BR {'(unknown station)'}"
+1 -3
View File
@@ -14,8 +14,6 @@
}
},
"services": {
"press": {
"service": "mdi:gesture-tap-button"
}
"press": "mdi:gesture-tap-button"
}
}
+3 -9
View File
@@ -9,14 +9,8 @@
}
},
"services": {
"create_event": {
"service": "mdi:calendar-plus"
},
"get_events": {
"service": "mdi:calendar-month"
},
"list_events": {
"service": "mdi:calendar-month"
}
"create_event": "mdi:calendar-plus",
"get_events": "mdi:calendar-month",
"list_events": "mdi:calendar-month"
}
}
+7 -21
View File
@@ -8,26 +8,12 @@
}
},
"services": {
"disable_motion_detection": {
"service": "mdi:motion-sensor-off"
},
"enable_motion_detection": {
"service": "mdi:motion-sensor"
},
"play_stream": {
"service": "mdi:play"
},
"record": {
"service": "mdi:record-rec"
},
"snapshot": {
"service": "mdi:camera"
},
"turn_off": {
"service": "mdi:video-off"
},
"turn_on": {
"service": "mdi:video"
}
"disable_motion_detection": "mdi:motion-sensor-off",
"enable_motion_detection": "mdi:motion-sensor",
"play_stream": "mdi:play",
"record": "mdi:record-rec",
"snapshot": "mdi:camera",
"turn_off": "mdi:video-off",
"turn_on": "mdi:video"
}
}
@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/camera",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["PyTurboJPEG==1.7.5"]
"requirements": ["PyTurboJPEG==1.7.1"]
}
@@ -54,9 +54,11 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return CanaryOptionsFlowHandler(config_entry)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
async def async_step_import(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by configuration file."""
return await self.async_step_user(import_data)
return await self.async_step_user(user_input)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
+5 -7
View File
@@ -29,11 +29,11 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
def __init__(self) -> None:
def __init__(self):
"""Initialize flow."""
self._ignore_cec = set[str]()
self._known_hosts = set[str]()
self._wanted_uuid = set[str]()
self._ignore_cec = set()
self._known_hosts = set()
self._wanted_uuid = set()
@staticmethod
@callback
@@ -43,9 +43,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return CastOptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
+1 -3
View File
@@ -1,7 +1,5 @@
{
"services": {
"show_lovelace_view": {
"service": "mdi:view-dashboard"
}
"show_lovelace_view": "mdi:view-dashboard"
}
}
@@ -95,9 +95,12 @@ class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN):
errors=self._errors,
)
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
async def async_step_import(
self,
user_input: Mapping[str, Any] | None = None,
) -> ConfigFlowResult:
"""Import a config entry.
Only host was required in the yaml file all other fields are optional
"""
return await self.async_step_user(import_data)
return await self.async_step_user(user_input)
+3 -9
View File
@@ -1,13 +1,7 @@
{
"services": {
"seek_forward": {
"service": "mdi:skip-forward"
},
"seek_backward": {
"service": "mdi:skip-backward"
},
"seek_by": {
"service": "mdi:timer-check-outline"
}
"seek_forward": "mdi:skip-forward",
"seek_backward": "mdi:skip-backward",
"seek_by": "mdi:timer-check-outline"
}
}
+10 -30
View File
@@ -56,35 +56,15 @@
}
},
"services": {
"set_fan_mode": {
"service": "mdi:fan"
},
"set_humidity": {
"service": "mdi:water-percent"
},
"set_swing_mode": {
"service": "mdi:arrow-oscillating"
},
"set_temperature": {
"service": "mdi:thermometer"
},
"set_aux_heat": {
"service": "mdi:radiator"
},
"set_preset_mode": {
"service": "mdi:sofa"
},
"set_hvac_mode": {
"service": "mdi:hvac"
},
"turn_on": {
"service": "mdi:power-on"
},
"turn_off": {
"service": "mdi:power-off"
},
"toggle": {
"service": "mdi:toggle-switch"
}
"set_fan_mode": "mdi:fan",
"set_humidity": "mdi:water-percent",
"set_swing_mode": "mdi:arrow-oscillating",
"set_temperature": "mdi:thermometer",
"set_aux_heat": "mdi:radiator",
"set_preset_mode": "mdi:sofa",
"set_hvac_mode": "mdi:hvac",
"turn_on": "mdi:power-on",
"turn_off": "mdi:power-off",
"toggle": "mdi:toggle-switch"
}
}
+2 -6
View File
@@ -1,10 +1,6 @@
{
"services": {
"remote_connect": {
"service": "mdi:cloud"
},
"remote_disconnect": {
"service": "mdi:cloud-off"
}
"remote_connect": "mdi:cloud",
"remote_disconnect": "mdi:cloud-off"
}
}

Some files were not shown because too many files have changed in this diff Show More