mirror of
https://github.com/home-assistant/core.git
synced 2025-08-11 08:35:15 +02:00
Merge branch 'dev' into heatpump
This commit is contained in:
@@ -359,7 +359,6 @@ omit =
|
|||||||
homeassistant/components/escea/__init__.py
|
homeassistant/components/escea/__init__.py
|
||||||
homeassistant/components/escea/climate.py
|
homeassistant/components/escea/climate.py
|
||||||
homeassistant/components/escea/discovery.py
|
homeassistant/components/escea/discovery.py
|
||||||
homeassistant/components/esphome/manager.py
|
|
||||||
homeassistant/components/etherscan/sensor.py
|
homeassistant/components/etherscan/sensor.py
|
||||||
homeassistant/components/eufy/*
|
homeassistant/components/eufy/*
|
||||||
homeassistant/components/eufylife_ble/__init__.py
|
homeassistant/components/eufylife_ble/__init__.py
|
||||||
|
10
build.yaml
10
build.yaml
@@ -1,10 +1,10 @@
|
|||||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.01.0
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.02.0
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.01.0
|
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.02.0
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.01.0
|
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.02.0
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.01.0
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.02.0
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.01.0
|
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.02.0
|
||||||
codenotary:
|
codenotary:
|
||||||
signer: notary@home-assistant.io
|
signer: notary@home-assistant.io
|
||||||
base_image: notary@home-assistant.io
|
base_image: notary@home-assistant.io
|
||||||
|
@@ -74,6 +74,7 @@ class AdaxDevice(ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_target_temperature_step = PRECISION_WHOLE
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
||||||
"""Initialize the heater."""
|
"""Initialize the heater."""
|
||||||
|
@@ -83,6 +83,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
|||||||
_attr_max_temp = 32
|
_attr_max_temp = 32
|
||||||
_attr_min_temp = 16
|
_attr_min_temp = 16
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||||
"""Initialize an AdvantageAir AC unit."""
|
"""Initialize an AdvantageAir AC unit."""
|
||||||
@@ -202,11 +203,16 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
|||||||
"""AdvantageAir MyTemp Zone control."""
|
"""AdvantageAir MyTemp Zone control."""
|
||||||
|
|
||||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_target_temperature_step = PRECISION_WHOLE
|
_attr_target_temperature_step = PRECISION_WHOLE
|
||||||
_attr_max_temp = 32
|
_attr_max_temp = 32
|
||||||
_attr_min_temp = 16
|
_attr_min_temp = 16
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
|
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
|
||||||
"""Initialize an AdvantageAir Zone control."""
|
"""Initialize an AdvantageAir Zone control."""
|
||||||
|
@@ -23,6 +23,13 @@ from .const import DOMAIN, MFCT_ID
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SERVICE_UUIDS = [
|
||||||
|
"b42e1f6e-ade7-11e4-89d3-123b93f75cba",
|
||||||
|
"b42e4a8e-ade7-11e4-89d3-123b93f75cba",
|
||||||
|
"b42e1c08-ade7-11e4-89d3-123b93f75cba",
|
||||||
|
"b42e3882-ade7-11e4-89d3-123b93f75cba",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Discovery:
|
class Discovery:
|
||||||
@@ -147,6 +154,9 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
if MFCT_ID not in discovery_info.manufacturer_data:
|
if MFCT_ID not in discovery_info.manufacturer_data:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not any(uuid in SERVICE_UUIDS for uuid in discovery_info.service_uuids):
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
device = await self._get_device_data(discovery_info)
|
device = await self._get_device_data(discovery_info)
|
||||||
except AirthingsDeviceUpdateError:
|
except AirthingsDeviceUpdateError:
|
||||||
|
@@ -117,6 +117,7 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
|||||||
_attr_name = None
|
_attr_name = None
|
||||||
_speeds: dict[int, str] = {}
|
_speeds: dict[int, str] = {}
|
||||||
_speeds_reverse: dict[str, int] = {}
|
_speeds_reverse: dict[str, int] = {}
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -129,7 +130,11 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
|||||||
super().__init__(coordinator, entry, system_zone_id, zone_data)
|
super().__init__(coordinator, entry, system_zone_id, zone_data)
|
||||||
|
|
||||||
self._attr_unique_id = f"{self._attr_unique_id}_{system_zone_id}"
|
self._attr_unique_id = f"{self._attr_unique_id}_{system_zone_id}"
|
||||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
self._attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
self._attr_target_temperature_step = API_TEMPERATURE_STEP
|
self._attr_target_temperature_step = API_TEMPERATURE_STEP
|
||||||
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
||||||
self.get_airzone_value(AZD_TEMP_UNIT)
|
self.get_airzone_value(AZD_TEMP_UNIT)
|
||||||
|
@@ -144,8 +144,13 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
|||||||
"""Define an Airzone Cloud climate."""
|
"""Define an Airzone Cloud climate."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
@@ -153,10 +153,15 @@ class AmbiclimateEntity(ClimateEntity):
|
|||||||
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_target_temperature_step = 1
|
_attr_target_temperature_step = 1
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, heater: AmbiclimateDevice, store: Store[dict[str, Any]]) -> None:
|
def __init__(self, heater: AmbiclimateDevice, store: Store[dict[str, Any]]) -> None:
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
@@ -46,6 +46,7 @@ class AtagThermostat(AtagEntity, ClimateEntity):
|
|||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||||
)
|
)
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator, atag_id):
|
def __init__(self, coordinator, atag_id):
|
||||||
"""Initialize an Atag climate device."""
|
"""Initialize an Atag climate device."""
|
||||||
|
@@ -578,6 +578,7 @@ def websocket_refresh_tokens(
|
|||||||
connection.send_result(msg["id"], tokens)
|
connection.send_result(msg["id"], tokens)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "auth/delete_refresh_token",
|
vol.Required("type"): "auth/delete_refresh_token",
|
||||||
@@ -585,8 +586,7 @@ def websocket_refresh_tokens(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.ws_require_user()
|
@websocket_api.ws_require_user()
|
||||||
@websocket_api.async_response
|
def websocket_delete_refresh_token(
|
||||||
async def websocket_delete_refresh_token(
|
|
||||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a delete refresh token request."""
|
"""Handle a delete refresh token request."""
|
||||||
@@ -601,6 +601,7 @@ async def websocket_delete_refresh_token(
|
|||||||
connection.send_result(msg["id"], {})
|
connection.send_result(msg["id"], {})
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "auth/delete_all_refresh_tokens",
|
vol.Required("type"): "auth/delete_all_refresh_tokens",
|
||||||
@@ -609,8 +610,7 @@ async def websocket_delete_refresh_token(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.ws_require_user()
|
@websocket_api.ws_require_user()
|
||||||
@websocket_api.async_response
|
def websocket_delete_all_refresh_tokens(
|
||||||
async def websocket_delete_all_refresh_tokens(
|
|
||||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle delete all refresh tokens request."""
|
"""Handle delete all refresh tokens request."""
|
||||||
|
@@ -33,10 +33,15 @@ async def async_setup_entry(
|
|||||||
class BAFAutoComfort(BAFEntity, ClimateEntity):
|
class BAFAutoComfort(BAFEntity, ClimateEntity):
|
||||||
"""BAF climate auto comfort."""
|
"""BAF climate auto comfort."""
|
||||||
|
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY]
|
_attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY]
|
||||||
_attr_translation_key = "auto_comfort"
|
_attr_translation_key = "auto_comfort"
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_attrs(self) -> None:
|
def _async_update_attrs(self) -> None:
|
||||||
|
@@ -63,6 +63,7 @@ class BalboaClimateEntity(BalboaEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, client: SpaClient) -> None:
|
def __init__(self, client: SpaClient) -> None:
|
||||||
"""Initialize the climate entity."""
|
"""Initialize the climate entity."""
|
||||||
|
@@ -53,8 +53,13 @@ async def async_setup_entry(
|
|||||||
class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEntity):
|
class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEntity):
|
||||||
"""Representation of a BleBox climate feature (saunaBox)."""
|
"""Representation of a BleBox climate feature (saunaBox)."""
|
||||||
|
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self):
|
||||||
|
@@ -35,9 +35,14 @@ class BroadlinkThermostat(ClimateEntity, BroadlinkEntity):
|
|||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF, HVACMode.AUTO]
|
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF, HVACMode.AUTO]
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_target_temperature_step = PRECISION_HALVES
|
_attr_target_temperature_step = PRECISION_HALVES
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, device: BroadlinkDevice) -> None:
|
def __init__(self, device: BroadlinkDevice) -> None:
|
||||||
"""Initialize the climate entity."""
|
"""Initialize the climate entity."""
|
||||||
|
@@ -73,12 +73,16 @@ class BSBLANClimate(
|
|||||||
_attr_name = None
|
_attr_name = None
|
||||||
# Determine preset modes
|
# Determine preset modes
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
_attr_preset_modes = PRESET_MODES
|
_attr_preset_modes = PRESET_MODES
|
||||||
|
|
||||||
# Determine hvac modes
|
# Determine hvac modes
|
||||||
_attr_hvac_modes = HVAC_MODES
|
_attr_hvac_modes = HVAC_MODES
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@@ -98,6 +98,6 @@ class TurboJPEGSingleton:
|
|||||||
TurboJPEGSingleton.__instance = TurboJPEG()
|
TurboJPEGSingleton.__instance = TurboJPEG()
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception(
|
_LOGGER.exception(
|
||||||
"Error loading libturbojpeg; Cameras may impact HomeKit performance"
|
"Error loading libturbojpeg; Camera snapshot performance will be sub-optimal"
|
||||||
)
|
)
|
||||||
TurboJPEGSingleton.__instance = False
|
TurboJPEGSingleton.__instance = False
|
||||||
|
@@ -4,8 +4,11 @@ from __future__ import annotations
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aioelectricitymaps import ElectricityMaps
|
from aioelectricitymaps import (
|
||||||
from aioelectricitymaps.exceptions import ElectricityMapsError, InvalidToken
|
ElectricityMaps,
|
||||||
|
ElectricityMapsError,
|
||||||
|
ElectricityMapsInvalidTokenError,
|
||||||
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
@@ -146,7 +149,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await fetch_latest_carbon_intensity(self.hass, em, data)
|
await fetch_latest_carbon_intensity(self.hass, em, data)
|
||||||
except InvalidToken:
|
except ElectricityMapsInvalidTokenError:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except ElectricityMapsError:
|
except ElectricityMapsError:
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
|
@@ -4,9 +4,12 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aioelectricitymaps import ElectricityMaps
|
from aioelectricitymaps import (
|
||||||
from aioelectricitymaps.exceptions import ElectricityMapsError, InvalidToken
|
CarbonIntensityResponse,
|
||||||
from aioelectricitymaps.models import CarbonIntensityResponse
|
ElectricityMaps,
|
||||||
|
ElectricityMapsError,
|
||||||
|
ElectricityMapsInvalidTokenError,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -43,7 +46,7 @@ class CO2SignalCoordinator(DataUpdateCoordinator[CarbonIntensityResponse]):
|
|||||||
return await fetch_latest_carbon_intensity(
|
return await fetch_latest_carbon_intensity(
|
||||||
self.hass, self.client, self.config_entry.data
|
self.hass, self.client, self.config_entry.data
|
||||||
)
|
)
|
||||||
except InvalidToken as err:
|
except ElectricityMapsInvalidTokenError as err:
|
||||||
raise ConfigEntryAuthFailed from err
|
raise ConfigEntryAuthFailed from err
|
||||||
except ElectricityMapsError as err:
|
except ElectricityMapsError as err:
|
||||||
raise UpdateFailed(str(err)) from err
|
raise UpdateFailed(str(err)) from err
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioelectricitymaps"],
|
"loggers": ["aioelectricitymaps"],
|
||||||
"requirements": ["aioelectricitymaps==0.2.0"]
|
"requirements": ["aioelectricitymaps==0.3.0"]
|
||||||
}
|
}
|
||||||
|
@@ -349,7 +349,7 @@ async def websocket_hass_agent_debug(
|
|||||||
},
|
},
|
||||||
# Slot values that would be received by the intent
|
# Slot values that would be received by the intent
|
||||||
"slots": { # direct access to values
|
"slots": { # direct access to values
|
||||||
entity_key: entity.value
|
entity_key: entity.text or entity.value
|
||||||
for entity_key, entity in result.entities.items()
|
for entity_key, entity in result.entities.items()
|
||||||
},
|
},
|
||||||
# Extra slot details, such as the originally matched text
|
# Extra slot details, such as the originally matched text
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""Standard conversation implementation for Home Assistant."""
|
"""Standard conversation implementation for Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -12,22 +13,15 @@ import re
|
|||||||
from typing import IO, Any
|
from typing import IO, Any
|
||||||
|
|
||||||
from hassil.expression import Expression, ListReference, Sequence
|
from hassil.expression import Expression, ListReference, Sequence
|
||||||
from hassil.intents import (
|
from hassil.intents import Intents, SlotList, TextSlotList, WildcardSlotList
|
||||||
Intents,
|
|
||||||
ResponseType,
|
|
||||||
SlotList,
|
|
||||||
TextSlotList,
|
|
||||||
WildcardSlotList,
|
|
||||||
)
|
|
||||||
from hassil.recognize import (
|
from hassil.recognize import (
|
||||||
MISSING_ENTITY,
|
MISSING_ENTITY,
|
||||||
RecognizeResult,
|
RecognizeResult,
|
||||||
UnmatchedEntity,
|
|
||||||
UnmatchedTextEntity,
|
UnmatchedTextEntity,
|
||||||
recognize_all,
|
recognize_all,
|
||||||
)
|
)
|
||||||
from hassil.util import merge_dict
|
from hassil.util import merge_dict
|
||||||
from home_assistant_intents import get_intents, get_languages
|
from home_assistant_intents import ErrorKey, get_intents, get_languages
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from homeassistant import core, setup
|
from homeassistant import core, setup
|
||||||
@@ -238,7 +232,10 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use last non-empty result as response
|
# Use last non-empty result as response.
|
||||||
|
#
|
||||||
|
# There may be multiple copies of a trigger running when editing in
|
||||||
|
# the UI, so it's critical that we filter out empty responses here.
|
||||||
response_text: str | None = None
|
response_text: str | None = None
|
||||||
for trigger_response in trigger_responses:
|
for trigger_response in trigger_responses:
|
||||||
response_text = response_text or trigger_response
|
response_text = response_text or trigger_response
|
||||||
@@ -246,7 +243,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
# Convert to conversation result
|
# Convert to conversation result
|
||||||
response = intent.IntentResponse(language=language)
|
response = intent.IntentResponse(language=language)
|
||||||
response.response_type = intent.IntentResponseType.ACTION_DONE
|
response.response_type = intent.IntentResponseType.ACTION_DONE
|
||||||
response.async_set_speech(response_text or "")
|
response.async_set_speech(response_text or "Done")
|
||||||
|
|
||||||
return ConversationResult(response=response)
|
return ConversationResult(response=response)
|
||||||
|
|
||||||
@@ -259,7 +256,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
return _make_error_result(
|
return _make_error_result(
|
||||||
language,
|
language,
|
||||||
intent.IntentResponseErrorCode.NO_INTENT_MATCH,
|
intent.IntentResponseErrorCode.NO_INTENT_MATCH,
|
||||||
self._get_error_text(ResponseType.NO_INTENT, lang_intents),
|
self._get_error_text(ErrorKey.NO_INTENT, lang_intents),
|
||||||
conversation_id,
|
conversation_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -268,14 +265,14 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Recognized intent '%s' for template '%s' but had unmatched: %s",
|
"Recognized intent '%s' for template '%s' but had unmatched: %s",
|
||||||
result.intent.name,
|
result.intent.name,
|
||||||
result.intent_sentence.text
|
(
|
||||||
if result.intent_sentence is not None
|
result.intent_sentence.text
|
||||||
else "",
|
if result.intent_sentence is not None
|
||||||
|
else ""
|
||||||
|
),
|
||||||
result.unmatched_entities_list,
|
result.unmatched_entities_list,
|
||||||
)
|
)
|
||||||
error_response_type, error_response_args = _get_unmatched_response(
|
error_response_type, error_response_args = _get_unmatched_response(result)
|
||||||
result.unmatched_entities
|
|
||||||
)
|
|
||||||
return _make_error_result(
|
return _make_error_result(
|
||||||
language,
|
language,
|
||||||
intent.IntentResponseErrorCode.NO_VALID_TARGETS,
|
intent.IntentResponseErrorCode.NO_VALID_TARGETS,
|
||||||
@@ -291,7 +288,8 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
|
|
||||||
# Slot values to pass to the intent
|
# Slot values to pass to the intent
|
||||||
slots = {
|
slots = {
|
||||||
entity.name: {"value": entity.value} for entity in result.entities_list
|
entity.name: {"value": entity.value, "text": entity.text or entity.value}
|
||||||
|
for entity in result.entities_list
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -325,7 +323,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
return _make_error_result(
|
return _make_error_result(
|
||||||
language,
|
language,
|
||||||
intent.IntentResponseErrorCode.FAILED_TO_HANDLE,
|
intent.IntentResponseErrorCode.FAILED_TO_HANDLE,
|
||||||
self._get_error_text(ResponseType.HANDLE_ERROR, lang_intents),
|
self._get_error_text(ErrorKey.HANDLE_ERROR, lang_intents),
|
||||||
conversation_id,
|
conversation_id,
|
||||||
)
|
)
|
||||||
except intent.IntentUnexpectedError:
|
except intent.IntentUnexpectedError:
|
||||||
@@ -333,7 +331,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
return _make_error_result(
|
return _make_error_result(
|
||||||
language,
|
language,
|
||||||
intent.IntentResponseErrorCode.UNKNOWN,
|
intent.IntentResponseErrorCode.UNKNOWN,
|
||||||
self._get_error_text(ResponseType.HANDLE_ERROR, lang_intents),
|
self._get_error_text(ErrorKey.HANDLE_ERROR, lang_intents),
|
||||||
conversation_id,
|
conversation_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -480,9 +478,11 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
for entity_name, entity_value in recognize_result.entities.items()
|
for entity_name, entity_value in recognize_result.entities.items()
|
||||||
},
|
},
|
||||||
# First matched or unmatched state
|
# First matched or unmatched state
|
||||||
"state": template.TemplateState(self.hass, state1)
|
"state": (
|
||||||
if state1 is not None
|
template.TemplateState(self.hass, state1)
|
||||||
else None,
|
if state1 is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
"query": {
|
"query": {
|
||||||
# Entity states that matched the query (e.g, "on")
|
# Entity states that matched the query (e.g, "on")
|
||||||
"matched": [
|
"matched": [
|
||||||
@@ -740,7 +740,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
|
|
||||||
if not entity:
|
if not entity:
|
||||||
# Default name
|
# Default name
|
||||||
entity_names.append((state.name, state.name, context))
|
entity_names.append((state.name, state.entity_id, context))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if entity.aliases:
|
if entity.aliases:
|
||||||
@@ -748,10 +748,10 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
if not alias.strip():
|
if not alias.strip():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
entity_names.append((alias, alias, context))
|
entity_names.append((alias, state.entity_id, context))
|
||||||
|
|
||||||
# Default name
|
# Default name
|
||||||
entity_names.append((state.name, state.name, context))
|
entity_names.append((state.name, state.entity_id, context))
|
||||||
|
|
||||||
# Expose all areas
|
# Expose all areas
|
||||||
areas = ar.async_get(self.hass)
|
areas = ar.async_get(self.hass)
|
||||||
@@ -791,11 +791,11 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
if device_area is None:
|
if device_area is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return {"area": device_area.id}
|
return {"area": {"value": device_area.id, "text": device_area.name}}
|
||||||
|
|
||||||
def _get_error_text(
|
def _get_error_text(
|
||||||
self,
|
self,
|
||||||
response_type: ResponseType,
|
error_key: ErrorKey,
|
||||||
lang_intents: LanguageIntents | None,
|
lang_intents: LanguageIntents | None,
|
||||||
**response_args,
|
**response_args,
|
||||||
) -> str:
|
) -> str:
|
||||||
@@ -803,7 +803,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||||||
if lang_intents is None:
|
if lang_intents is None:
|
||||||
return _DEFAULT_ERROR_TEXT
|
return _DEFAULT_ERROR_TEXT
|
||||||
|
|
||||||
response_key = response_type.value
|
response_key = error_key.value
|
||||||
response_str = (
|
response_str = (
|
||||||
lang_intents.error_responses.get(response_key) or _DEFAULT_ERROR_TEXT
|
lang_intents.error_responses.get(response_key) or _DEFAULT_ERROR_TEXT
|
||||||
)
|
)
|
||||||
@@ -916,59 +916,72 @@ def _make_error_result(
|
|||||||
return ConversationResult(response, conversation_id)
|
return ConversationResult(response, conversation_id)
|
||||||
|
|
||||||
|
|
||||||
def _get_unmatched_response(
|
def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str, Any]]:
|
||||||
unmatched_entities: dict[str, UnmatchedEntity],
|
"""Get key and template arguments for error when there are unmatched intent entities/slots."""
|
||||||
) -> tuple[ResponseType, dict[str, Any]]:
|
|
||||||
error_response_type = ResponseType.NO_INTENT
|
|
||||||
error_response_args: dict[str, Any] = {}
|
|
||||||
|
|
||||||
if unmatched_name := unmatched_entities.get("name"):
|
# Filter out non-text and missing context entities
|
||||||
# Unmatched device or entity
|
unmatched_text: dict[str, str] = {
|
||||||
assert isinstance(unmatched_name, UnmatchedTextEntity)
|
key: entity.text.strip()
|
||||||
error_response_type = ResponseType.NO_ENTITY
|
for key, entity in result.unmatched_entities.items()
|
||||||
error_response_args["entity"] = unmatched_name.text
|
if isinstance(entity, UnmatchedTextEntity) and entity.text != MISSING_ENTITY
|
||||||
|
}
|
||||||
|
|
||||||
elif unmatched_area := unmatched_entities.get("area"):
|
if unmatched_area := unmatched_text.get("area"):
|
||||||
# Unmatched area
|
# area only
|
||||||
assert isinstance(unmatched_area, UnmatchedTextEntity)
|
return ErrorKey.NO_AREA, {"area": unmatched_area}
|
||||||
error_response_type = ResponseType.NO_AREA
|
|
||||||
error_response_args["area"] = unmatched_area.text
|
|
||||||
|
|
||||||
return error_response_type, error_response_args
|
# Area may still have matched
|
||||||
|
matched_area: str | None = None
|
||||||
|
if matched_area_entity := result.entities.get("area"):
|
||||||
|
matched_area = matched_area_entity.text.strip()
|
||||||
|
|
||||||
|
if unmatched_name := unmatched_text.get("name"):
|
||||||
|
if matched_area:
|
||||||
|
# device in area
|
||||||
|
return ErrorKey.NO_ENTITY_IN_AREA, {
|
||||||
|
"entity": unmatched_name,
|
||||||
|
"area": matched_area,
|
||||||
|
}
|
||||||
|
|
||||||
|
# device only
|
||||||
|
return ErrorKey.NO_ENTITY, {"entity": unmatched_name}
|
||||||
|
|
||||||
|
# Default error
|
||||||
|
return ErrorKey.NO_INTENT, {}
|
||||||
|
|
||||||
|
|
||||||
def _get_no_states_matched_response(
|
def _get_no_states_matched_response(
|
||||||
no_states_error: intent.NoStatesMatchedError,
|
no_states_error: intent.NoStatesMatchedError,
|
||||||
) -> tuple[ResponseType, dict[str, Any]]:
|
) -> tuple[ErrorKey, dict[str, Any]]:
|
||||||
"""Return error response type and template arguments for error."""
|
"""Return key and template arguments for error when intent returns no matching states."""
|
||||||
if not (
|
|
||||||
no_states_error.area
|
|
||||||
and (no_states_error.device_classes or no_states_error.domains)
|
|
||||||
):
|
|
||||||
# Device class and domain must be paired with an area for the error
|
|
||||||
# message.
|
|
||||||
return ResponseType.NO_INTENT, {}
|
|
||||||
|
|
||||||
error_response_args: dict[str, Any] = {"area": no_states_error.area}
|
# Device classes should be checked before domains
|
||||||
|
|
||||||
# Check device classes first, since it's more specific than domain
|
|
||||||
if no_states_error.device_classes:
|
if no_states_error.device_classes:
|
||||||
# No exposed entities of a particular class in an area.
|
device_class = next(iter(no_states_error.device_classes)) # first device class
|
||||||
# Example: "close the bedroom windows"
|
if no_states_error.area:
|
||||||
#
|
# device_class in area
|
||||||
# Only use the first device class for the error message
|
return ErrorKey.NO_DEVICE_CLASS_IN_AREA, {
|
||||||
error_response_args["device_class"] = next(iter(no_states_error.device_classes))
|
"device_class": device_class,
|
||||||
|
"area": no_states_error.area,
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseType.NO_DEVICE_CLASS, error_response_args
|
# device_class only
|
||||||
|
return ErrorKey.NO_DEVICE_CLASS, {"device_class": device_class}
|
||||||
|
|
||||||
# No exposed entities of a domain in an area.
|
if no_states_error.domains:
|
||||||
# Example: "turn on lights in kitchen"
|
domain = next(iter(no_states_error.domains)) # first domain
|
||||||
assert no_states_error.domains
|
if no_states_error.area:
|
||||||
#
|
# domain in area
|
||||||
# Only use the first domain for the error message
|
return ErrorKey.NO_DOMAIN_IN_AREA, {
|
||||||
error_response_args["domain"] = next(iter(no_states_error.domains))
|
"domain": domain,
|
||||||
|
"area": no_states_error.area,
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseType.NO_DOMAIN, error_response_args
|
# domain only
|
||||||
|
return ErrorKey.NO_DOMAIN, {"domain": domain}
|
||||||
|
|
||||||
|
# Default error
|
||||||
|
return ErrorKey.NO_INTENT, {}
|
||||||
|
|
||||||
|
|
||||||
def _collect_list_references(expression: Expression, list_names: set[str]) -> None:
|
def _collect_list_references(expression: Expression, list_names: set[str]) -> None:
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["hassil==1.6.0", "home-assistant-intents==2024.1.29"]
|
"requirements": ["hassil==1.6.1", "home-assistant-intents==2024.2.2"]
|
||||||
}
|
}
|
||||||
|
@@ -98,7 +98,12 @@ async def async_attach_trigger(
|
|||||||
# mypy does not understand the type narrowing, unclear why
|
# mypy does not understand the type narrowing, unclear why
|
||||||
return automation_result.conversation_response # type: ignore[return-value]
|
return automation_result.conversation_response # type: ignore[return-value]
|
||||||
|
|
||||||
return "Done"
|
# It's important to return None here instead of a string.
|
||||||
|
#
|
||||||
|
# When editing in the UI, a copy of this trigger is registered.
|
||||||
|
# If we return a string from here, there is a race condition between the
|
||||||
|
# two trigger copies for who will provide a response.
|
||||||
|
return None
|
||||||
|
|
||||||
default_agent = await _get_agent_manager(hass).async_get_agent(HOME_ASSISTANT_AGENT)
|
default_agent = await _get_agent_manager(hass).async_get_agent(HOME_ASSISTANT_AGENT)
|
||||||
assert isinstance(default_agent, DefaultAgent)
|
assert isinstance(default_agent, DefaultAgent)
|
||||||
|
@@ -100,6 +100,7 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
|
|||||||
TYPE = DOMAIN
|
TYPE = DOMAIN
|
||||||
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, device: Thermostat, gateway: DeconzGateway) -> None:
|
def __init__(self, device: Thermostat, gateway: DeconzGateway) -> None:
|
||||||
"""Set up thermostat device."""
|
"""Set up thermostat device."""
|
||||||
@@ -119,7 +120,11 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
|
|||||||
HVAC_MODE_TO_DECONZ[item]: item for item in self._attr_hvac_modes
|
HVAC_MODE_TO_DECONZ[item]: item for item in self._attr_hvac_modes
|
||||||
}
|
}
|
||||||
|
|
||||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
self._attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
if device.fan_mode:
|
if device.fan_mode:
|
||||||
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
|
||||||
|
@@ -97,6 +97,7 @@ class DemoClimate(ClimateEntity):
|
|||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
_attr_translation_key = "ubercool"
|
_attr_translation_key = "ubercool"
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -137,6 +138,9 @@ class DemoClimate(ClimateEntity):
|
|||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||||
)
|
)
|
||||||
|
self._attr_supported_features |= (
|
||||||
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
self._target_temperature = target_temperature
|
self._target_temperature = target_temperature
|
||||||
self._target_humidity = target_humidity
|
self._target_humidity = target_humidity
|
||||||
self._unit_of_measurement = unit_of_measurement
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
1
homeassistant/components/duquesne_light/__init__.py
Normal file
1
homeassistant/components/duquesne_light/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Virtual integration: Duquesne Light."""
|
6
homeassistant/components/duquesne_light/manifest.json
Normal file
6
homeassistant/components/duquesne_light/manifest.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"domain": "duquesne_light",
|
||||||
|
"name": "Duquesne Light",
|
||||||
|
"integration_type": "virtual",
|
||||||
|
"supported_by": "opower"
|
||||||
|
}
|
@@ -75,7 +75,7 @@ async def _validate_input(
|
|||||||
rest_config = create_rest_config(
|
rest_config = create_rest_config(
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
country=country,
|
alpha_2_country=country,
|
||||||
override_rest_url=rest_url,
|
override_rest_url=rest_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -266,6 +266,10 @@ class EcovacsConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
# If not we will inform the user about the mismatch.
|
# If not we will inform the user about the mismatch.
|
||||||
error = None
|
error = None
|
||||||
placeholders = None
|
placeholders = None
|
||||||
|
|
||||||
|
# Convert the country to upper case as ISO 3166-1 alpha-2 country codes are upper case
|
||||||
|
user_input[CONF_COUNTRY] = user_input[CONF_COUNTRY].upper()
|
||||||
|
|
||||||
if len(user_input[CONF_COUNTRY]) != 2:
|
if len(user_input[CONF_COUNTRY]) != 2:
|
||||||
error = "invalid_country_length"
|
error = "invalid_country_length"
|
||||||
placeholders = {"countries_url": "https://www.iso.org/obp/ui/#search/code/"}
|
placeholders = {"countries_url": "https://www.iso.org/obp/ui/#search/code/"}
|
||||||
|
@@ -49,7 +49,7 @@ class EcovacsController:
|
|||||||
create_rest_config(
|
create_rest_config(
|
||||||
aiohttp_client.async_get_clientsession(self._hass),
|
aiohttp_client.async_get_clientsession(self._hass),
|
||||||
device_id=self._device_id,
|
device_id=self._device_id,
|
||||||
country=country,
|
alpha_2_country=country,
|
||||||
override_rest_url=config.get(CONF_OVERRIDE_REST_URL),
|
override_rest_url=config.get(CONF_OVERRIDE_REST_URL),
|
||||||
),
|
),
|
||||||
config[CONF_USERNAME],
|
config[CONF_USERNAME],
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||||
"requirements": ["py-sucks==0.9.8", "deebot-client==5.0.0"]
|
"requirements": ["py-sucks==0.9.8", "deebot-client==5.1.0"]
|
||||||
}
|
}
|
||||||
|
@@ -47,13 +47,13 @@
|
|||||||
"name": "Relocate"
|
"name": "Relocate"
|
||||||
},
|
},
|
||||||
"reset_lifespan_brush": {
|
"reset_lifespan_brush": {
|
||||||
"name": "Reset brush lifespan"
|
"name": "Reset main brush lifespan"
|
||||||
},
|
},
|
||||||
"reset_lifespan_filter": {
|
"reset_lifespan_filter": {
|
||||||
"name": "Reset filter lifespan"
|
"name": "Reset filter lifespan"
|
||||||
},
|
},
|
||||||
"reset_lifespan_side_brush": {
|
"reset_lifespan_side_brush": {
|
||||||
"name": "Reset side brush lifespan"
|
"name": "Reset side brushes lifespan"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
@@ -79,13 +79,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lifespan_brush": {
|
"lifespan_brush": {
|
||||||
"name": "Brush lifespan"
|
"name": "Main brush lifespan"
|
||||||
},
|
},
|
||||||
"lifespan_filter": {
|
"lifespan_filter": {
|
||||||
"name": "Filter lifespan"
|
"name": "Filter lifespan"
|
||||||
},
|
},
|
||||||
"lifespan_side_brush": {
|
"lifespan_side_brush": {
|
||||||
"name": "Side brush lifespan"
|
"name": "Side brushes lifespan"
|
||||||
},
|
},
|
||||||
"network_ip": {
|
"network_ip": {
|
||||||
"name": "IP address"
|
"name": "IP address"
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"name": "Area cleaned"
|
"name": "Area cleaned"
|
||||||
},
|
},
|
||||||
"stats_time": {
|
"stats_time": {
|
||||||
"name": "Time cleaned"
|
"name": "Cleaning duration"
|
||||||
},
|
},
|
||||||
"total_stats_area": {
|
"total_stats_area": {
|
||||||
"name": "Total area cleaned"
|
"name": "Total area cleaned"
|
||||||
@@ -109,12 +109,12 @@
|
|||||||
"name": "Total cleanings"
|
"name": "Total cleanings"
|
||||||
},
|
},
|
||||||
"total_stats_time": {
|
"total_stats_time": {
|
||||||
"name": "Total time cleaned"
|
"name": "Total cleaning duration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"water_amount": {
|
"water_amount": {
|
||||||
"name": "Water amount",
|
"name": "Water flow level",
|
||||||
"state": {
|
"state": {
|
||||||
"high": "High",
|
"high": "High",
|
||||||
"low": "Low",
|
"low": "Low",
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
"name": "Advanced mode"
|
"name": "Advanced mode"
|
||||||
},
|
},
|
||||||
"carpet_auto_fan_boost": {
|
"carpet_auto_fan_boost": {
|
||||||
"name": "Carpet auto fan speed boost"
|
"name": "Carpet auto-boost suction"
|
||||||
},
|
},
|
||||||
"clean_preference": {
|
"clean_preference": {
|
||||||
"name": "Clean preference"
|
"name": "Clean preference"
|
||||||
|
@@ -7,6 +7,6 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["elgato==5.1.1"],
|
"requirements": ["elgato==5.1.2"],
|
||||||
"zeroconf": ["_elg._tcp.local."]
|
"zeroconf": ["_elg._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -35,8 +35,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._api_token = api_token = user_input[CONF_API_TOKEN]
|
self._api_token = api_token = user_input[CONF_API_TOKEN]
|
||||||
client = Elvia(meter_value_token=api_token).meter_value()
|
client = Elvia(meter_value_token=api_token).meter_value()
|
||||||
try:
|
try:
|
||||||
|
end_time = dt_util.utcnow()
|
||||||
results = await client.get_meter_values(
|
results = await client.get_meter_values(
|
||||||
start_time=(dt_util.now() - timedelta(hours=1)).isoformat()
|
start_time=(end_time - timedelta(hours=1)).isoformat(),
|
||||||
|
end_time=end_time.isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
except ElviaError.AuthError as exception:
|
except ElviaError.AuthError as exception:
|
||||||
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
from elvia import Elvia
|
from elvia import Elvia, error as ElviaError
|
||||||
|
|
||||||
from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
|
from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
|
||||||
from homeassistant.components.recorder.statistics import (
|
from homeassistant.components.recorder.statistics import (
|
||||||
@@ -38,11 +38,18 @@ class ElviaImporter:
|
|||||||
self.client = Elvia(meter_value_token=api_token).meter_value()
|
self.client = Elvia(meter_value_token=api_token).meter_value()
|
||||||
self.metering_point_id = metering_point_id
|
self.metering_point_id = metering_point_id
|
||||||
|
|
||||||
async def _fetch_hourly_data(self, since: datetime) -> list[MeterValueTimeSeries]:
|
async def _fetch_hourly_data(
|
||||||
|
self,
|
||||||
|
since: datetime,
|
||||||
|
until: datetime,
|
||||||
|
) -> list[MeterValueTimeSeries]:
|
||||||
"""Fetch hourly data."""
|
"""Fetch hourly data."""
|
||||||
LOGGER.debug("Fetching hourly data since %s", since)
|
start_time = since.isoformat()
|
||||||
|
end_time = until.isoformat()
|
||||||
|
LOGGER.debug("Fetching hourly data %s - %s", start_time, end_time)
|
||||||
all_data = await self.client.get_meter_values(
|
all_data = await self.client.get_meter_values(
|
||||||
start_time=since.isoformat(),
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
metering_point_ids=[self.metering_point_id],
|
metering_point_ids=[self.metering_point_id],
|
||||||
)
|
)
|
||||||
return all_data["meteringpoints"][0]["metervalue"]["timeSeries"]
|
return all_data["meteringpoints"][0]["metervalue"]["timeSeries"]
|
||||||
@@ -61,18 +68,37 @@ class ElviaImporter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not last_stats:
|
if not last_stats:
|
||||||
# First time we insert 1 years of data (if available)
|
# First time we insert 3 years of data (if available)
|
||||||
hourly_data = await self._fetch_hourly_data(
|
hourly_data: list[MeterValueTimeSeries] = []
|
||||||
since=dt_util.now() - timedelta(days=365)
|
until = dt_util.utcnow()
|
||||||
)
|
for year in (3, 2, 1):
|
||||||
|
try:
|
||||||
|
year_hours = await self._fetch_hourly_data(
|
||||||
|
since=until - timedelta(days=365 * year),
|
||||||
|
until=until - timedelta(days=365 * (year - 1)),
|
||||||
|
)
|
||||||
|
except ElviaError.ElviaException:
|
||||||
|
# This will raise if the contract have no data for the
|
||||||
|
# year, we can safely ignore this
|
||||||
|
continue
|
||||||
|
hourly_data.extend(year_hours)
|
||||||
|
|
||||||
if hourly_data is None or len(hourly_data) == 0:
|
if hourly_data is None or len(hourly_data) == 0:
|
||||||
|
LOGGER.error("No data available for the metering point")
|
||||||
return
|
return
|
||||||
last_stats_time = None
|
last_stats_time = None
|
||||||
_sum = 0.0
|
_sum = 0.0
|
||||||
else:
|
else:
|
||||||
hourly_data = await self._fetch_hourly_data(
|
try:
|
||||||
since=dt_util.utc_from_timestamp(last_stats[statistic_id][0]["end"])
|
hourly_data = await self._fetch_hourly_data(
|
||||||
)
|
since=dt_util.utc_from_timestamp(
|
||||||
|
last_stats[statistic_id][0]["end"]
|
||||||
|
),
|
||||||
|
until=dt_util.utcnow(),
|
||||||
|
)
|
||||||
|
except ElviaError.ElviaException as err:
|
||||||
|
LOGGER.error("Error fetching data: %s", err)
|
||||||
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hourly_data is None
|
hourly_data is None
|
||||||
|
@@ -352,7 +352,6 @@ class ESPHomeManager:
|
|||||||
if self.voice_assistant_udp_server is not None:
|
if self.voice_assistant_udp_server is not None:
|
||||||
_LOGGER.warning("Voice assistant UDP server was not stopped")
|
_LOGGER.warning("Voice assistant UDP server was not stopped")
|
||||||
self.voice_assistant_udp_server.stop()
|
self.voice_assistant_udp_server.stop()
|
||||||
self.voice_assistant_udp_server.close()
|
|
||||||
self.voice_assistant_udp_server = None
|
self.voice_assistant_udp_server = None
|
||||||
|
|
||||||
hass = self.hass
|
hass = self.hass
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""ESPHome voice assistant support."""
|
"""ESPHome voice assistant support."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -67,7 +68,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
"""Receive UDP packets and forward them to the voice assistant."""
|
"""Receive UDP packets and forward them to the voice assistant."""
|
||||||
|
|
||||||
started = False
|
started = False
|
||||||
stopped = False
|
stop_requested = False
|
||||||
transport: asyncio.DatagramTransport | None = None
|
transport: asyncio.DatagramTransport | None = None
|
||||||
remote_addr: tuple[str, int] | None = None
|
remote_addr: tuple[str, int] | None = None
|
||||||
|
|
||||||
@@ -92,6 +93,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
self._tts_done = asyncio.Event()
|
self._tts_done = asyncio.Event()
|
||||||
self._tts_task: asyncio.Task | None = None
|
self._tts_task: asyncio.Task | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_running(self) -> bool:
|
||||||
|
"""True if the the UDP server is started and hasn't been asked to stop."""
|
||||||
|
return self.started and (not self.stop_requested)
|
||||||
|
|
||||||
async def start_server(self) -> int:
|
async def start_server(self) -> int:
|
||||||
"""Start accepting connections."""
|
"""Start accepting connections."""
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
"""Accept connection."""
|
"""Accept connection."""
|
||||||
if self.started:
|
if self.started:
|
||||||
raise RuntimeError("Can only start once")
|
raise RuntimeError("Can only start once")
|
||||||
if self.stopped:
|
if self.stop_requested:
|
||||||
raise RuntimeError("No longer accepting connections")
|
raise RuntimeError("No longer accepting connections")
|
||||||
|
|
||||||
self.started = True
|
self.started = True
|
||||||
@@ -124,7 +130,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
@callback
|
@callback
|
||||||
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
||||||
"""Handle incoming UDP packet."""
|
"""Handle incoming UDP packet."""
|
||||||
if not self.started or self.stopped:
|
if not self.is_running:
|
||||||
return
|
return
|
||||||
if self.remote_addr is None:
|
if self.remote_addr is None:
|
||||||
self.remote_addr = addr
|
self.remote_addr = addr
|
||||||
@@ -142,19 +148,19 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop the receiver."""
|
"""Stop the receiver."""
|
||||||
self.queue.put_nowait(b"")
|
self.queue.put_nowait(b"")
|
||||||
self.started = False
|
self.close()
|
||||||
self.stopped = True
|
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Close the receiver."""
|
"""Close the receiver."""
|
||||||
self.started = False
|
self.started = False
|
||||||
self.stopped = True
|
self.stop_requested = True
|
||||||
|
|
||||||
if self.transport is not None:
|
if self.transport is not None:
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
|
||||||
async def _iterate_packets(self) -> AsyncIterable[bytes]:
|
async def _iterate_packets(self) -> AsyncIterable[bytes]:
|
||||||
"""Iterate over incoming packets."""
|
"""Iterate over incoming packets."""
|
||||||
if not self.started or self.stopped:
|
if not self.is_running:
|
||||||
raise RuntimeError("Not running")
|
raise RuntimeError("Not running")
|
||||||
|
|
||||||
while data := await self.queue.get():
|
while data := await self.queue.get():
|
||||||
@@ -303,8 +309,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
async def _send_tts(self, media_id: str) -> None:
|
async def _send_tts(self, media_id: str) -> None:
|
||||||
"""Send TTS audio to device via UDP."""
|
"""Send TTS audio to device via UDP."""
|
||||||
|
# Always send stream start/end events
|
||||||
|
self.handle_event(VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.transport is None:
|
if (not self.is_running) or (self.transport is None):
|
||||||
return
|
return
|
||||||
|
|
||||||
extension, data = await tts.async_get_media_source_audio(
|
extension, data = await tts.async_get_media_source_audio(
|
||||||
@@ -337,15 +346,11 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
_LOGGER.debug("Sending %d bytes of audio", audio_bytes_size)
|
_LOGGER.debug("Sending %d bytes of audio", audio_bytes_size)
|
||||||
|
|
||||||
self.handle_event(
|
|
||||||
VoiceAssistantEventType.VOICE_ASSISTANT_TTS_STREAM_START, {}
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes_per_sample = stt.AudioBitRates.BITRATE_16 // 8
|
bytes_per_sample = stt.AudioBitRates.BITRATE_16 // 8
|
||||||
sample_offset = 0
|
sample_offset = 0
|
||||||
samples_left = audio_bytes_size // bytes_per_sample
|
samples_left = audio_bytes_size // bytes_per_sample
|
||||||
|
|
||||||
while samples_left > 0:
|
while (samples_left > 0) and self.is_running:
|
||||||
bytes_offset = sample_offset * bytes_per_sample
|
bytes_offset = sample_offset * bytes_per_sample
|
||||||
chunk: bytes = audio_bytes[bytes_offset : bytes_offset + 1024]
|
chunk: bytes = audio_bytes[bytes_offset : bytes_offset + 1024]
|
||||||
samples_in_chunk = len(chunk) // bytes_per_sample
|
samples_in_chunk = len(chunk) // bytes_per_sample
|
||||||
|
@@ -156,6 +156,7 @@ class EvoClimateEntity(EvoDevice, ClimateEntity):
|
|||||||
"""Base for an evohome Climate device."""
|
"""Base for an evohome Climate device."""
|
||||||
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_modes(self) -> list[HVACMode]:
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
@@ -190,7 +191,10 @@ class EvoZone(EvoChild, EvoClimateEntity):
|
|||||||
]
|
]
|
||||||
|
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = (
|
||||||
ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_zone_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
async def async_zone_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
||||||
@@ -372,6 +376,9 @@ class EvoController(EvoClimateEntity):
|
|||||||
]
|
]
|
||||||
if self._attr_preset_modes:
|
if self._attr_preset_modes:
|
||||||
self._attr_supported_features = ClimateEntityFeature.PRESET_MODE
|
self._attr_supported_features = ClimateEntityFeature.PRESET_MODE
|
||||||
|
self._attr_supported_features |= (
|
||||||
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
async def async_tcs_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
async def async_tcs_svc_request(self, service: str, data: dict[str, Any]) -> None:
|
||||||
"""Process a service request (system mode) for a controller.
|
"""Process a service request (system mode) for a controller.
|
||||||
|
@@ -126,6 +126,8 @@ async def async_setup_entry(
|
|||||||
class FibaroThermostat(FibaroDevice, ClimateEntity):
|
class FibaroThermostat(FibaroDevice, ClimateEntity):
|
||||||
"""Representation of a Fibaro Thermostat."""
|
"""Representation of a Fibaro Thermostat."""
|
||||||
|
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, fibaro_device: DeviceModel) -> None:
|
def __init__(self, fibaro_device: DeviceModel) -> None:
|
||||||
"""Initialize the Fibaro device."""
|
"""Initialize the Fibaro device."""
|
||||||
super().__init__(fibaro_device)
|
super().__init__(fibaro_device)
|
||||||
@@ -209,6 +211,11 @@ class FibaroThermostat(FibaroDevice, ClimateEntity):
|
|||||||
if mode in OPMODES_PRESET:
|
if mode in OPMODES_PRESET:
|
||||||
self._attr_preset_modes.append(OPMODES_PRESET[mode])
|
self._attr_preset_modes.append(OPMODES_PRESET[mode])
|
||||||
|
|
||||||
|
if HVACMode.OFF in self._attr_hvac_modes and len(self._attr_hvac_modes) > 1:
|
||||||
|
self._attr_supported_features |= (
|
||||||
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call when entity is added to hass."""
|
"""Call when entity is added to hass."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@@ -69,6 +69,7 @@ class Flexit(ClimateEntity):
|
|||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
||||||
)
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hub: ModbusHub, modbus_slave: int | None, name: str | None
|
self, hub: ModbusHub, modbus_slave: int | None, name: str | None
|
||||||
|
@@ -62,13 +62,17 @@ class FlexitClimateEntity(FlexitEntity, ClimateEntity):
|
|||||||
]
|
]
|
||||||
|
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
|
||||||
_attr_target_temperature_step = PRECISION_HALVES
|
_attr_target_temperature_step = PRECISION_HALVES
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_max_temp = MAX_TEMP
|
_attr_max_temp = MAX_TEMP
|
||||||
_attr_min_temp = MIN_TEMP
|
_attr_min_temp = MIN_TEMP
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator: FlexitCoordinator) -> None:
|
def __init__(self, coordinator: FlexitCoordinator) -> None:
|
||||||
"""Initialize the Flexit unit."""
|
"""Initialize the Flexit unit."""
|
||||||
|
@@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20240131.0"]
|
"requirements": ["home-assistant-frontend==20240202.0"]
|
||||||
}
|
}
|
||||||
|
36
homeassistant/components/google_assistant/data_redaction.py
Normal file
36
homeassistant/components/google_assistant/data_redaction.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""Helpers to redact Google Assistant data when logging."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.redact import async_redact_data, partial_redact
|
||||||
|
|
||||||
|
REQUEST_MSG_TO_REDACT: dict[str, Callable[[str], str]] = {
|
||||||
|
"agentUserId": partial_redact,
|
||||||
|
"uuid": partial_redact,
|
||||||
|
"webhookId": partial_redact,
|
||||||
|
}
|
||||||
|
|
||||||
|
RESPONSE_MSG_TO_REDACT = REQUEST_MSG_TO_REDACT | {id: partial_redact}
|
||||||
|
|
||||||
|
SYNC_MSG_TO_REDACT = REQUEST_MSG_TO_REDACT
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_redact_request_msg(msg: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Mask sensitive data in message."""
|
||||||
|
return async_redact_data(msg, REQUEST_MSG_TO_REDACT)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_redact_response_msg(msg: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Mask sensitive data in message."""
|
||||||
|
return async_redact_data(msg, RESPONSE_MSG_TO_REDACT)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_redact_sync_msg(msg: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Mask sensitive data in message."""
|
||||||
|
return async_redact_data(msg, SYNC_MSG_TO_REDACT)
|
@@ -32,6 +32,7 @@ from homeassistant.helpers import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.network import get_url
|
from homeassistant.helpers.network import get_url
|
||||||
|
from homeassistant.helpers.redact import partial_redact
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ from .const import (
|
|||||||
STORE_AGENT_USER_IDS,
|
STORE_AGENT_USER_IDS,
|
||||||
STORE_GOOGLE_LOCAL_WEBHOOK_ID,
|
STORE_GOOGLE_LOCAL_WEBHOOK_ID,
|
||||||
)
|
)
|
||||||
|
from .data_redaction import async_redact_request_msg, async_redact_response_msg
|
||||||
from .error import SmartHomeError
|
from .error import SmartHomeError
|
||||||
|
|
||||||
SYNC_DELAY = 15
|
SYNC_DELAY = 15
|
||||||
@@ -332,8 +334,8 @@ class AbstractConfig(ABC):
|
|||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Register webhook handler %s for agent user id %s",
|
"Register webhook handler %s for agent user id %s",
|
||||||
webhook_id,
|
partial_redact(webhook_id),
|
||||||
user_agent_id,
|
partial_redact(user_agent_id),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
webhook.async_register(
|
webhook.async_register(
|
||||||
@@ -348,8 +350,8 @@ class AbstractConfig(ABC):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Webhook handler %s for agent user id %s is already defined!",
|
"Webhook handler %s for agent user id %s is already defined!",
|
||||||
webhook_id,
|
partial_redact(webhook_id),
|
||||||
user_agent_id,
|
partial_redact(user_agent_id),
|
||||||
)
|
)
|
||||||
setup_successful = False
|
setup_successful = False
|
||||||
break
|
break
|
||||||
@@ -374,8 +376,8 @@ class AbstractConfig(ABC):
|
|||||||
webhook_id = self.get_local_webhook_id(agent_user_id)
|
webhook_id = self.get_local_webhook_id(agent_user_id)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Unregister webhook handler %s for agent user id %s",
|
"Unregister webhook handler %s for agent user id %s",
|
||||||
webhook_id,
|
partial_redact(webhook_id),
|
||||||
agent_user_id,
|
partial_redact(agent_user_id),
|
||||||
)
|
)
|
||||||
webhook.async_unregister(self.hass, webhook_id)
|
webhook.async_unregister(self.hass, webhook_id)
|
||||||
|
|
||||||
@@ -410,7 +412,7 @@ class AbstractConfig(ABC):
|
|||||||
"Received local message from %s (JS %s):\n%s\n",
|
"Received local message from %s (JS %s):\n%s\n",
|
||||||
request.remote,
|
request.remote,
|
||||||
request.headers.get("HA-Cloud-Version", "unknown"),
|
request.headers.get("HA-Cloud-Version", "unknown"),
|
||||||
pprint.pformat(payload),
|
pprint.pformat(async_redact_request_msg(payload)),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (agent_user_id := self.get_local_agent_user_id(webhook_id)) is None:
|
if (agent_user_id := self.get_local_agent_user_id(webhook_id)) is None:
|
||||||
@@ -421,8 +423,8 @@ class AbstractConfig(ABC):
|
|||||||
"Cannot process request for webhook %s as no linked agent user is"
|
"Cannot process request for webhook %s as no linked agent user is"
|
||||||
" found:\n%s\n"
|
" found:\n%s\n"
|
||||||
),
|
),
|
||||||
webhook_id,
|
partial_redact(webhook_id),
|
||||||
pprint.pformat(payload),
|
pprint.pformat(async_redact_request_msg(payload)),
|
||||||
)
|
)
|
||||||
webhook.async_unregister(self.hass, webhook_id)
|
webhook.async_unregister(self.hass, webhook_id)
|
||||||
return None
|
return None
|
||||||
@@ -441,7 +443,10 @@ class AbstractConfig(ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_LOGGER.debug("Responding to local message:\n%s\n", pprint.pformat(result))
|
_LOGGER.debug(
|
||||||
|
"Responding to local message:\n%s\n",
|
||||||
|
pprint.pformat(async_redact_response_msg(result)),
|
||||||
|
)
|
||||||
|
|
||||||
return json_response(result)
|
return json_response(result)
|
||||||
|
|
||||||
|
@@ -18,6 +18,11 @@ from .const import (
|
|||||||
EVENT_QUERY_RECEIVED,
|
EVENT_QUERY_RECEIVED,
|
||||||
EVENT_SYNC_RECEIVED,
|
EVENT_SYNC_RECEIVED,
|
||||||
)
|
)
|
||||||
|
from .data_redaction import (
|
||||||
|
async_redact_request_msg,
|
||||||
|
async_redact_response_msg,
|
||||||
|
async_redact_sync_msg,
|
||||||
|
)
|
||||||
from .error import SmartHomeError
|
from .error import SmartHomeError
|
||||||
from .helpers import GoogleEntity, RequestData, async_get_entities
|
from .helpers import GoogleEntity, RequestData, async_get_entities
|
||||||
|
|
||||||
@@ -42,7 +47,11 @@ async def async_handle_message(hass, config, user_id, message, source):
|
|||||||
response = await _process(hass, data, message)
|
response = await _process(hass, data, message)
|
||||||
|
|
||||||
if response and "errorCode" in response["payload"]:
|
if response and "errorCode" in response["payload"]:
|
||||||
_LOGGER.error("Error handling message %s: %s", message, response["payload"])
|
_LOGGER.error(
|
||||||
|
"Error handling message %s: %s",
|
||||||
|
async_redact_request_msg(message),
|
||||||
|
async_redact_response_msg(response["payload"]),
|
||||||
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -118,7 +127,7 @@ async def async_devices_sync(
|
|||||||
devices = await async_devices_sync_response(hass, data.config, agent_user_id)
|
devices = await async_devices_sync_response(hass, data.config, agent_user_id)
|
||||||
response = create_sync_response(agent_user_id, devices)
|
response = create_sync_response(agent_user_id, devices)
|
||||||
|
|
||||||
_LOGGER.debug("Syncing entities response: %s", response)
|
_LOGGER.debug("Syncing entities response: %s", async_redact_sync_msg(response))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
"""Support for GPSD."""
|
"""Sensor platform for GPSD integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ from homeassistant.components.sensor import (
|
|||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -24,6 +27,7 @@ from homeassistant.const import (
|
|||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
|
EntityCategory,
|
||||||
)
|
)
|
||||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -43,6 +47,28 @@ ATTR_SPEED = "speed"
|
|||||||
|
|
||||||
DEFAULT_NAME = "GPS"
|
DEFAULT_NAME = "GPS"
|
||||||
|
|
||||||
|
_MODE_VALUES = {2: "2d_fix", 3: "3d_fix"}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class GpsdSensorDescription(SensorEntityDescription):
|
||||||
|
"""Class describing GPSD sensor entities."""
|
||||||
|
|
||||||
|
value_fn: Callable[[AGPS3mechanism], str | None]
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[GpsdSensorDescription, ...] = (
|
||||||
|
GpsdSensorDescription(
|
||||||
|
key="mode",
|
||||||
|
translation_key="mode",
|
||||||
|
name=None,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=list(_MODE_VALUES.values()),
|
||||||
|
value_fn=lambda agps_thread: _MODE_VALUES.get(agps_thread.data_stream.mode),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
@@ -64,7 +90,9 @@ async def async_setup_entry(
|
|||||||
config_entry.data[CONF_HOST],
|
config_entry.data[CONF_HOST],
|
||||||
config_entry.data[CONF_PORT],
|
config_entry.data[CONF_PORT],
|
||||||
config_entry.entry_id,
|
config_entry.entry_id,
|
||||||
|
description,
|
||||||
)
|
)
|
||||||
|
for description in SENSOR_TYPES
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,23 +129,23 @@ class GpsdSensor(SensorEntity):
|
|||||||
"""Representation of a GPS receiver available via GPSD."""
|
"""Representation of a GPS receiver available via GPSD."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
|
||||||
_attr_translation_key = "mode"
|
entity_description: GpsdSensorDescription
|
||||||
_attr_device_class = SensorDeviceClass.ENUM
|
|
||||||
_attr_options = ["2d_fix", "3d_fix"]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
host: str,
|
host: str,
|
||||||
port: int,
|
port: int,
|
||||||
unique_id: str,
|
unique_id: str,
|
||||||
|
description: GpsdSensorDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the GPSD sensor."""
|
"""Initialize the GPSD sensor."""
|
||||||
|
self.entity_description = description
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, unique_id)},
|
identifiers={(DOMAIN, unique_id)},
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
)
|
)
|
||||||
self._attr_unique_id = f"{unique_id}-mode"
|
self._attr_unique_id = f"{unique_id}-{self.entity_description.key}"
|
||||||
|
|
||||||
self.agps_thread = AGPS3mechanism()
|
self.agps_thread = AGPS3mechanism()
|
||||||
self.agps_thread.stream_data(host=host, port=port)
|
self.agps_thread.stream_data(host=host, port=port)
|
||||||
@@ -126,11 +154,7 @@ class GpsdSensor(SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> str | None:
|
def native_value(self) -> str | None:
|
||||||
"""Return the state of GPSD."""
|
"""Return the state of GPSD."""
|
||||||
if self.agps_thread.data_stream.mode == 3:
|
return self.entity_description.value_fn(self.agps_thread)
|
||||||
return "3d_fix"
|
|
||||||
if self.agps_thread.data_stream.mode == 2:
|
|
||||||
return "2d_fix"
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
@@ -30,6 +30,7 @@ from homeassistant.components.light import (
|
|||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
LightEntityFeature,
|
LightEntityFeature,
|
||||||
|
filter_supported_color_modes,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -162,6 +163,9 @@ class LightGroup(GroupEntity, LightEntity):
|
|||||||
if mode:
|
if mode:
|
||||||
self.mode = all
|
self.mode = all
|
||||||
|
|
||||||
|
self._attr_color_mode = ColorMode.UNKNOWN
|
||||||
|
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Forward the turn_on command to all lights in the light group."""
|
"""Forward the turn_on command to all lights in the light group."""
|
||||||
data = {
|
data = {
|
||||||
@@ -261,26 +265,36 @@ class LightGroup(GroupEntity, LightEntity):
|
|||||||
effects_count = Counter(itertools.chain(all_effects))
|
effects_count = Counter(itertools.chain(all_effects))
|
||||||
self._attr_effect = effects_count.most_common(1)[0][0]
|
self._attr_effect = effects_count.most_common(1)[0][0]
|
||||||
|
|
||||||
self._attr_color_mode = None
|
supported_color_modes = {ColorMode.ONOFF}
|
||||||
all_color_modes = list(find_state_attributes(on_states, ATTR_COLOR_MODE))
|
|
||||||
if all_color_modes:
|
|
||||||
# Report the most common color mode, select brightness and onoff last
|
|
||||||
color_mode_count = Counter(itertools.chain(all_color_modes))
|
|
||||||
if ColorMode.ONOFF in color_mode_count:
|
|
||||||
color_mode_count[ColorMode.ONOFF] = -1
|
|
||||||
if ColorMode.BRIGHTNESS in color_mode_count:
|
|
||||||
color_mode_count[ColorMode.BRIGHTNESS] = 0
|
|
||||||
self._attr_color_mode = color_mode_count.most_common(1)[0][0]
|
|
||||||
|
|
||||||
self._attr_supported_color_modes = None
|
|
||||||
all_supported_color_modes = list(
|
all_supported_color_modes = list(
|
||||||
find_state_attributes(states, ATTR_SUPPORTED_COLOR_MODES)
|
find_state_attributes(states, ATTR_SUPPORTED_COLOR_MODES)
|
||||||
)
|
)
|
||||||
if all_supported_color_modes:
|
if all_supported_color_modes:
|
||||||
# Merge all color modes.
|
# Merge all color modes.
|
||||||
self._attr_supported_color_modes = cast(
|
supported_color_modes = filter_supported_color_modes(
|
||||||
set[str], set().union(*all_supported_color_modes)
|
cast(set[ColorMode], set().union(*all_supported_color_modes))
|
||||||
)
|
)
|
||||||
|
self._attr_supported_color_modes = supported_color_modes
|
||||||
|
|
||||||
|
self._attr_color_mode = ColorMode.UNKNOWN
|
||||||
|
all_color_modes = list(find_state_attributes(on_states, ATTR_COLOR_MODE))
|
||||||
|
if all_color_modes:
|
||||||
|
# Report the most common color mode, select brightness and onoff last
|
||||||
|
color_mode_count = Counter(itertools.chain(all_color_modes))
|
||||||
|
if ColorMode.ONOFF in color_mode_count:
|
||||||
|
if ColorMode.ONOFF in supported_color_modes:
|
||||||
|
color_mode_count[ColorMode.ONOFF] = -1
|
||||||
|
else:
|
||||||
|
color_mode_count.pop(ColorMode.ONOFF)
|
||||||
|
if ColorMode.BRIGHTNESS in color_mode_count:
|
||||||
|
if ColorMode.BRIGHTNESS in supported_color_modes:
|
||||||
|
color_mode_count[ColorMode.BRIGHTNESS] = 0
|
||||||
|
else:
|
||||||
|
color_mode_count.pop(ColorMode.BRIGHTNESS)
|
||||||
|
if color_mode_count:
|
||||||
|
self._attr_color_mode = color_mode_count.most_common(1)[0][0]
|
||||||
|
else:
|
||||||
|
self._attr_color_mode = next(iter(supported_color_modes))
|
||||||
|
|
||||||
self._attr_supported_features = LightEntityFeature(0)
|
self._attr_supported_features = LightEntityFeature(0)
|
||||||
for support in find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
for support in find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
||||||
|
@@ -139,6 +139,7 @@ class HomeKitBaseClimateEntity(HomeKitEntity, ClimateEntity):
|
|||||||
"""The base HomeKit Controller climate entity."""
|
"""The base HomeKit Controller climate entity."""
|
||||||
|
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_reconfigure(self) -> None:
|
def _async_reconfigure(self) -> None:
|
||||||
|
@@ -14,6 +14,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohomekit", "commentjson"],
|
"loggers": ["aiohomekit", "commentjson"],
|
||||||
"requirements": ["aiohomekit==3.1.3"],
|
"requirements": ["aiohomekit==3.1.4"],
|
||||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,10 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import (
|
||||||
|
async_create_clientsession,
|
||||||
|
async_get_clientsession,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
@@ -48,9 +51,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
username = config_entry.data[CONF_USERNAME]
|
username = config_entry.data[CONF_USERNAME]
|
||||||
password = config_entry.data[CONF_PASSWORD]
|
password = config_entry.data[CONF_PASSWORD]
|
||||||
|
|
||||||
client = aiosomecomfort.AIOSomeComfort(
|
if len(hass.config_entries.async_entries(DOMAIN)) > 1:
|
||||||
username, password, session=async_get_clientsession(hass)
|
session = async_create_clientsession(hass)
|
||||||
)
|
else:
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
client = aiosomecomfort.AIOSomeComfort(username, password, session=session)
|
||||||
try:
|
try:
|
||||||
await client.login()
|
await client.login()
|
||||||
await client.discover()
|
await client.discover()
|
||||||
@@ -76,7 +82,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
if len(devices) == 0:
|
if len(devices) == 0:
|
||||||
_LOGGER.debug("No devices found")
|
_LOGGER.debug("No devices found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
data = HoneywellData(config_entry.entry_id, client, devices)
|
data = HoneywellData(config_entry.entry_id, client, devices)
|
||||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = data
|
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = data
|
||||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||||
|
@@ -134,12 +134,17 @@ class KNXClimate(KnxEntity, ClimateEntity):
|
|||||||
|
|
||||||
_device: XknxClimate
|
_device: XknxClimate
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
|
def __init__(self, xknx: XKNX, config: ConfigType) -> None:
|
||||||
"""Initialize of a KNX climate device."""
|
"""Initialize of a KNX climate device."""
|
||||||
super().__init__(_create_climate(xknx, config))
|
super().__init__(_create_climate(xknx, config))
|
||||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||||
self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
self._attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
if self._device.supports_on_off:
|
||||||
|
self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
|
||||||
if self.preset_modes:
|
if self.preset_modes:
|
||||||
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
|
||||||
self._attr_target_temperature_step = self._device.temperature_step
|
self._attr_target_temperature_step = self._device.temperature_step
|
||||||
|
@@ -42,7 +42,7 @@ async def async_setup_entry(
|
|||||||
lights = []
|
lights = []
|
||||||
|
|
||||||
for area_name, device in entry_data.lights:
|
for area_name, device in entry_data.lights:
|
||||||
if device.type == "CEILING_FAN_TYPE2":
|
if device.type == "CEILING_FAN_TYPE":
|
||||||
# If this is a fan, check to see if this entity already exists.
|
# If this is a fan, check to see if this entity already exists.
|
||||||
# If not, do not create a new one.
|
# If not, do not create a new one.
|
||||||
entity_id = ent_reg.async_get_entity_id(
|
entity_id = ent_reg.async_get_entity_id(
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/matrix",
|
"documentation": "https://www.home-assistant.io/integrations/matrix",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["matrix_client"],
|
"loggers": ["matrix_client"],
|
||||||
"requirements": ["matrix-nio==0.22.1", "Pillow==10.2.0"]
|
"requirements": ["matrix-nio==0.24.0", "Pillow==10.2.0"]
|
||||||
}
|
}
|
||||||
|
@@ -73,11 +73,8 @@ class MatterClimate(MatterEntity, ClimateEntity):
|
|||||||
"""Representation of a Matter climate entity."""
|
"""Representation of a Matter climate entity."""
|
||||||
|
|
||||||
_attr_temperature_unit: str = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit: str = UnitOfTemperature.CELSIUS
|
||||||
_attr_supported_features: ClimateEntityFeature = (
|
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
|
||||||
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
|
||||||
)
|
|
||||||
_attr_hvac_mode: HVACMode = HVACMode.OFF
|
_attr_hvac_mode: HVACMode = HVACMode.OFF
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -99,6 +96,13 @@ class MatterClimate(MatterEntity, ClimateEntity):
|
|||||||
self._attr_hvac_modes.append(HVACMode.COOL)
|
self._attr_hvac_modes.append(HVACMode.COOL)
|
||||||
if feature_map & ThermostatFeature.kAutoMode:
|
if feature_map & ThermostatFeature.kAutoMode:
|
||||||
self._attr_hvac_modes.append(HVACMode.HEAT_COOL)
|
self._attr_hvac_modes.append(HVACMode.HEAT_COOL)
|
||||||
|
self._attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
)
|
||||||
|
if any(mode for mode in self.hvac_modes if mode != HVACMode.OFF):
|
||||||
|
self._attr_supported_features |= ClimateEntityFeature.TURN_ON
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
|
@@ -68,8 +68,12 @@ class MaxCubeClimate(ClimateEntity):
|
|||||||
|
|
||||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.HEAT]
|
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.HEAT]
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, handler, device):
|
def __init__(self, handler, device):
|
||||||
"""Initialize MAX! Cube ClimateEntity."""
|
"""Initialize MAX! Cube ClimateEntity."""
|
||||||
|
@@ -114,6 +114,7 @@ class MelCloudClimate(ClimateEntity):
|
|||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, device: MelCloudDevice) -> None:
|
def __init__(self, device: MelCloudDevice) -> None:
|
||||||
"""Initialize the climate."""
|
"""Initialize the climate."""
|
||||||
@@ -137,6 +138,8 @@ class AtaDeviceClimate(MelCloudClimate):
|
|||||||
ClimateEntityFeature.FAN_MODE
|
ClimateEntityFeature.FAN_MODE
|
||||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
| ClimateEntityFeature.SWING_MODE
|
| ClimateEntityFeature.SWING_MODE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, device: MelCloudDevice, ata_device: AtaDevice) -> None:
|
def __init__(self, device: MelCloudDevice, ata_device: AtaDevice) -> None:
|
||||||
|
@@ -57,9 +57,13 @@ class MelissaClimate(ClimateEntity):
|
|||||||
|
|
||||||
_attr_hvac_modes = OP_MODES
|
_attr_hvac_modes = OP_MODES
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
ClimateEntityFeature.FAN_MODE
|
||||||
|
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, api, serial_number, init_data):
|
def __init__(self, api, serial_number, init_data):
|
||||||
"""Initialize the climate device."""
|
"""Initialize the climate device."""
|
||||||
|
@@ -99,6 +99,7 @@ class MillHeater(CoordinatorEntity[MillDataUpdateCoordinator], ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_target_temperature_step = PRECISION_TENTHS
|
_attr_target_temperature_step = PRECISION_TENTHS
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coordinator: MillDataUpdateCoordinator, heater: mill.Heater
|
self, coordinator: MillDataUpdateCoordinator, heater: mill.Heater
|
||||||
|
@@ -182,7 +182,6 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
self._data_type = config[CONF_DATA_TYPE]
|
self._data_type = config[CONF_DATA_TYPE]
|
||||||
self._structure: str = config[CONF_STRUCTURE]
|
self._structure: str = config[CONF_STRUCTURE]
|
||||||
self._scale = config[CONF_SCALE]
|
self._scale = config[CONF_SCALE]
|
||||||
self._precision = config.get(CONF_PRECISION, 2)
|
|
||||||
self._offset = config[CONF_OFFSET]
|
self._offset = config[CONF_OFFSET]
|
||||||
self._slave_count = config.get(CONF_SLAVE_COUNT, None) or config.get(
|
self._slave_count = config.get(CONF_SLAVE_COUNT, None) or config.get(
|
||||||
CONF_VIRTUAL_COUNT, 0
|
CONF_VIRTUAL_COUNT, 0
|
||||||
@@ -196,11 +195,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
DataType.UINT32,
|
DataType.UINT32,
|
||||||
DataType.UINT64,
|
DataType.UINT64,
|
||||||
)
|
)
|
||||||
if self._value_is_int:
|
if not self._value_is_int:
|
||||||
if self._min_value:
|
self._precision = config.get(CONF_PRECISION, 2)
|
||||||
self._min_value = round(self._min_value)
|
else:
|
||||||
if self._max_value:
|
self._precision = config.get(CONF_PRECISION, 0)
|
||||||
self._max_value = round(self._max_value)
|
|
||||||
|
|
||||||
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
|
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
|
||||||
"""Do swap as needed."""
|
"""Do swap as needed."""
|
||||||
@@ -235,13 +233,13 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
return None
|
return None
|
||||||
val: float | int = self._scale * entry + self._offset
|
val: float | int = self._scale * entry + self._offset
|
||||||
if self._min_value is not None and val < self._min_value:
|
if self._min_value is not None and val < self._min_value:
|
||||||
return str(self._min_value)
|
val = self._min_value
|
||||||
if self._max_value is not None and val > self._max_value:
|
if self._max_value is not None and val > self._max_value:
|
||||||
return str(self._max_value)
|
val = self._max_value
|
||||||
if self._zero_suppress is not None and abs(val) <= self._zero_suppress:
|
if self._zero_suppress is not None and abs(val) <= self._zero_suppress:
|
||||||
return "0"
|
return "0"
|
||||||
if self._precision == 0 or self._value_is_int:
|
if self._precision == 0:
|
||||||
return str(int(round(val, 0)))
|
return str(round(val))
|
||||||
return f"{float(val):.{self._precision}f}"
|
return f"{float(val):.{self._precision}f}"
|
||||||
|
|
||||||
def unpack_structure_result(self, registers: list[int]) -> str | None:
|
def unpack_structure_result(self, registers: list[int]) -> str | None:
|
||||||
|
@@ -97,7 +97,12 @@ async def async_setup_platform(
|
|||||||
class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
||||||
"""Representation of a Modbus Thermostat."""
|
"""Representation of a Modbus Thermostat."""
|
||||||
|
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@@ -203,141 +203,6 @@ def duplicate_fan_mode_validator(config: dict[str, Any]) -> dict:
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def scan_interval_validator(config: dict) -> dict:
|
|
||||||
"""Control scan_interval."""
|
|
||||||
for hub in config:
|
|
||||||
minimum_scan_interval = DEFAULT_SCAN_INTERVAL
|
|
||||||
for component, conf_key in PLATFORMS:
|
|
||||||
if conf_key not in hub:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for entry in hub[conf_key]:
|
|
||||||
scan_interval = entry.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
|
||||||
if scan_interval == 0:
|
|
||||||
continue
|
|
||||||
if scan_interval < 5:
|
|
||||||
_LOGGER.warning(
|
|
||||||
(
|
|
||||||
"%s %s scan_interval(%d) is lower than 5 seconds, "
|
|
||||||
"which may cause Home Assistant stability issues"
|
|
||||||
),
|
|
||||||
component,
|
|
||||||
entry.get(CONF_NAME),
|
|
||||||
scan_interval,
|
|
||||||
)
|
|
||||||
entry[CONF_SCAN_INTERVAL] = scan_interval
|
|
||||||
minimum_scan_interval = min(scan_interval, minimum_scan_interval)
|
|
||||||
if (
|
|
||||||
CONF_TIMEOUT in hub
|
|
||||||
and hub[CONF_TIMEOUT] > minimum_scan_interval - 1
|
|
||||||
and minimum_scan_interval > 1
|
|
||||||
):
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Modbus %s timeout(%d) is adjusted(%d) due to scan_interval",
|
|
||||||
hub.get(CONF_NAME, ""),
|
|
||||||
hub[CONF_TIMEOUT],
|
|
||||||
minimum_scan_interval - 1,
|
|
||||||
)
|
|
||||||
hub[CONF_TIMEOUT] = minimum_scan_interval - 1
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def duplicate_entity_validator(config: dict) -> dict:
|
|
||||||
"""Control scan_interval."""
|
|
||||||
for hub_index, hub in enumerate(config):
|
|
||||||
for component, conf_key in PLATFORMS:
|
|
||||||
if conf_key not in hub:
|
|
||||||
continue
|
|
||||||
names: set[str] = set()
|
|
||||||
errors: list[int] = []
|
|
||||||
addresses: set[str] = set()
|
|
||||||
for index, entry in enumerate(hub[conf_key]):
|
|
||||||
name = entry[CONF_NAME]
|
|
||||||
addr = str(entry[CONF_ADDRESS])
|
|
||||||
if CONF_INPUT_TYPE in entry:
|
|
||||||
addr += "_" + str(entry[CONF_INPUT_TYPE])
|
|
||||||
elif CONF_WRITE_TYPE in entry:
|
|
||||||
addr += "_" + str(entry[CONF_WRITE_TYPE])
|
|
||||||
if CONF_COMMAND_ON in entry:
|
|
||||||
addr += "_" + str(entry[CONF_COMMAND_ON])
|
|
||||||
if CONF_COMMAND_OFF in entry:
|
|
||||||
addr += "_" + str(entry[CONF_COMMAND_OFF])
|
|
||||||
inx = entry.get(CONF_SLAVE, None) or entry.get(CONF_DEVICE_ADDRESS, 0)
|
|
||||||
addr += "_" + str(inx)
|
|
||||||
entry_addrs: set[str] = set()
|
|
||||||
entry_addrs.add(addr)
|
|
||||||
|
|
||||||
if CONF_TARGET_TEMP in entry:
|
|
||||||
a = str(entry[CONF_TARGET_TEMP])
|
|
||||||
a += "_" + str(inx)
|
|
||||||
entry_addrs.add(a)
|
|
||||||
if CONF_HVAC_MODE_REGISTER in entry:
|
|
||||||
a = str(entry[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS])
|
|
||||||
a += "_" + str(inx)
|
|
||||||
entry_addrs.add(a)
|
|
||||||
if CONF_FAN_MODE_REGISTER in entry:
|
|
||||||
a = str(
|
|
||||||
entry[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]
|
|
||||||
if isinstance(entry[CONF_FAN_MODE_REGISTER][CONF_ADDRESS], int)
|
|
||||||
else entry[CONF_FAN_MODE_REGISTER][CONF_ADDRESS][0]
|
|
||||||
)
|
|
||||||
a += "_" + str(inx)
|
|
||||||
entry_addrs.add(a)
|
|
||||||
|
|
||||||
dup_addrs = entry_addrs.intersection(addresses)
|
|
||||||
|
|
||||||
if len(dup_addrs) > 0:
|
|
||||||
for addr in dup_addrs:
|
|
||||||
err = (
|
|
||||||
f"Modbus {component}/{name} address {addr} is duplicate, second"
|
|
||||||
" entry not loaded!"
|
|
||||||
)
|
|
||||||
_LOGGER.warning(err)
|
|
||||||
errors.append(index)
|
|
||||||
elif name in names:
|
|
||||||
err = (
|
|
||||||
f"Modbus {component}/{name} is duplicate, second entry not"
|
|
||||||
" loaded!"
|
|
||||||
)
|
|
||||||
_LOGGER.warning(err)
|
|
||||||
errors.append(index)
|
|
||||||
else:
|
|
||||||
names.add(name)
|
|
||||||
addresses.update(entry_addrs)
|
|
||||||
|
|
||||||
for i in reversed(errors):
|
|
||||||
del config[hub_index][conf_key][i]
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def duplicate_modbus_validator(config: dict) -> dict:
|
|
||||||
"""Control modbus connection for duplicates."""
|
|
||||||
hosts: set[str] = set()
|
|
||||||
names: set[str] = set()
|
|
||||||
errors = []
|
|
||||||
for index, hub in enumerate(config):
|
|
||||||
name = hub.get(CONF_NAME, DEFAULT_HUB)
|
|
||||||
if hub[CONF_TYPE] == SERIAL:
|
|
||||||
host = hub[CONF_PORT]
|
|
||||||
else:
|
|
||||||
host = f"{hub[CONF_HOST]}_{hub[CONF_PORT]}"
|
|
||||||
if host in hosts:
|
|
||||||
err = f"Modbus {name} contains duplicate host/port {host}, not loaded!"
|
|
||||||
_LOGGER.warning(err)
|
|
||||||
errors.append(index)
|
|
||||||
elif name in names:
|
|
||||||
err = f"Modbus {name} is duplicate, second entry not loaded!"
|
|
||||||
_LOGGER.warning(err)
|
|
||||||
errors.append(index)
|
|
||||||
else:
|
|
||||||
hosts.add(host)
|
|
||||||
names.add(name)
|
|
||||||
|
|
||||||
for i in reversed(errors):
|
|
||||||
del config[i]
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def register_int_list_validator(value: Any) -> Any:
|
def register_int_list_validator(value: Any) -> Any:
|
||||||
"""Check if a register (CONF_ADRESS) is an int or a list having only 1 register."""
|
"""Check if a register (CONF_ADRESS) is an int or a list having only 1 register."""
|
||||||
if isinstance(value, int) and value >= 0:
|
if isinstance(value, int) and value >= 0:
|
||||||
@@ -354,7 +219,125 @@ def register_int_list_validator(value: Any) -> Any:
|
|||||||
|
|
||||||
def check_config(config: dict) -> dict:
|
def check_config(config: dict) -> dict:
|
||||||
"""Do final config check."""
|
"""Do final config check."""
|
||||||
config2 = duplicate_modbus_validator(config)
|
hosts: set[str] = set()
|
||||||
config3 = scan_interval_validator(config2)
|
hub_names: set[str] = set()
|
||||||
config4 = duplicate_entity_validator(config3)
|
hub_name_inx = 0
|
||||||
return config4
|
minimum_scan_interval = 0
|
||||||
|
ent_names: set[str] = set()
|
||||||
|
ent_addr: set[str] = set()
|
||||||
|
|
||||||
|
def validate_modbus(hub: dict, hub_name_inx: int) -> bool:
|
||||||
|
"""Validate modbus entries."""
|
||||||
|
host: str = (
|
||||||
|
hub[CONF_PORT]
|
||||||
|
if hub[CONF_TYPE] == SERIAL
|
||||||
|
else f"{hub[CONF_HOST]}_{hub[CONF_PORT]}"
|
||||||
|
)
|
||||||
|
if CONF_NAME not in hub:
|
||||||
|
hub[CONF_NAME] = (
|
||||||
|
DEFAULT_HUB if not hub_name_inx else f"{DEFAULT_HUB}_{hub_name_inx}"
|
||||||
|
)
|
||||||
|
hub_name_inx += 1
|
||||||
|
err = f"Modbus host/port {host} is missing name, added {hub[CONF_NAME]}!"
|
||||||
|
_LOGGER.warning(err)
|
||||||
|
name = hub[CONF_NAME]
|
||||||
|
if host in hosts or name in hub_names:
|
||||||
|
err = f"Modbus {name} host/port {host} is duplicate, not loaded!"
|
||||||
|
_LOGGER.warning(err)
|
||||||
|
return False
|
||||||
|
hosts.add(host)
|
||||||
|
hub_names.add(name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_entity(
|
||||||
|
hub_name: str,
|
||||||
|
entity: dict,
|
||||||
|
minimum_scan_interval: int,
|
||||||
|
ent_names: set,
|
||||||
|
ent_addr: set,
|
||||||
|
) -> bool:
|
||||||
|
"""Validate entity."""
|
||||||
|
name = entity[CONF_NAME]
|
||||||
|
addr = str(entity[CONF_ADDRESS])
|
||||||
|
scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
|
if scan_interval < 5:
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"%s %s scan_interval(%d) is lower than 5 seconds, "
|
||||||
|
"which may cause Home Assistant stability issues"
|
||||||
|
),
|
||||||
|
hub_name,
|
||||||
|
name,
|
||||||
|
scan_interval,
|
||||||
|
)
|
||||||
|
entity[CONF_SCAN_INTERVAL] = scan_interval
|
||||||
|
minimum_scan_interval = min(scan_interval, minimum_scan_interval)
|
||||||
|
for conf_type in (
|
||||||
|
CONF_INPUT_TYPE,
|
||||||
|
CONF_WRITE_TYPE,
|
||||||
|
CONF_COMMAND_ON,
|
||||||
|
CONF_COMMAND_OFF,
|
||||||
|
):
|
||||||
|
if conf_type in entity:
|
||||||
|
addr += f"_{entity[conf_type]}"
|
||||||
|
inx = entity.get(CONF_SLAVE, None) or entity.get(CONF_DEVICE_ADDRESS, 0)
|
||||||
|
addr += f"_{inx}"
|
||||||
|
loc_addr: set[str] = {addr}
|
||||||
|
|
||||||
|
if CONF_TARGET_TEMP in entity:
|
||||||
|
loc_addr.add(f"{entity[CONF_TARGET_TEMP]}_{inx}")
|
||||||
|
if CONF_HVAC_MODE_REGISTER in entity:
|
||||||
|
loc_addr.add(f"{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||||
|
if CONF_FAN_MODE_REGISTER in entity:
|
||||||
|
loc_addr.add(f"{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||||
|
|
||||||
|
dup_addrs = ent_addr.intersection(loc_addr)
|
||||||
|
if len(dup_addrs) > 0:
|
||||||
|
for addr in dup_addrs:
|
||||||
|
err = (
|
||||||
|
f"Modbus {hub_name}/{name} address {addr} is duplicate, second"
|
||||||
|
" entry not loaded!"
|
||||||
|
)
|
||||||
|
_LOGGER.warning(err)
|
||||||
|
return False
|
||||||
|
if name in ent_names:
|
||||||
|
err = f"Modbus {hub_name}/{name} is duplicate, second entry not loaded!"
|
||||||
|
_LOGGER.warning(err)
|
||||||
|
return False
|
||||||
|
ent_names.add(name)
|
||||||
|
ent_addr.update(loc_addr)
|
||||||
|
return True
|
||||||
|
|
||||||
|
hub_inx = 0
|
||||||
|
while hub_inx < len(config):
|
||||||
|
hub = config[hub_inx]
|
||||||
|
if not validate_modbus(hub, hub_name_inx):
|
||||||
|
del config[hub_inx]
|
||||||
|
continue
|
||||||
|
for _component, conf_key in PLATFORMS:
|
||||||
|
if conf_key not in hub:
|
||||||
|
continue
|
||||||
|
entity_inx = 0
|
||||||
|
entities = hub[conf_key]
|
||||||
|
minimum_scan_interval = 9999
|
||||||
|
while entity_inx < len(entities):
|
||||||
|
if not validate_entity(
|
||||||
|
hub[CONF_NAME],
|
||||||
|
entities[entity_inx],
|
||||||
|
minimum_scan_interval,
|
||||||
|
ent_names,
|
||||||
|
ent_addr,
|
||||||
|
):
|
||||||
|
del entities[entity_inx]
|
||||||
|
else:
|
||||||
|
entity_inx += 1
|
||||||
|
|
||||||
|
if hub[CONF_TIMEOUT] >= minimum_scan_interval:
|
||||||
|
hub[CONF_TIMEOUT] = minimum_scan_interval - 1
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Modbus %s timeout is adjusted(%d) due to scan_interval",
|
||||||
|
hub[CONF_NAME],
|
||||||
|
hub[CONF_TIMEOUT],
|
||||||
|
)
|
||||||
|
hub_inx += 1
|
||||||
|
return config
|
||||||
|
@@ -46,6 +46,7 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity):
|
|||||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL]
|
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL]
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_preset_modes = [PRESET_AUTO, PRESET_DAY, PRESET_NIGHT]
|
_attr_preset_modes = [PRESET_AUTO, PRESET_DAY, PRESET_NIGHT]
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator: Alpha2BaseCoordinator, heat_area_id: str) -> None:
|
def __init__(self, coordinator: Alpha2BaseCoordinator, heat_area_id: str) -> None:
|
||||||
"""Initialize Alpha2 ClimateEntity."""
|
"""Initialize Alpha2 ClimateEntity."""
|
||||||
|
@@ -610,6 +610,7 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity):
|
|||||||
_attributes_extra_blocked = MQTT_CLIMATE_ATTRIBUTES_BLOCKED
|
_attributes_extra_blocked = MQTT_CLIMATE_ATTRIBUTES_BLOCKED
|
||||||
_attr_target_temperature_low: float | None = None
|
_attr_target_temperature_low: float | None = None
|
||||||
_attr_target_temperature_high: float | None = None
|
_attr_target_temperature_high: float | None = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config_schema() -> vol.Schema:
|
def config_schema() -> vol.Schema:
|
||||||
|
@@ -70,11 +70,12 @@ class MySensorsHVAC(mysensors.device.MySensorsChildEntity, ClimateEntity):
|
|||||||
"""Representation of a MySensors HVAC."""
|
"""Representation of a MySensors HVAC."""
|
||||||
|
|
||||||
_attr_hvac_modes = OPERATION_LIST
|
_attr_hvac_modes = OPERATION_LIST
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> ClimateEntityFeature:
|
def supported_features(self) -> ClimateEntityFeature:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
features = ClimateEntityFeature(0)
|
features = ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
set_req = self.gateway.const.SetReq
|
set_req = self.gateway.const.SetReq
|
||||||
if set_req.V_HVAC_SPEED in self._values:
|
if set_req.V_HVAC_SPEED in self._values:
|
||||||
features = features | ClimateEntityFeature.FAN_MODE
|
features = features | ClimateEntityFeature.FAN_MODE
|
||||||
|
@@ -100,6 +100,7 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, device: Device) -> None:
|
def __init__(self, device: Device) -> None:
|
||||||
"""Initialize ThermostatEntity."""
|
"""Initialize ThermostatEntity."""
|
||||||
@@ -246,7 +247,7 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
|
|
||||||
def _get_supported_features(self) -> ClimateEntityFeature:
|
def _get_supported_features(self) -> ClimateEntityFeature:
|
||||||
"""Compute the bitmap of supported features from the current state."""
|
"""Compute the bitmap of supported features from the current state."""
|
||||||
features = ClimateEntityFeature(0)
|
features = ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
if HVACMode.HEAT_COOL in self.hvac_modes:
|
if HVACMode.HEAT_COOL in self.hvac_modes:
|
||||||
features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||||
if HVACMode.HEAT in self.hvac_modes or HVACMode.COOL in self.hvac_modes:
|
if HVACMode.HEAT in self.hvac_modes or HVACMode.COOL in self.hvac_modes:
|
||||||
|
@@ -190,6 +190,7 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity):
|
|||||||
_attr_supported_features = SUPPORT_FLAGS
|
_attr_supported_features = SUPPORT_FLAGS
|
||||||
_attr_target_temperature_step = PRECISION_HALVES
|
_attr_target_temperature_step = PRECISION_HALVES
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, netatmo_device: NetatmoRoom) -> None:
|
def __init__(self, netatmo_device: NetatmoRoom) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
@@ -153,6 +153,7 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
|
|||||||
"""Provides Nexia Climate support."""
|
"""Provides Nexia Climate support."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coordinator: NexiaDataUpdateCoordinator, zone: NexiaThermostatZone
|
self, coordinator: NexiaDataUpdateCoordinator, zone: NexiaThermostatZone
|
||||||
|
@@ -72,6 +72,7 @@ class NibeClimateEntity(CoordinatorEntity[Coordinator], ClimateEntity):
|
|||||||
_attr_target_temperature_step = 0.5
|
_attr_target_temperature_step = 0.5
|
||||||
_attr_max_temp = 35.0
|
_attr_max_temp = 35.0
|
||||||
_attr_min_temp = 5.0
|
_attr_min_temp = 5.0
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@@ -81,6 +81,7 @@ class NoboZone(ClimateEntity):
|
|||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_target_temperature_step = 1
|
_attr_target_temperature_step = 1
|
||||||
# Need to poll to get preset change when in HVACMode.AUTO, so can't set _attr_should_poll = False
|
# Need to poll to get preset change when in HVACMode.AUTO, so can't set _attr_should_poll = False
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, zone_id, hub: nobo, override_type) -> None:
|
def __init__(self, zone_id, hub: nobo, override_type) -> None:
|
||||||
"""Initialize the climate device."""
|
"""Initialize the climate device."""
|
||||||
|
@@ -78,6 +78,7 @@ class NuHeatThermostat(CoordinatorEntity, ClimateEntity):
|
|||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_preset_modes = PRESET_MODES
|
_attr_preset_modes = PRESET_MODES
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator, thermostat, temperature_unit):
|
def __init__(self, coordinator, thermostat, temperature_unit):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from pyoctoprintapi import OctoprintClient
|
from pyoctoprintapi import OctoprintClient
|
||||||
@@ -11,24 +12,28 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
CONF_BINARY_SENSORS,
|
CONF_BINARY_SENSORS,
|
||||||
|
CONF_DEVICE_ID,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_MONITORED_CONDITIONS,
|
CONF_MONITORED_CONDITIONS,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PATH,
|
CONF_PATH,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
|
CONF_PROFILE_NAME,
|
||||||
CONF_SENSORS,
|
CONF_SENSORS,
|
||||||
CONF_SSL,
|
CONF_SSL,
|
||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
|
||||||
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import homeassistant.helpers.device_registry as dr
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util import slugify as util_slugify
|
from homeassistant.util import slugify as util_slugify
|
||||||
from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
|
from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CONF_BAUDRATE, DOMAIN, SERVICE_CONNECT
|
||||||
from .coordinator import OctoprintDataUpdateCoordinator
|
from .coordinator import OctoprintDataUpdateCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -122,6 +127,15 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SERVICE_CONNECT_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_DEVICE_ID): cv.string,
|
||||||
|
vol.Optional(CONF_PROFILE_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_PORT): cv.string,
|
||||||
|
vol.Optional(CONF_BAUDRATE): cv.positive_int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the OctoPrint component."""
|
"""Set up the OctoPrint component."""
|
||||||
@@ -194,6 +208,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
async def async_printer_connect(call: ServiceCall) -> None:
|
||||||
|
"""Connect to a printer."""
|
||||||
|
client = async_get_client_for_service_call(hass, call)
|
||||||
|
await client.connect(
|
||||||
|
printer_profile=call.data.get(CONF_PROFILE_NAME),
|
||||||
|
port=call.data.get(CONF_PORT),
|
||||||
|
baud_rate=call.data.get(CONF_BAUDRATE),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not hass.services.has_service(DOMAIN, SERVICE_CONNECT):
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_CONNECT,
|
||||||
|
async_printer_connect,
|
||||||
|
schema=SERVICE_CONNECT_SCHEMA,
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -205,3 +236,24 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
def async_get_client_for_service_call(
|
||||||
|
hass: HomeAssistant, call: ServiceCall
|
||||||
|
) -> OctoprintClient:
|
||||||
|
"""Get the client related to a service call (by device ID)."""
|
||||||
|
device_id = call.data[CONF_DEVICE_ID]
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
if device_entry := device_registry.async_get(device_id):
|
||||||
|
for entry_id in device_entry.config_entries:
|
||||||
|
if data := hass.data[DOMAIN].get(entry_id):
|
||||||
|
return cast(OctoprintClient, data["client"])
|
||||||
|
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="missing_client",
|
||||||
|
translation_placeholders={
|
||||||
|
"device_id": device_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@@ -3,3 +3,6 @@
|
|||||||
DOMAIN = "octoprint"
|
DOMAIN = "octoprint"
|
||||||
|
|
||||||
DEFAULT_NAME = "OctoPrint"
|
DEFAULT_NAME = "OctoPrint"
|
||||||
|
|
||||||
|
SERVICE_CONNECT = "printer_connect"
|
||||||
|
CONF_BAUDRATE = "baudrate"
|
||||||
|
27
homeassistant/components/octoprint/services.yaml
Normal file
27
homeassistant/components/octoprint/services.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
printer_connect:
|
||||||
|
fields:
|
||||||
|
device_id:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
device:
|
||||||
|
integration: octoprint
|
||||||
|
profile_name:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
port:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
baudrate:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- "9600"
|
||||||
|
- "19200"
|
||||||
|
- "38400"
|
||||||
|
- "57600"
|
||||||
|
- "115200"
|
||||||
|
- "230400"
|
||||||
|
- "250000"
|
@@ -35,5 +35,34 @@
|
|||||||
"progress": {
|
"progress": {
|
||||||
"get_api_key": "Open the OctoPrint UI and click 'Allow' on the Access Request for 'Home Assistant'."
|
"get_api_key": "Open the OctoPrint UI and click 'Allow' on the Access Request for 'Home Assistant'."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"missing_client": {
|
||||||
|
"message": "No client for device ID: {device_id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"printer_connect": {
|
||||||
|
"name": "Connect to a printer",
|
||||||
|
"description": "Instructs the octoprint server to connect to a printer.",
|
||||||
|
"fields": {
|
||||||
|
"device_id": {
|
||||||
|
"name": "Server",
|
||||||
|
"description": "The server that should connect."
|
||||||
|
},
|
||||||
|
"profile_name": {
|
||||||
|
"name": "Profile name",
|
||||||
|
"description": "Printer profile to connect with."
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"name": "Serial port",
|
||||||
|
"description": "Port name to connect on."
|
||||||
|
},
|
||||||
|
"baudrate": {
|
||||||
|
"name": "Baudrate",
|
||||||
|
"description": "Baud rate."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,8 +66,13 @@ class ThermostatDevice(ClimateEntity):
|
|||||||
"""Interface class for the oemthermostat module."""
|
"""Interface class for the oemthermostat module."""
|
||||||
|
|
||||||
_attr_hvac_modes = SUPPORT_HVAC
|
_attr_hvac_modes = SUPPORT_HVAC
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, thermostat, name):
|
def __init__(self, thermostat, name):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
|
@@ -38,7 +38,8 @@ DEVICE_BINARY_SENSORS: dict[str, tuple[OneWireBinarySensorEntityDescription, ...
|
|||||||
key=f"sensed.{id}",
|
key=f"sensed.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"sensed_{id.lower()}",
|
translation_key="sensed_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
),
|
),
|
||||||
@@ -47,7 +48,8 @@ DEVICE_BINARY_SENSORS: dict[str, tuple[OneWireBinarySensorEntityDescription, ...
|
|||||||
key=f"sensed.{id}",
|
key=f"sensed.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"sensed_{id}",
|
translation_key="sensed_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_7
|
for id in DEVICE_KEYS_0_7
|
||||||
),
|
),
|
||||||
@@ -56,7 +58,8 @@ DEVICE_BINARY_SENSORS: dict[str, tuple[OneWireBinarySensorEntityDescription, ...
|
|||||||
key=f"sensed.{id}",
|
key=f"sensed.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"sensed_{id.lower()}",
|
translation_key="sensed_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
),
|
),
|
||||||
@@ -72,7 +75,8 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireBinarySensorEntityDescription, ...]] = {
|
|||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||||
translation_key=f"hub_short_{id}",
|
translation_key="hub_short_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_3
|
for id in DEVICE_KEYS_0_3
|
||||||
),
|
),
|
||||||
|
@@ -236,7 +236,8 @@ DEVICE_SENSORS: dict[str, tuple[OneWireSensorEntityDescription, ...]] = {
|
|||||||
native_unit_of_measurement="count",
|
native_unit_of_measurement="count",
|
||||||
read_mode=READ_MODE_INT,
|
read_mode=READ_MODE_INT,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
translation_key=f"counter_{id.lower()}",
|
translation_key="counter_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
),
|
),
|
||||||
@@ -276,7 +277,8 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireSensorEntityDescription, ...]] = {
|
|||||||
native_unit_of_measurement=UnitOfPressure.CBAR,
|
native_unit_of_measurement=UnitOfPressure.CBAR,
|
||||||
read_mode=READ_MODE_FLOAT,
|
read_mode=READ_MODE_FLOAT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
translation_key=f"moisture_{id}",
|
translation_key="moisture_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_3
|
for id in DEVICE_KEYS_0_3
|
||||||
),
|
),
|
||||||
@@ -396,7 +398,8 @@ def get_entities(
|
|||||||
description,
|
description,
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
translation_key=f"wetness_{s_id}",
|
translation_key="wetness_id",
|
||||||
|
translation_placeholders={"id": s_id},
|
||||||
)
|
)
|
||||||
override_key = None
|
override_key = None
|
||||||
if description.override_key:
|
if description.override_key:
|
||||||
|
@@ -21,55 +21,16 @@
|
|||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"sensed_a": {
|
"sensed_id": {
|
||||||
"name": "Sensed A"
|
"name": "Sensed {id}"
|
||||||
},
|
},
|
||||||
"sensed_b": {
|
"hub_short_id": {
|
||||||
"name": "Sensed B"
|
"name": "Hub short on branch {id}"
|
||||||
},
|
|
||||||
"sensed_0": {
|
|
||||||
"name": "Sensed 0"
|
|
||||||
},
|
|
||||||
"sensed_1": {
|
|
||||||
"name": "Sensed 1"
|
|
||||||
},
|
|
||||||
"sensed_2": {
|
|
||||||
"name": "Sensed 2"
|
|
||||||
},
|
|
||||||
"sensed_3": {
|
|
||||||
"name": "Sensed 3"
|
|
||||||
},
|
|
||||||
"sensed_4": {
|
|
||||||
"name": "Sensed 4"
|
|
||||||
},
|
|
||||||
"sensed_5": {
|
|
||||||
"name": "Sensed 5"
|
|
||||||
},
|
|
||||||
"sensed_6": {
|
|
||||||
"name": "Sensed 6"
|
|
||||||
},
|
|
||||||
"sensed_7": {
|
|
||||||
"name": "Sensed 7"
|
|
||||||
},
|
|
||||||
"hub_short_0": {
|
|
||||||
"name": "Hub short on branch 0"
|
|
||||||
},
|
|
||||||
"hub_short_1": {
|
|
||||||
"name": "Hub short on branch 1"
|
|
||||||
},
|
|
||||||
"hub_short_2": {
|
|
||||||
"name": "Hub short on branch 2"
|
|
||||||
},
|
|
||||||
"hub_short_3": {
|
|
||||||
"name": "Hub short on branch 3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"counter_a": {
|
"counter_id": {
|
||||||
"name": "Counter A"
|
"name": "Counter {id}"
|
||||||
},
|
|
||||||
"counter_b": {
|
|
||||||
"name": "Counter B"
|
|
||||||
},
|
},
|
||||||
"humidity_hih3600": {
|
"humidity_hih3600": {
|
||||||
"name": "HIH3600 humidity"
|
"name": "HIH3600 humidity"
|
||||||
@@ -86,17 +47,8 @@
|
|||||||
"humidity_raw": {
|
"humidity_raw": {
|
||||||
"name": "Raw humidity"
|
"name": "Raw humidity"
|
||||||
},
|
},
|
||||||
"moisture_1": {
|
"moisture_id": {
|
||||||
"name": "Moisture 1"
|
"name": "Moisture {id}"
|
||||||
},
|
|
||||||
"moisture_2": {
|
|
||||||
"name": "Moisture 2"
|
|
||||||
},
|
|
||||||
"moisture_3": {
|
|
||||||
"name": "Moisture 3"
|
|
||||||
},
|
|
||||||
"moisture_4": {
|
|
||||||
"name": "Moisture 4"
|
|
||||||
},
|
},
|
||||||
"thermocouple_temperature_k": {
|
"thermocouple_temperature_k": {
|
||||||
"name": "Thermocouple K temperature"
|
"name": "Thermocouple K temperature"
|
||||||
@@ -113,121 +65,31 @@
|
|||||||
"voltage_vis_gradient": {
|
"voltage_vis_gradient": {
|
||||||
"name": "VIS voltage gradient"
|
"name": "VIS voltage gradient"
|
||||||
},
|
},
|
||||||
"wetness_0": {
|
"wetness_id": {
|
||||||
"name": "Wetness 0"
|
"name": "Wetness {id}"
|
||||||
},
|
|
||||||
"wetness_1": {
|
|
||||||
"name": "Wetness 1"
|
|
||||||
},
|
|
||||||
"wetness_2": {
|
|
||||||
"name": "Wetness 2"
|
|
||||||
},
|
|
||||||
"wetness_3": {
|
|
||||||
"name": "Wetness 3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"hub_branch_0": {
|
"hub_branch_id": {
|
||||||
"name": "Hub branch 0"
|
"name": "Hub branch {id}"
|
||||||
},
|
|
||||||
"hub_branch_1": {
|
|
||||||
"name": "Hub branch 1"
|
|
||||||
},
|
|
||||||
"hub_branch_2": {
|
|
||||||
"name": "Hub branch 2"
|
|
||||||
},
|
|
||||||
"hub_branch_3": {
|
|
||||||
"name": "Hub branch 3"
|
|
||||||
},
|
},
|
||||||
"iad": {
|
"iad": {
|
||||||
"name": "Current A/D control"
|
"name": "Current A/D control"
|
||||||
},
|
},
|
||||||
"latch_0": {
|
"latch_id": {
|
||||||
"name": "Latch 0"
|
"name": "Latch {id}"
|
||||||
},
|
},
|
||||||
"latch_1": {
|
"leaf_sensor_id": {
|
||||||
"name": "Latch 1"
|
"name": "Leaf sensor {id}"
|
||||||
},
|
},
|
||||||
"latch_2": {
|
"moisture_sensor_id": {
|
||||||
"name": "Latch 2"
|
"name": "Moisture sensor {id}"
|
||||||
},
|
|
||||||
"latch_3": {
|
|
||||||
"name": "Latch 3"
|
|
||||||
},
|
|
||||||
"latch_4": {
|
|
||||||
"name": "Latch 4"
|
|
||||||
},
|
|
||||||
"latch_5": {
|
|
||||||
"name": "Latch 5"
|
|
||||||
},
|
|
||||||
"latch_6": {
|
|
||||||
"name": "Latch 6"
|
|
||||||
},
|
|
||||||
"latch_7": {
|
|
||||||
"name": "Latch 7"
|
|
||||||
},
|
|
||||||
"latch_a": {
|
|
||||||
"name": "Latch A"
|
|
||||||
},
|
|
||||||
"latch_b": {
|
|
||||||
"name": "Latch B"
|
|
||||||
},
|
|
||||||
"leaf_sensor_0": {
|
|
||||||
"name": "Leaf sensor 0"
|
|
||||||
},
|
|
||||||
"leaf_sensor_1": {
|
|
||||||
"name": "Leaf sensor 1"
|
|
||||||
},
|
|
||||||
"leaf_sensor_2": {
|
|
||||||
"name": "Leaf sensor 2"
|
|
||||||
},
|
|
||||||
"leaf_sensor_3": {
|
|
||||||
"name": "Leaf sensor 3"
|
|
||||||
},
|
|
||||||
"moisture_sensor_0": {
|
|
||||||
"name": "Moisture sensor 0"
|
|
||||||
},
|
|
||||||
"moisture_sensor_1": {
|
|
||||||
"name": "Moisture sensor 1"
|
|
||||||
},
|
|
||||||
"moisture_sensor_2": {
|
|
||||||
"name": "Moisture sensor 2"
|
|
||||||
},
|
|
||||||
"moisture_sensor_3": {
|
|
||||||
"name": "Moisture sensor 3"
|
|
||||||
},
|
},
|
||||||
"pio": {
|
"pio": {
|
||||||
"name": "Programmed input-output"
|
"name": "Programmed input-output"
|
||||||
},
|
},
|
||||||
"pio_0": {
|
"pio_id": {
|
||||||
"name": "Programmed input-output 0"
|
"name": "Programmed input-output {id}"
|
||||||
},
|
|
||||||
"pio_1": {
|
|
||||||
"name": "Programmed input-output 1"
|
|
||||||
},
|
|
||||||
"pio_2": {
|
|
||||||
"name": "Programmed input-output 2"
|
|
||||||
},
|
|
||||||
"pio_3": {
|
|
||||||
"name": "Programmed input-output 3"
|
|
||||||
},
|
|
||||||
"pio_4": {
|
|
||||||
"name": "Programmed input-output 4"
|
|
||||||
},
|
|
||||||
"pio_5": {
|
|
||||||
"name": "Programmed input-output 5"
|
|
||||||
},
|
|
||||||
"pio_6": {
|
|
||||||
"name": "Programmed input-output 6"
|
|
||||||
},
|
|
||||||
"pio_7": {
|
|
||||||
"name": "Programmed input-output 7"
|
|
||||||
},
|
|
||||||
"pio_a": {
|
|
||||||
"name": "Programmed input-output A"
|
|
||||||
},
|
|
||||||
"pio_b": {
|
|
||||||
"name": "Programmed input-output B"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -42,7 +42,8 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
key=f"PIO.{id}",
|
key=f"PIO.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"pio_{id.lower()}",
|
translation_key="pio_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
]
|
]
|
||||||
@@ -51,7 +52,8 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
key=f"latch.{id}",
|
key=f"latch.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"latch_{id.lower()}",
|
translation_key="latch_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
]
|
]
|
||||||
@@ -71,7 +73,8 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
key=f"PIO.{id}",
|
key=f"PIO.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"pio_{id}",
|
translation_key="pio_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_7
|
for id in DEVICE_KEYS_0_7
|
||||||
]
|
]
|
||||||
@@ -80,7 +83,8 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
key=f"latch.{id}",
|
key=f"latch.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"latch_{id}",
|
translation_key="latch_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_7
|
for id in DEVICE_KEYS_0_7
|
||||||
]
|
]
|
||||||
@@ -90,7 +94,8 @@ DEVICE_SWITCHES: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
key=f"PIO.{id}",
|
key=f"PIO.{id}",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
translation_key=f"pio_{id.lower()}",
|
translation_key="pio_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_A_B
|
for id in DEVICE_KEYS_A_B
|
||||||
),
|
),
|
||||||
@@ -106,7 +111,8 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key=f"hub_branch_{id}",
|
translation_key="hub_branch_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_3
|
for id in DEVICE_KEYS_0_3
|
||||||
),
|
),
|
||||||
@@ -117,7 +123,8 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key=f"leaf_sensor_{id}",
|
translation_key="leaf_sensor_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_3
|
for id in DEVICE_KEYS_0_3
|
||||||
]
|
]
|
||||||
@@ -127,7 +134,8 @@ HOBBYBOARD_EF: dict[str, tuple[OneWireEntityDescription, ...]] = {
|
|||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
read_mode=READ_MODE_BOOL,
|
read_mode=READ_MODE_BOOL,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key=f"moisture_sensor_{id}",
|
translation_key="moisture_sensor_id",
|
||||||
|
translation_placeholders={"id": str(id)},
|
||||||
)
|
)
|
||||||
for id in DEVICE_KEYS_0_3
|
for id in DEVICE_KEYS_0_3
|
||||||
]
|
]
|
||||||
|
@@ -84,6 +84,7 @@ class OpenThermClimate(ClimateEntity):
|
|||||||
_away_state_a = False
|
_away_state_a = False
|
||||||
_away_state_b = False
|
_away_state_b = False
|
||||||
_current_operation: HVACAction | None = None
|
_current_operation: HVACAction | None = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, gw_dev, options):
|
def __init__(self, gw_dev, options):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
|
@@ -53,6 +53,7 @@ class AtlanticElectricalHeater(OverkizEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> HVACMode:
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
@@ -75,6 +75,7 @@ class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint(
|
|||||||
| ClimateEntityFeature.TURN_ON
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -45,6 +45,7 @@ class AtlanticElectricalTowelDryer(OverkizEntity, ClimateEntity):
|
|||||||
_attr_preset_modes = [*PRESET_MODE_TO_OVERKIZ]
|
_attr_preset_modes = [*PRESET_MODE_TO_OVERKIZ]
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -54,6 +54,7 @@ class AtlanticHeatRecoveryVentilation(OverkizEntity, ClimateEntity):
|
|||||||
| ClimateEntityFeature.TURN_ON
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -83,6 +83,7 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -30,6 +30,7 @@ class AtlanticPassAPCZoneControl(OverkizEntity, ClimateEntity):
|
|||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> HVACMode:
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
@@ -90,6 +90,7 @@ class HitachiAirToAirHeatPumpHLRRWIFI(OverkizEntity, ClimateEntity):
|
|||||||
_attr_target_temperature_step = 1.0
|
_attr_target_temperature_step = 1.0
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -81,6 +81,7 @@ class SomfyHeatingTemperatureInterface(OverkizEntity, ClimateEntity):
|
|||||||
# Both min and max temp values have been retrieved from the Somfy Application.
|
# Both min and max temp values have been retrieved from the Somfy Application.
|
||||||
_attr_min_temp = 15.0
|
_attr_min_temp = 15.0
|
||||||
_attr_max_temp = 26.0
|
_attr_max_temp = 26.0
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -64,6 +64,7 @@ class SomfyThermostat(OverkizEntity, ClimateEntity):
|
|||||||
_attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ]
|
_attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ]
|
||||||
_attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ]
|
_attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ]
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
# Both min and max temp values have been retrieved from the Somfy Application.
|
# Both min and max temp values have been retrieved from the Somfy Application.
|
||||||
_attr_min_temp = 15.0
|
_attr_min_temp = 15.0
|
||||||
|
@@ -58,6 +58,7 @@ class ValveHeatingTemperatureInterface(OverkizEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_translation_key = DOMAIN
|
_attr_translation_key = DOMAIN
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
|
||||||
|
@@ -113,12 +113,6 @@ POWERWALL_INSTANT_SENSORS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_battery_charge(battery_data: BatteryResponse) -> float:
|
|
||||||
"""Get the current value in %."""
|
|
||||||
ratio = float(battery_data.energy_remaining) / float(battery_data.capacity)
|
|
||||||
return round(100 * ratio, 1)
|
|
||||||
|
|
||||||
|
|
||||||
BATTERY_INSTANT_SENSORS: list[PowerwallSensorEntityDescription] = [
|
BATTERY_INSTANT_SENSORS: list[PowerwallSensorEntityDescription] = [
|
||||||
PowerwallSensorEntityDescription[BatteryResponse, int](
|
PowerwallSensorEntityDescription[BatteryResponse, int](
|
||||||
key="battery_capacity",
|
key="battery_capacity",
|
||||||
@@ -202,16 +196,6 @@ BATTERY_INSTANT_SENSORS: list[PowerwallSensorEntityDescription] = [
|
|||||||
suggested_display_precision=1,
|
suggested_display_precision=1,
|
||||||
value_fn=lambda battery_data: battery_data.energy_remaining,
|
value_fn=lambda battery_data: battery_data.energy_remaining,
|
||||||
),
|
),
|
||||||
PowerwallSensorEntityDescription[BatteryResponse, float](
|
|
||||||
key="charge",
|
|
||||||
translation_key="charge",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
suggested_display_precision=0,
|
|
||||||
value_fn=_get_battery_charge,
|
|
||||||
),
|
|
||||||
PowerwallSensorEntityDescription[BatteryResponse, str](
|
PowerwallSensorEntityDescription[BatteryResponse, str](
|
||||||
key="grid_state",
|
key="grid_state",
|
||||||
translation_key="grid_state",
|
translation_key="grid_state",
|
||||||
|
@@ -60,6 +60,7 @@ class ProliphixThermostat(ClimateEntity):
|
|||||||
_attr_precision = PRECISION_TENTHS
|
_attr_precision = PRECISION_TENTHS
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, pdp):
|
def __init__(self, pdp):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
49
homeassistant/components/proximity/diagnostics.py
Normal file
49
homeassistant/components/proximity/diagnostics.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""Diagnostics support for Proximity."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.device_tracker import ATTR_GPS, ATTR_IP, ATTR_MAC
|
||||||
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
|
from homeassistant.components.person import ATTR_USER_ID
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import ProximityDataUpdateCoordinator
|
||||||
|
|
||||||
|
TO_REDACT = {
|
||||||
|
ATTR_GPS,
|
||||||
|
ATTR_IP,
|
||||||
|
ATTR_LATITUDE,
|
||||||
|
ATTR_LONGITUDE,
|
||||||
|
ATTR_MAC,
|
||||||
|
ATTR_USER_ID,
|
||||||
|
"context",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_config_entry_diagnostics(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return diagnostics for a config entry."""
|
||||||
|
coordinator: ProximityDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
diag_data = {
|
||||||
|
"entry": entry.as_dict(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tracked_states: dict[str, dict] = {}
|
||||||
|
for tracked_entity_id in coordinator.tracked_entities:
|
||||||
|
if (state := hass.states.get(tracked_entity_id)) is None:
|
||||||
|
continue
|
||||||
|
tracked_states[tracked_entity_id] = state.as_dict()
|
||||||
|
|
||||||
|
diag_data["data"] = {
|
||||||
|
"proximity": coordinator.data.proximity,
|
||||||
|
"entities": coordinator.data.entities,
|
||||||
|
"entity_mapping": coordinator.entity_mapping,
|
||||||
|
"tracked_states": async_redact_data(tracked_states, TO_REDACT),
|
||||||
|
}
|
||||||
|
return diag_data
|
@@ -106,6 +106,7 @@ class RadioThermostat(RadioThermostatEntity, ClimateEntity):
|
|||||||
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||||
_attr_precision = PRECISION_HALVES
|
_attr_precision = PRECISION_HALVES
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator: RadioThermUpdateCoordinator) -> None:
|
def __init__(self, coordinator: RadioThermUpdateCoordinator) -> None:
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
@@ -113,7 +114,10 @@ class RadioThermostat(RadioThermostatEntity, ClimateEntity):
|
|||||||
self._attr_unique_id = self.init_data.mac
|
self._attr_unique_id = self.init_data.mac
|
||||||
self._attr_fan_modes = CT30_FAN_OPERATION_LIST
|
self._attr_fan_modes = CT30_FAN_OPERATION_LIST
|
||||||
self._attr_supported_features = (
|
self._attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.FAN_MODE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
if not isinstance(self.device, radiotherm.thermostat.CT80):
|
if not isinstance(self.device, radiotherm.thermostat.CT80):
|
||||||
return
|
return
|
||||||
|
@@ -794,4 +794,6 @@ def purge_entity_data(
|
|||||||
_LOGGER.debug("Purging entity data hasn't fully completed yet")
|
_LOGGER.debug("Purging entity data hasn't fully completed yet")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_purge_old_entity_ids(instance, session)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@@ -81,6 +81,7 @@ class SchluterThermostat(CoordinatorEntity, ClimateEntity):
|
|||||||
_attr_hvac_modes = [HVACMode.HEAT]
|
_attr_hvac_modes = [HVACMode.HEAT]
|
||||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator, serial_number, api, session_id):
|
def __init__(self, coordinator, serial_number, api, session_id):
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
@@ -81,8 +81,12 @@ class ScreenLogicClimate(ScreenLogicPushEntity, ClimateEntity, RestoreEntity):
|
|||||||
entity_description: ScreenLogicClimateDescription
|
entity_description: ScreenLogicClimateDescription
|
||||||
_attr_hvac_modes = SUPPORTED_MODES
|
_attr_hvac_modes = SUPPORTED_MODES
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.PRESET_MODE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator, entity_description) -> None:
|
def __init__(self, coordinator, entity_description) -> None:
|
||||||
"""Initialize a ScreenLogic climate entity."""
|
"""Initialize a ScreenLogic climate entity."""
|
||||||
|
@@ -191,6 +191,7 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity):
|
|||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_precision = PRECISION_TENTHS
|
_attr_precision = PRECISION_TENTHS
|
||||||
_attr_translation_key = "climate_device"
|
_attr_translation_key = "climate_device"
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, coordinator: SensiboDataUpdateCoordinator, device_id: str
|
self, coordinator: SensiboDataUpdateCoordinator, device_id: str
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sentry",
|
"documentation": "https://www.home-assistant.io/integrations/sentry",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["sentry-sdk==1.39.2"]
|
"requirements": ["sentry-sdk==1.40.0"]
|
||||||
}
|
}
|
||||||
|
@@ -45,6 +45,7 @@ class SENZClimate(CoordinatorEntity, ClimateEntity):
|
|||||||
_attr_min_temp = 5
|
_attr_min_temp = 5
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@@ -167,6 +167,7 @@ class BlockSleepingClimate(
|
|||||||
)
|
)
|
||||||
_attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"]
|
_attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"]
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -448,6 +449,7 @@ class RpcClimate(ShellyRpcEntity, ClimateEntity):
|
|||||||
)
|
)
|
||||||
_attr_target_temperature_step = RPC_THERMOSTAT_SETTINGS["step"]
|
_attr_target_temperature_step = RPC_THERMOSTAT_SETTINGS["step"]
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
|
||||||
def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None:
|
def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user