forked from home-assistant/core
Compare commits
5 Commits
entity_com
...
compensati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02c0cfd680 | ||
|
|
22bb68d610 | ||
|
|
42fe1d6097 | ||
|
|
ebb450db48 | ||
|
|
d73e12df93 |
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.25.12
|
||||
uses: github/codeql-action/init@v3.25.11
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.25.12
|
||||
uses: github/codeql-action/analyze@v3.25.11
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.5.2
|
||||
rev: v0.5.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
|
||||
@@ -21,7 +21,6 @@ homeassistant.helpers.entity_platform
|
||||
homeassistant.helpers.entity_values
|
||||
homeassistant.helpers.event
|
||||
homeassistant.helpers.reload
|
||||
homeassistant.helpers.script
|
||||
homeassistant.helpers.script_variables
|
||||
homeassistant.helpers.singleton
|
||||
homeassistant.helpers.sun
|
||||
@@ -385,7 +384,6 @@ homeassistant.components.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.script.*
|
||||
homeassistant.components.search.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.sensibo.*
|
||||
|
||||
@@ -743,8 +743,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/kitchen_sink/ @home-assistant/core
|
||||
/homeassistant/components/kmtronic/ @dgomes
|
||||
/tests/components/kmtronic/ @dgomes
|
||||
/homeassistant/components/knocki/ @joostlek @jgatto1 @JakeBosh
|
||||
/tests/components/knocki/ @joostlek @jgatto1 @JakeBosh
|
||||
/homeassistant/components/knocki/ @joostlek @jgatto1
|
||||
/tests/components/knocki/ @joostlek @jgatto1
|
||||
/homeassistant/components/knx/ @Julius2342 @farmio @marvin-w
|
||||
/tests/components/knx/ @Julius2342 @farmio @marvin-w
|
||||
/homeassistant/components/kodi/ @OnFreund
|
||||
@@ -884,6 +884,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/moat/ @bdraco
|
||||
/homeassistant/components/mobile_app/ @home-assistant/core
|
||||
/tests/components/mobile_app/ @home-assistant/core
|
||||
/homeassistant/components/modbus/ @janiversen
|
||||
/tests/components/modbus/ @janiversen
|
||||
/homeassistant/components/modem_callerid/ @tkdrob
|
||||
/tests/components/modem_callerid/ @tkdrob
|
||||
/homeassistant/components/modern_forms/ @wonderslug
|
||||
|
||||
@@ -82,54 +82,33 @@ async def async_setup_entry(
|
||||
"""Add Airzone binary sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
added_systems: set[str] = set()
|
||||
added_zones: set[str] = set()
|
||||
binary_sensors: list[AirzoneBinarySensor] = [
|
||||
AirzoneSystemBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_id,
|
||||
system_data,
|
||||
)
|
||||
for system_id, system_data in coordinator.data[AZD_SYSTEMS].items()
|
||||
for description in SYSTEM_BINARY_SENSOR_TYPES
|
||||
if description.key in system_data
|
||||
]
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of binary sensors."""
|
||||
binary_sensors.extend(
|
||||
AirzoneZoneBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
for description in ZONE_BINARY_SENSOR_TYPES
|
||||
if description.key in zone_data
|
||||
)
|
||||
|
||||
entities: list[AirzoneBinarySensor] = []
|
||||
|
||||
systems_data = coordinator.data.get(AZD_SYSTEMS, {})
|
||||
received_systems = set(systems_data)
|
||||
new_systems = received_systems - added_systems
|
||||
if new_systems:
|
||||
entities.extend(
|
||||
AirzoneSystemBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_id,
|
||||
systems_data.get(system_id),
|
||||
)
|
||||
for system_id in new_systems
|
||||
for description in SYSTEM_BINARY_SENSOR_TYPES
|
||||
if description.key in systems_data.get(system_id)
|
||||
)
|
||||
added_systems.update(new_systems)
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
entities.extend(
|
||||
AirzoneZoneBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_BINARY_SENSOR_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
async_add_entities(binary_sensors)
|
||||
|
||||
|
||||
class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
|
||||
|
||||
@@ -102,31 +102,17 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone climate from a config_entry."""
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of climate."""
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
async_add_entities(
|
||||
AirzoneClimate(
|
||||
coordinator,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
async_add_entities(
|
||||
AirzoneClimate(
|
||||
coordinator,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
)
|
||||
|
||||
|
||||
class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.8.0"]
|
||||
"requirements": ["aioairzone==0.7.7"]
|
||||
}
|
||||
|
||||
@@ -83,34 +83,21 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone select from a config_entry."""
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of select."""
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
async_add_entities(
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_SELECT_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
async_add_entities(
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for description in ZONE_SELECT_TYPES
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
if description.key in zone_data
|
||||
)
|
||||
|
||||
|
||||
class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
|
||||
|
||||
@@ -85,37 +85,21 @@ async def async_setup_entry(
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of sensors."""
|
||||
|
||||
entities: list[AirzoneSensor] = []
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
entities.extend(
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_SENSOR_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entities: list[AirzoneSensor] = []
|
||||
sensors: list[AirzoneSensor] = [
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
for description in ZONE_SENSOR_TYPES
|
||||
if description.key in zone_data
|
||||
]
|
||||
|
||||
if AZD_HOT_WATER in coordinator.data:
|
||||
entities.extend(
|
||||
sensors.extend(
|
||||
AirzoneHotWaterSensor(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -126,7 +110,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
if AZD_WEBSERVER in coordinator.data:
|
||||
entities.extend(
|
||||
sensors.extend(
|
||||
AirzoneWebServerSensor(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -136,10 +120,7 @@ async def async_setup_entry(
|
||||
if description.key in coordinator.data[AZD_WEBSERVER]
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
class AirzoneSensor(AirzoneEntity, SensorEntity):
|
||||
|
||||
@@ -61,7 +61,7 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone Water Heater from a config_entry."""
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
if AZD_HOT_WATER in coordinator.data:
|
||||
async_add_entities([AirzoneWaterHeater(coordinator, entry)])
|
||||
|
||||
@@ -1513,7 +1513,7 @@ async def async_api_adjust_range(
|
||||
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
|
||||
service = SERVICE_SET_COVER_POSITION
|
||||
if not (current := entity.attributes.get(cover.ATTR_CURRENT_POSITION)):
|
||||
if not (current := entity.attributes.get(cover.ATTR_POSITION)):
|
||||
msg = f"Unable to determine {entity.entity_id} current position"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
position = response_value = min(100, max(0, range_delta + current))
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/amazon_polly",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["boto3", "botocore", "s3transfer"],
|
||||
"requirements": ["boto3==1.34.131"]
|
||||
"requirements": ["boto3==1.34.51"]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["python_homeassistant_analytics"],
|
||||
"requirements": ["python-homeassistant-analytics==0.7.0"],
|
||||
"requirements": ["python-homeassistant-analytics==0.6.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
},
|
||||
"learn_sendevent": {
|
||||
"name": "Learn sendevent",
|
||||
"description": "Translates a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of performing this action."
|
||||
"description": "Translates a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of calling this service."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioaquacell"],
|
||||
"requirements": ["aioaquacell==0.2.0"]
|
||||
"requirements": ["aioaquacell==0.1.8"]
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@ from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
DOMAIN_DATA_ENTRIES,
|
||||
SIGNAL_CLIENT_DATA,
|
||||
SIGNAL_CLIENT_STARTED,
|
||||
SIGNAL_CLIENT_STOPPED,
|
||||
)
|
||||
|
||||
type ArcamFmjConfigEntry = ConfigEntry[Client]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
@@ -31,21 +31,34 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ArcamFmjConfigEntry) -> bool:
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
hass.data[DOMAIN_DATA_ENTRIES] = {}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up config entry."""
|
||||
entry.runtime_data = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
|
||||
entries = hass.data[DOMAIN_DATA_ENTRIES]
|
||||
|
||||
client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
|
||||
entries[entry.entry_id] = client
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass, _run_client(hass, entry.runtime_data, DEFAULT_SCAN_INTERVAL), "arcam_fmj"
|
||||
hass, _run_client(hass, client, DEFAULT_SCAN_INTERVAL), "arcam_fmj"
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Cleanup before removing config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
hass.data[DOMAIN_DATA_ENTRIES].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def _run_client(hass: HomeAssistant, client: Client, interval: float) -> None:
|
||||
|
||||
@@ -10,11 +10,18 @@ from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import ssdp
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN
|
||||
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN, DOMAIN_DATA_ENTRIES
|
||||
|
||||
|
||||
def get_entry_client(hass: HomeAssistant, entry: ConfigEntry) -> Client:
|
||||
"""Retrieve client associated with a config entry."""
|
||||
client: Client = hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id]
|
||||
return client
|
||||
|
||||
|
||||
class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@@ -11,3 +11,5 @@ EVENT_TURN_ON = "arcam_fmj.turn_on"
|
||||
DEFAULT_PORT = 50000
|
||||
DEFAULT_NAME = "Arcam FMJ"
|
||||
DEFAULT_SCAN_INTERVAL = 5
|
||||
|
||||
DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries"
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.components.media_player import (
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
@@ -26,7 +27,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import ArcamFmjConfigEntry
|
||||
from .config_flow import get_entry_client
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
EVENT_TURN_ON,
|
||||
@@ -40,12 +41,12 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ArcamFmjConfigEntry,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the configuration entry."""
|
||||
|
||||
client = config_entry.runtime_data
|
||||
client = get_entry_client(hass, config_entry)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==6.4.3", "yalexs-ble==2.4.3"]
|
||||
"requirements": ["yalexs==6.4.2", "yalexs-ble==2.4.3"]
|
||||
}
|
||||
|
||||
@@ -9,3 +9,5 @@ from typing import Final
|
||||
DOMAIN: Final = "autarco"
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
SCAN_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
SENSORS_SOLAR: Final = "solar"
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
from autarco import AccountSite, Autarco, Inverter, Solar
|
||||
from autarco import AccountSite, Autarco, Solar
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -17,7 +17,6 @@ class AutarcoData(NamedTuple):
|
||||
"""Class for defining data in dict."""
|
||||
|
||||
solar: Solar
|
||||
inverters: dict[str, Inverter]
|
||||
|
||||
|
||||
class AutarcoDataUpdateCoordinator(DataUpdateCoordinator[AutarcoData]):
|
||||
@@ -45,5 +44,4 @@ class AutarcoDataUpdateCoordinator(DataUpdateCoordinator[AutarcoData]):
|
||||
"""Fetch data from Autarco API."""
|
||||
return AutarcoData(
|
||||
solar=await self.client.get_solar(self.site.public_key),
|
||||
inverters=await self.client.get_inverters(self.site.public_key),
|
||||
)
|
||||
|
||||
@@ -27,16 +27,6 @@ async def async_get_config_entry_diagnostics(
|
||||
"energy_production_month": coordinator.data.solar.energy_production_month,
|
||||
"energy_production_total": coordinator.data.solar.energy_production_total,
|
||||
},
|
||||
"inverters": [
|
||||
{
|
||||
"serial_number": inverter.serial_number,
|
||||
"out_ac_power": inverter.out_ac_power,
|
||||
"out_ac_energy_total": inverter.out_ac_energy_total,
|
||||
"grid_turned_off": inverter.grid_turned_off,
|
||||
"health": inverter.health,
|
||||
}
|
||||
for inverter in coordinator.data.inverters.values()
|
||||
],
|
||||
}
|
||||
for coordinator in autarco_data
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from autarco import Inverter, Solar
|
||||
from autarco import Solar
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -29,7 +29,7 @@ from .coordinator import AutarcoDataUpdateCoordinator
|
||||
class AutarcoSolarSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes an Autarco sensor entity."""
|
||||
|
||||
value_fn: Callable[[Solar], StateType]
|
||||
state: Callable[[Solar], StateType]
|
||||
|
||||
|
||||
SENSORS_SOLAR: tuple[AutarcoSolarSensorEntityDescription, ...] = (
|
||||
@@ -39,21 +39,21 @@ SENSORS_SOLAR: tuple[AutarcoSolarSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda solar: solar.power_production,
|
||||
state=lambda solar: solar.power_production,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_today",
|
||||
translation_key="energy_production_today",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
value_fn=lambda solar: solar.energy_production_today,
|
||||
state=lambda solar: solar.energy_production_today,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_month",
|
||||
translation_key="energy_production_month",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
value_fn=lambda solar: solar.energy_production_month,
|
||||
state=lambda solar: solar.energy_production_month,
|
||||
),
|
||||
AutarcoSolarSensorEntityDescription(
|
||||
key="energy_production_total",
|
||||
@@ -61,34 +61,7 @@ SENSORS_SOLAR: tuple[AutarcoSolarSensorEntityDescription, ...] = (
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda solar: solar.energy_production_total,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AutarcoInverterSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes an Autarco inverter sensor entity."""
|
||||
|
||||
value_fn: Callable[[Inverter], StateType]
|
||||
|
||||
|
||||
SENSORS_INVERTER: tuple[AutarcoInverterSensorEntityDescription, ...] = (
|
||||
AutarcoInverterSensorEntityDescription(
|
||||
key="out_ac_power",
|
||||
translation_key="out_ac_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda inverter: inverter.out_ac_power,
|
||||
),
|
||||
AutarcoInverterSensorEntityDescription(
|
||||
key="out_ac_energy_total",
|
||||
translation_key="out_ac_energy_total",
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
value_fn=lambda inverter: inverter.out_ac_energy_total,
|
||||
state=lambda solar: solar.energy_production_total,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -99,25 +72,14 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Autarco sensors based on a config entry."""
|
||||
entities: list[SensorEntity] = []
|
||||
for coordinator in entry.runtime_data:
|
||||
entities.extend(
|
||||
async_add_entities(
|
||||
AutarcoSolarSensorEntity(
|
||||
coordinator=coordinator,
|
||||
description=description,
|
||||
)
|
||||
for description in SENSORS_SOLAR
|
||||
)
|
||||
entities.extend(
|
||||
AutarcoInverterSensorEntity(
|
||||
coordinator=coordinator,
|
||||
description=description,
|
||||
serial_number=inverter,
|
||||
)
|
||||
for description in SENSORS_INVERTER
|
||||
for inverter in coordinator.data.inverters
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AutarcoSolarSensorEntity(
|
||||
@@ -149,41 +111,4 @@ class AutarcoSolarSensorEntity(
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.solar)
|
||||
|
||||
|
||||
class AutarcoInverterSensorEntity(
|
||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""Defines an Autarco inverter sensor."""
|
||||
|
||||
entity_description: AutarcoInverterSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
coordinator: AutarcoDataUpdateCoordinator,
|
||||
description: AutarcoInverterSensorEntityDescription,
|
||||
serial_number: str,
|
||||
) -> None:
|
||||
"""Initialize Autarco sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.entity_description = description
|
||||
self._serial_number = serial_number
|
||||
self._attr_unique_id = f"{serial_number}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, serial_number)},
|
||||
name=f"Inverter {serial_number}",
|
||||
manufacturer="Autarco",
|
||||
model="Inverter",
|
||||
serial_number=serial_number,
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(
|
||||
self.coordinator.data.inverters[self._serial_number]
|
||||
)
|
||||
return self.entity_description.state(self.coordinator.data.solar)
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
},
|
||||
"energy_production_total": {
|
||||
"name": "Energy production total"
|
||||
},
|
||||
"out_ac_power": {
|
||||
"name": "Power AC output"
|
||||
},
|
||||
"out_ac_energy_total": {
|
||||
"name": "Energy AC output total"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Remove all automations and load new ones from config."""
|
||||
await async_get_blueprints(hass).async_reset_cache()
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if (conf := await component.async_prepare_reload(skip_reset=True)) is None:
|
||||
return
|
||||
if automation_id := service_call.data.get(CONF_ID):
|
||||
await _async_process_single_config(hass, conf, component, automation_id)
|
||||
else:
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
},
|
||||
"issues": {
|
||||
"service_not_found": {
|
||||
"title": "{name} uses an unknown action",
|
||||
"title": "{name} uses an unknown service",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "[%key:component::automation::issues::service_not_found::title%]",
|
||||
"description": "The automation \"{name}\" (`{entity_id}`) has an unknown action: `{service}`.\n\nThis error prevents the automation from running correctly. Maybe this action is no longer available, or perhaps a typo caused it.\n\nTo fix this error, [edit the automation]({edit}) and remove this action.\n\nClick on SUBMIT below to confirm you have fixed this automation."
|
||||
"description": "The automation \"{name}\" (`{entity_id}`) has an action that calls an unknown service: `{service}`.\n\nThis error prevents the automation from running correctly. Maybe this service is no longer available, or perhaps a typo caused it.\n\nTo fix this error, [edit the automation]({edit}) and remove the action that calls this service.\n\nClick on SUBMIT below to confirm you have fixed this automation."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/aws",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiobotocore", "botocore"],
|
||||
"requirements": ["aiobotocore==2.13.1", "botocore==1.34.131"]
|
||||
"requirements": ["aiobotocore==2.13.0"]
|
||||
}
|
||||
|
||||
@@ -65,18 +65,13 @@ class AzureDataExplorerClient:
|
||||
)
|
||||
|
||||
if data[CONF_USE_QUEUED_CLIENT] is True:
|
||||
# Queued is the only option supported on free tier of ADX
|
||||
# Queded is the only option supported on free tear of ADX
|
||||
self.write_client = QueuedIngestClient(kcsb_ingest)
|
||||
else:
|
||||
self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb_ingest)
|
||||
|
||||
self.query_client = KustoClient(kcsb_query)
|
||||
|
||||
# Reduce the HTTP logging, the default INFO logging is too verbose.
|
||||
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(
|
||||
logging.WARNING
|
||||
)
|
||||
|
||||
def test_connection(self) -> None:
|
||||
"""Test connection, will throw Exception if it cannot connect."""
|
||||
|
||||
@@ -85,7 +80,7 @@ class AzureDataExplorerClient:
|
||||
self.query_client.execute_query(self._database, query)
|
||||
|
||||
def ingest_data(self, adx_events: str) -> None:
|
||||
"""Send data to Azure Data Explorer."""
|
||||
"""Send data to Axure Data Explorer."""
|
||||
|
||||
bytes_stream = io.StringIO(adx_events)
|
||||
stream_descriptor = StreamDescriptor(bytes_stream)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"name": "Bayesian",
|
||||
"codeowners": ["@HarvsG"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bayesian",
|
||||
"integration_type": "helper",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"""Support for Blinkstick lights."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
# from blinkstick import blinkstick
|
||||
from blinkstick import blinkstick
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"domain": "blinksticklight",
|
||||
"name": "BlinkStick",
|
||||
"codeowners": [],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"documentation": "https://www.home-assistant.io/integrations/blinksticklight",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["blinkstick"],
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extend = "../../../pyproject.toml"
|
||||
|
||||
lint.extend-ignore = [
|
||||
"F821"
|
||||
]
|
||||
@@ -131,7 +131,7 @@ SENSOR_TYPES: list[BMWSensorEntityDescription] = [
|
||||
BMWSensorEntityDescription(
|
||||
key="fuel_and_battery.remaining_fuel",
|
||||
translation_key="remaining_fuel",
|
||||
device_class=SensorDeviceClass.VOLUME_STORAGE,
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
native_unit_of_measurement=UnitOfVolume.LITERS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"message": "Authentication failed for {email}, check your email and password"
|
||||
},
|
||||
"notify_missing_argument_item": {
|
||||
"message": "Failed to perform action {service}. 'URGENT_MESSAGE' requires a value @ data['item']. Got None"
|
||||
"message": "Failed to call service {service}. 'URGENT_MESSAGE' requires a value @ data['item']. Got None"
|
||||
},
|
||||
"notify_request_failed": {
|
||||
"message": "Failed to send push notification for bring due to a connection error, try again later"
|
||||
|
||||
@@ -8,7 +8,6 @@ DOMAINS_AND_TYPES = {
|
||||
Platform.CLIMATE: {"HYS"},
|
||||
Platform.LIGHT: {"LB1", "LB2"},
|
||||
Platform.REMOTE: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
|
||||
Platform.SELECT: {"HYS"},
|
||||
Platform.SENSOR: {
|
||||
"A1",
|
||||
"MP1S",
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
"""Support for Broadlink selects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BroadlinkDevice
|
||||
from .const import DOMAIN
|
||||
from .entity import BroadlinkEntity
|
||||
|
||||
DAY_ID_TO_NAME = {
|
||||
1: "monday",
|
||||
2: "tuesday",
|
||||
3: "wednesday",
|
||||
4: "thursday",
|
||||
5: "friday",
|
||||
6: "saturday",
|
||||
7: "sunday",
|
||||
}
|
||||
DAY_NAME_TO_ID = {v: k for k, v in DAY_ID_TO_NAME.items()}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Broadlink select."""
|
||||
device = hass.data[DOMAIN].devices[config_entry.entry_id]
|
||||
async_add_entities([BroadlinkDayOfWeek(device)])
|
||||
|
||||
|
||||
class BroadlinkDayOfWeek(BroadlinkEntity, SelectEntity):
|
||||
"""Representation of a Broadlink day of week."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_current_option: str | None = None
|
||||
_attr_options = list(DAY_NAME_TO_ID)
|
||||
_attr_translation_key = "day_of_week"
|
||||
|
||||
def __init__(self, device: BroadlinkDevice) -> None:
|
||||
"""Initialize the select."""
|
||||
super().__init__(device)
|
||||
|
||||
self._attr_unique_id = f"{device.unique_id}-dayofweek"
|
||||
|
||||
def _update_state(self, data: dict[str, Any]) -> None:
|
||||
"""Update the state of the entity."""
|
||||
if data is None or "dayofweek" not in data:
|
||||
self._attr_current_option = None
|
||||
else:
|
||||
self._attr_current_option = DAY_ID_TO_NAME[data["dayofweek"]]
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self._device.async_request(
|
||||
self._device.api.set_time,
|
||||
hour=self._coordinator.data["hour"],
|
||||
minute=self._coordinator.data["min"],
|
||||
second=self._coordinator.data["sec"],
|
||||
day=DAY_NAME_TO_ID[option],
|
||||
)
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
@@ -61,20 +61,6 @@
|
||||
"total_consumption": {
|
||||
"name": "Total consumption"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"day_of_week": {
|
||||
"name": "Day of week",
|
||||
"state": {
|
||||
"monday": "[%key:common::time::monday%]",
|
||||
"tuesday": "[%key:common::time::tuesday%]",
|
||||
"wednesday": "[%key:common::time::wednesday%]",
|
||||
"thursday": "[%key:common::time::thursday%]",
|
||||
"friday": "[%key:common::time::friday%]",
|
||||
"saturday": "[%key:common::time::saturday%]",
|
||||
"sunday": "[%key:common::time::sunday%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +111,12 @@
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_service_calendar_list_events": {
|
||||
"title": "Detected use of deprecated action `calendar.list_events`",
|
||||
"title": "Detected use of deprecated service `calendar.list_events`",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": "[%key:component::calendar::issues::deprecated_service_calendar_list_events::title%]",
|
||||
"description": "Use `calendar.get_events` instead which supports multiple entities.\n\nPlease replace this action and adjust your automations and scripts and select **submit** to close this issue."
|
||||
"description": "Use `calendar.get_events` instead which supports multiple entities.\n\nPlease replace this service and adjust your automations and scripts and select **submit** to close this issue."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ import numpy as np
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_MAXIMUM,
|
||||
CONF_MINIMUM,
|
||||
CONF_NAME,
|
||||
CONF_SOURCE,
|
||||
CONF_UNIQUE_ID,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -32,6 +35,7 @@ from .const import (
|
||||
DEFAULT_DEGREE,
|
||||
DEFAULT_PRECISION,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -77,59 +81,104 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def create_compensation_data(
|
||||
hass: HomeAssistant, compensation: str, conf: ConfigType, should_raise: bool = False
|
||||
) -> None:
|
||||
"""Create compensation data."""
|
||||
_LOGGER.debug("Setup %s.%s", DOMAIN, compensation)
|
||||
|
||||
degree = conf[CONF_DEGREE]
|
||||
|
||||
initial_coefficients: list[tuple[float, float]] = conf[CONF_DATAPOINTS]
|
||||
sorted_coefficients = sorted(initial_coefficients, key=itemgetter(0))
|
||||
|
||||
# get x values and y values from the x,y point pairs
|
||||
x_values, y_values = zip(*initial_coefficients, strict=False)
|
||||
|
||||
# try to get valid coefficients for a polynomial
|
||||
coefficients = None
|
||||
with np.errstate(all="raise"):
|
||||
try:
|
||||
coefficients = np.polyfit(x_values, y_values, degree)
|
||||
except FloatingPointError as error:
|
||||
_LOGGER.error(
|
||||
"Setup of %s encountered an error, %s",
|
||||
compensation,
|
||||
error,
|
||||
)
|
||||
if should_raise:
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="setup_error",
|
||||
translation_placeholders={
|
||||
"title": conf[CONF_NAME],
|
||||
"error": str(error),
|
||||
},
|
||||
) from error
|
||||
|
||||
if coefficients is not None:
|
||||
data = {
|
||||
k: v for k, v in conf.items() if k not in [CONF_DEGREE, CONF_DATAPOINTS]
|
||||
}
|
||||
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
||||
|
||||
if data[CONF_LOWER_LIMIT]:
|
||||
data[CONF_MINIMUM] = sorted_coefficients[0]
|
||||
else:
|
||||
data[CONF_MINIMUM] = None
|
||||
|
||||
if data[CONF_UPPER_LIMIT]:
|
||||
data[CONF_MAXIMUM] = sorted_coefficients[-1]
|
||||
else:
|
||||
data[CONF_MAXIMUM] = None
|
||||
|
||||
hass.data[DATA_COMPENSATION][compensation] = data
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Compensation sensor."""
|
||||
hass.data[DATA_COMPENSATION] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
for compensation, conf in config[DOMAIN].items():
|
||||
_LOGGER.debug("Setup %s.%s", DOMAIN, compensation)
|
||||
|
||||
degree = conf[CONF_DEGREE]
|
||||
|
||||
initial_coefficients: list[tuple[float, float]] = conf[CONF_DATAPOINTS]
|
||||
sorted_coefficients = sorted(initial_coefficients, key=itemgetter(0))
|
||||
|
||||
# get x values and y values from the x,y point pairs
|
||||
x_values, y_values = zip(*initial_coefficients, strict=False)
|
||||
|
||||
# try to get valid coefficients for a polynomial
|
||||
coefficients = None
|
||||
with np.errstate(all="raise"):
|
||||
try:
|
||||
coefficients = np.polyfit(x_values, y_values, degree)
|
||||
except FloatingPointError as error:
|
||||
_LOGGER.error(
|
||||
"Setup of %s encountered an error, %s",
|
||||
compensation,
|
||||
error,
|
||||
)
|
||||
|
||||
if coefficients is not None:
|
||||
data = {
|
||||
k: v for k, v in conf.items() if k not in [CONF_DEGREE, CONF_DATAPOINTS]
|
||||
}
|
||||
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
||||
|
||||
if data[CONF_LOWER_LIMIT]:
|
||||
data[CONF_MINIMUM] = sorted_coefficients[0]
|
||||
else:
|
||||
data[CONF_MINIMUM] = None
|
||||
|
||||
if data[CONF_UPPER_LIMIT]:
|
||||
data[CONF_MAXIMUM] = sorted_coefficients[-1]
|
||||
else:
|
||||
data[CONF_MAXIMUM] = None
|
||||
|
||||
hass.data[DATA_COMPENSATION][compensation] = data
|
||||
|
||||
hass.async_create_task(
|
||||
async_load_platform(
|
||||
hass,
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
{CONF_COMPENSATION: compensation},
|
||||
config,
|
||||
)
|
||||
await create_compensation_data(hass, compensation, conf)
|
||||
hass.async_create_task(
|
||||
async_load_platform(
|
||||
hass,
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
{CONF_COMPENSATION: compensation},
|
||||
config,
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Compensation from a config entry."""
|
||||
config = dict(entry.options)
|
||||
data_points = config[CONF_DATAPOINTS]
|
||||
new_data_points = []
|
||||
for data_point in data_points:
|
||||
values = data_point.split(",", maxsplit=1)
|
||||
new_data_points.append([float(values[0]), float(values[1])])
|
||||
config[CONF_DATAPOINTS] = new_data_points
|
||||
|
||||
await create_compensation_data(hass, entry.entry_id, config, True)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload Compensation config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
147
homeassistant/components/compensation/config_flow.py
Normal file
147
homeassistant/components/compensation/config_flow.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""Config flow for statistics."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
)
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaCommonFlowHandler,
|
||||
SchemaConfigFlowHandler,
|
||||
SchemaFlowError,
|
||||
SchemaFlowFormStep,
|
||||
)
|
||||
from homeassistant.helpers.selector import (
|
||||
AttributeSelector,
|
||||
AttributeSelectorConfig,
|
||||
BooleanSelector,
|
||||
EntitySelector,
|
||||
NumberSelector,
|
||||
NumberSelectorConfig,
|
||||
NumberSelectorMode,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
TextSelector,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
CONF_DATAPOINTS,
|
||||
CONF_DEGREE,
|
||||
CONF_LOWER_LIMIT,
|
||||
CONF_PRECISION,
|
||||
CONF_UPPER_LIMIT,
|
||||
DEFAULT_DEGREE,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PRECISION,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
async def get_options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
||||
"""Get options schema."""
|
||||
entity_id = handler.options[CONF_ENTITY_ID]
|
||||
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DATAPOINTS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[],
|
||||
multiple=True,
|
||||
custom_value=True,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_ATTRIBUTE): AttributeSelector(
|
||||
AttributeSelectorConfig(entity_id=entity_id)
|
||||
),
|
||||
vol.Optional(CONF_UPPER_LIMIT, default=False): BooleanSelector(),
|
||||
vol.Optional(CONF_LOWER_LIMIT, default=False): BooleanSelector(),
|
||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): NumberSelector(
|
||||
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
||||
),
|
||||
vol.Optional(CONF_DEGREE, default=DEFAULT_DEGREE): NumberSelector(
|
||||
NumberSelectorConfig(min=0, max=7, step=1, mode=NumberSelectorMode.BOX)
|
||||
),
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): TextSelector(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _is_valid_data_points(check_data_points: list[str]) -> bool:
|
||||
"""Validate data points."""
|
||||
result = False
|
||||
for data_point in check_data_points:
|
||||
if not data_point.find(",") > 0:
|
||||
return False
|
||||
values = data_point.split(",", maxsplit=1)
|
||||
for value in values:
|
||||
try:
|
||||
float(value)
|
||||
except ValueError:
|
||||
return False
|
||||
result = True
|
||||
return result
|
||||
|
||||
|
||||
async def validate_options(
|
||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Validate options selected."""
|
||||
|
||||
user_input[CONF_PRECISION] = int(user_input[CONF_PRECISION])
|
||||
user_input[CONF_DEGREE] = int(user_input[CONF_DEGREE])
|
||||
|
||||
if not _is_valid_data_points(user_input[CONF_DATAPOINTS]):
|
||||
raise SchemaFlowError("incorrect_datapoints")
|
||||
|
||||
if len(user_input[CONF_DATAPOINTS]) <= user_input[CONF_DEGREE]:
|
||||
raise SchemaFlowError("not_enough_datapoints")
|
||||
|
||||
handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
|
||||
|
||||
return user_input
|
||||
|
||||
|
||||
DATA_SCHEMA_SETUP = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
|
||||
vol.Required(CONF_ENTITY_ID): EntitySelector(),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_FLOW = {
|
||||
"user": SchemaFlowFormStep(
|
||||
schema=DATA_SCHEMA_SETUP,
|
||||
next_step="options",
|
||||
),
|
||||
"options": SchemaFlowFormStep(
|
||||
schema=get_options_schema,
|
||||
validate_user_input=validate_options,
|
||||
),
|
||||
}
|
||||
OPTIONS_FLOW = {
|
||||
"init": SchemaFlowFormStep(
|
||||
get_options_schema,
|
||||
validate_user_input=validate_options,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class CompensationConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
||||
"""Handle a config flow for Compensation."""
|
||||
|
||||
config_flow = CONFIG_FLOW
|
||||
options_flow = OPTIONS_FLOW
|
||||
|
||||
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
||||
"""Return config entry title."""
|
||||
return cast(str, options[CONF_NAME])
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Compensation constants."""
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "compensation"
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
SENSOR = "compensation"
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"domain": "compensation",
|
||||
"name": "Compensation",
|
||||
"codeowners": ["@Petro31"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
||||
"integration_type": "helper",
|
||||
"iot_class": "calculated",
|
||||
"requirements": ["numpy==1.26.0"]
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ from typing import Any
|
||||
import numpy as np
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_MAXIMUM,
|
||||
CONF_MINIMUM,
|
||||
CONF_SOURCE,
|
||||
@@ -80,6 +82,36 @@ async def async_setup_platform(
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Compensation sensor entry."""
|
||||
compensation = entry.entry_id
|
||||
conf: dict[str, Any] = hass.data[DATA_COMPENSATION][compensation]
|
||||
|
||||
source: str = conf[CONF_ENTITY_ID]
|
||||
attribute: str | None = conf.get(CONF_ATTRIBUTE)
|
||||
name = entry.title
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
CompensationSensor(
|
||||
entry.entry_id,
|
||||
name,
|
||||
source,
|
||||
attribute,
|
||||
conf[CONF_PRECISION],
|
||||
conf[CONF_POLYNOMIAL],
|
||||
conf.get(CONF_UNIT_OF_MEASUREMENT),
|
||||
conf[CONF_MINIMUM],
|
||||
conf[CONF_MAXIMUM],
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class CompensationSensor(SensorEntity):
|
||||
"""Representation of a Compensation sensor."""
|
||||
|
||||
|
||||
82
homeassistant/components/compensation/strings.json
Normal file
82
homeassistant/components/compensation/strings.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"incorrect_datapoints": "Datapoints needs to be provided in the right format, ex. '1.0, 0.0'.",
|
||||
"not_enough_datapoints": "The number of datapoints needs to be more than the configured degree."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Add a compensation sensor",
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"entity_id": "Entity"
|
||||
},
|
||||
"data_description": {
|
||||
"name": "Name for the created entity.",
|
||||
"entity_id": "Entity to use as source."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"description": "Read the documention for further details on how to configure the statistics sensor using these options.",
|
||||
"data": {
|
||||
"data_points": "Data points",
|
||||
"attribute": "Attribute",
|
||||
"upper_limit": "Upper limit",
|
||||
"lower_limit": "Lower limit",
|
||||
"precision": "Precision",
|
||||
"degree": "Degree",
|
||||
"unit_of_measurement": "Unit of measurement"
|
||||
},
|
||||
"data_description": {
|
||||
"data_points": "The collection of data point conversions with the format 'uncompensated_value, compensated_value', ex. '1.0, 0.0'",
|
||||
"attribute": "Attribute from the source to monitor/compensate.",
|
||||
"upper_limit": "Enables an upper limit for the sensor. The upper limit is defined by the data collections (data_points) greatest uncompensated value.",
|
||||
"lower_limit": "Enables a lower limit for the sensor. The lower limit is defined by the data collections (data_points) lowest uncompensated value.",
|
||||
"precision": "Defines the precision of the calculated values, through the argument of round().",
|
||||
"degree": "The degree of a polynomial.",
|
||||
"unit_of_measurement": "Defines the units of measurement of the sensor, if any."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||
},
|
||||
"error": {
|
||||
"incorrect_datapoints": "[%key:component::compensation::config::error::incorrect_datapoints%]",
|
||||
"not_enough_datapoints": "[%key:component::compensation::config::error::not_enough_datapoints%]"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "[%key:component::compensation::config::step::options::description%]",
|
||||
"data": {
|
||||
"data_points": "[%key:component::compensation::config::step::options::data::data_points%]",
|
||||
"attribute": "[%key:component::compensation::config::step::options::data::attribute%]",
|
||||
"upper_limit": "[%key:component::compensation::config::step::options::data::upper_limit%]",
|
||||
"lower_limit": "[%key:component::compensation::config::step::options::data::lower_limit%]",
|
||||
"precision": "[%key:component::compensation::config::step::options::data::precision%]",
|
||||
"degree": "[%key:component::compensation::config::step::options::data::degree%]",
|
||||
"unit_of_measurement": "[%key:component::compensation::config::step::options::data::unit_of_measurement%]"
|
||||
},
|
||||
"data_description": {
|
||||
"data_points": "[%key:component::compensation::config::step::options::data_description::data_points%]",
|
||||
"attribute": "[%key:component::compensation::config::step::options::data_description::attribute%]",
|
||||
"upper_limit": "[%key:component::compensation::config::step::options::data_description::upper_limit%]",
|
||||
"lower_limit": "[%key:component::compensation::config::step::options::data_description::lower_limit%]",
|
||||
"precision": "[%key:component::compensation::config::step::options::data_description::precision%]",
|
||||
"degree": "[%key:component::compensation::config::step::options::data_description::degree%]",
|
||||
"unit_of_measurement": "[%key:component::compensation::config::step::options::data_description::unit_of_measurement%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"setup_error": {
|
||||
"message": "Setup of {title} could not be setup due to {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
"""Support for Concord232 alarm control panels."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
# from concord232 import client as concord232_client
|
||||
from concord232 import client as concord232_client
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"""Support for exposing Concord232 elements as sensors."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
# from concord232 import client as concord232_client
|
||||
from concord232 import client as concord232_client
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"domain": "concord232",
|
||||
"name": "Concord232",
|
||||
"codeowners": [],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"documentation": "https://www.home-assistant.io/integrations/concord232",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["concord232", "stevedore"],
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extend = "../../../pyproject.toml"
|
||||
|
||||
lint.extend-ignore = [
|
||||
"F821"
|
||||
]
|
||||
@@ -40,7 +40,6 @@ class DevoloDeviceEntity(Entity):
|
||||
identifiers={(DOMAIN, self._device_instance.uid)},
|
||||
manufacturer=device_instance.brand,
|
||||
model=device_instance.name,
|
||||
model_id=device_instance.identifier,
|
||||
name=device_instance.settings_property["general_device_settings"].name,
|
||||
suggested_area=device_instance.settings_property[
|
||||
"general_device_settings"
|
||||
|
||||
@@ -48,7 +48,6 @@ class DevoloEntity(Entity):
|
||||
identifiers={(DOMAIN, str(self.device.serial_number))},
|
||||
manufacturer="devolo",
|
||||
model=self.device.product,
|
||||
model_id=self.device.mt_number,
|
||||
serial_number=self.device.serial_number,
|
||||
sw_version=self.device.firmware_version,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from doorbirdpy import DoorBird
|
||||
@@ -16,7 +17,7 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -26,6 +27,8 @@ from .device import ConfiguredDoorBird
|
||||
from .models import DoorBirdConfigEntry, DoorBirdData
|
||||
from .view import DoorBirdRequestView
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CUSTOM_URL = "hass_url_override"
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
@@ -49,14 +52,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: DoorBirdConfigEntry) ->
|
||||
|
||||
device = DoorBird(device_ip, username, password, http_session=session)
|
||||
try:
|
||||
status = await device.ready()
|
||||
info = await device.info()
|
||||
except ClientResponseError as err:
|
||||
if err.status == HTTPStatus.UNAUTHORIZED:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
_LOGGER.error(
|
||||
"Authorization rejected by DoorBird for %s@%s", username, device_ip
|
||||
)
|
||||
return False
|
||||
raise ConfigEntryNotReady from err
|
||||
except OSError as oserr:
|
||||
_LOGGER.error("Failed to setup doorbird at %s: %s", device_ip, oserr)
|
||||
raise ConfigEntryNotReady from oserr
|
||||
|
||||
if not status[0]:
|
||||
_LOGGER.error(
|
||||
"Could not connect to DoorBird as %s@%s: Error %s",
|
||||
username,
|
||||
device_ip,
|
||||
str(status[1]),
|
||||
)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
token: str = door_station_config.get(CONF_TOKEN, config_entry_id)
|
||||
custom_url: str | None = door_station_config.get(CONF_CUSTOM_URL)
|
||||
name: str | None = door_station_config.get(CONF_NAME)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -22,7 +21,6 @@ from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNA
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
|
||||
from .const import (
|
||||
CONF_EVENTS,
|
||||
@@ -38,20 +36,14 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_OPTIONS = {CONF_EVENTS: [DEFAULT_DOORBELL_EVENT, DEFAULT_MOTION_EVENT]}
|
||||
|
||||
|
||||
AUTH_VOL_DICT: VolDictType = {
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
AUTH_SCHEMA = vol.Schema(AUTH_VOL_DICT)
|
||||
|
||||
|
||||
def _schema_with_defaults(
|
||||
host: str | None = None, name: str | None = None
|
||||
) -> vol.Schema:
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default=host): str,
|
||||
**AUTH_VOL_DICT,
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Optional(CONF_NAME, default=name): str,
|
||||
}
|
||||
)
|
||||
@@ -64,6 +56,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
||||
data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD], http_session=session
|
||||
)
|
||||
try:
|
||||
status = await device.ready()
|
||||
info = await device.info()
|
||||
except ClientResponseError as err:
|
||||
if err.status == HTTPStatus.UNAUTHORIZED:
|
||||
@@ -72,6 +65,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
||||
except OSError as err:
|
||||
raise CannotConnect from err
|
||||
|
||||
if not status[0]:
|
||||
raise CannotConnect
|
||||
|
||||
mac_addr = get_mac_address_from_door_station_info(info)
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
@@ -100,47 +96,6 @@ class DoorBirdConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the DoorBird config flow."""
|
||||
self.discovery_schema: vol.Schema | None = None
|
||||
self.reauth_entry: ConfigEntry | None = None
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauth."""
|
||||
entry_id = self.context["entry_id"]
|
||||
self.reauth_entry = self.hass.config_entries.async_get_entry(entry_id)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauth input."""
|
||||
errors: dict[str, str] = {}
|
||||
existing_entry = self.reauth_entry
|
||||
assert existing_entry
|
||||
existing_data = existing_entry.data
|
||||
placeholders: dict[str, str] = {
|
||||
CONF_NAME: existing_data[CONF_NAME],
|
||||
CONF_HOST: existing_data[CONF_HOST],
|
||||
}
|
||||
self.context["title_placeholders"] = placeholders
|
||||
if user_input is not None:
|
||||
new_config = {
|
||||
**existing_data,
|
||||
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
}
|
||||
_, errors = await self._async_validate_or_error(new_config)
|
||||
if not errors:
|
||||
return self.async_update_reload_and_abort(
|
||||
existing_entry, data=new_config
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
description_placeholders=placeholders,
|
||||
step_id="reauth_confirm",
|
||||
data_schema=AUTH_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
||||
@@ -4,6 +4,9 @@ from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "doorbird"
|
||||
PLATFORMS = [Platform.BUTTON, Platform.CAMERA, Platform.EVENT]
|
||||
DOOR_STATION = "door_station"
|
||||
DOOR_STATION_INFO = "door_station_info"
|
||||
DOOR_STATION_EVENT_ENTITY_IDS = "door_station_event_entity_ids"
|
||||
|
||||
CONF_EVENTS = "events"
|
||||
MANUFACTURER = "Bird Home Automation Group"
|
||||
|
||||
@@ -7,8 +7,7 @@ from homeassistant.components.event import (
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -71,15 +70,14 @@ class DoorBirdEventEntity(DoorBirdEntity, EventEntity):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to device events."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
self.hass.bus.async_listen(
|
||||
f"{DOMAIN}_{self._doorbird_event.event}",
|
||||
self._async_handle_event,
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_handle_event(self) -> None:
|
||||
def _async_handle_event(self, event: Event) -> None:
|
||||
"""Handle a device event."""
|
||||
event_types = self.entity_description.event_types
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/doorbird",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["doorbirdpy"],
|
||||
"requirements": ["DoorBirdPy==3.0.2"],
|
||||
"requirements": ["DoorBirdPy==3.0.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_axis-video._tcp.local.",
|
||||
|
||||
@@ -23,20 +23,12 @@
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your DoorBird device."
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "Re-authenticate DoorBird device {name} at {host}",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"link_local_address": "Link local addresses are not supported",
|
||||
"not_doorbird_device": "This device is not a DoorBird",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
"not_doorbird_device": "This device is not a DoorBird"
|
||||
},
|
||||
"flow_title": "{name} ({host})",
|
||||
"error": {
|
||||
|
||||
@@ -7,7 +7,6 @@ from http import HTTPStatus
|
||||
from aiohttp import web
|
||||
|
||||
from homeassistant.components.http import KEY_HASS, HomeAssistantView
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import API_URL, DOMAIN
|
||||
from .util import get_door_station_by_token
|
||||
@@ -46,7 +45,5 @@ class DoorBirdRequestView(HomeAssistantView):
|
||||
# Do not copy this pattern in the future
|
||||
# for any new integrations.
|
||||
#
|
||||
event_type = f"{DOMAIN}_{event}"
|
||||
hass.bus.async_fire(event_type, event_data)
|
||||
async_dispatcher_send(hass, event_type)
|
||||
hass.bus.async_fire(f"{DOMAIN}_{event}", event_data)
|
||||
return web.Response(text="OK")
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""Support for Dovado router."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
# import dovado
|
||||
import dovado
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"domain": "dovado",
|
||||
"name": "Dovado",
|
||||
"codeowners": [],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"documentation": "https://www.home-assistant.io/integrations/dovado",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["dovado==0.4.1"]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
extend = "../../../pyproject.toml"
|
||||
|
||||
lint.extend-ignore = [
|
||||
"F821"
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"domain": "dsmr",
|
||||
"name": "DSMR Smart Meter",
|
||||
"name": "DSMR Slimme Meter",
|
||||
"codeowners": ["@Robbie1221", "@frenck"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/dsmr",
|
||||
|
||||
@@ -16,7 +16,7 @@ from dsmr_parser.clients.rfxtrx_protocol import (
|
||||
create_rfxtrx_dsmr_reader,
|
||||
create_rfxtrx_tcp_dsmr_reader,
|
||||
)
|
||||
from dsmr_parser.objects import DSMRObject, Telegram
|
||||
from dsmr_parser.objects import DSMRObject
|
||||
import serial
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
@@ -380,7 +380,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
|
||||
|
||||
|
||||
def create_mbus_entity(
|
||||
mbus: int, mtype: int, telegram: Telegram
|
||||
mbus: int, mtype: int, telegram: dict[str, DSMRObject]
|
||||
) -> DSMRSensorEntityDescription | None:
|
||||
"""Create a new MBUS Entity."""
|
||||
if (
|
||||
@@ -478,7 +478,7 @@ def rename_old_gas_to_mbus(
|
||||
|
||||
|
||||
def create_mbus_entities(
|
||||
hass: HomeAssistant, telegram: Telegram, entry: ConfigEntry
|
||||
hass: HomeAssistant, telegram: dict[str, DSMRObject], entry: ConfigEntry
|
||||
) -> list[DSMREntity]:
|
||||
"""Create MBUS Entities."""
|
||||
entities = []
|
||||
@@ -523,7 +523,7 @@ async def async_setup_entry(
|
||||
add_entities_handler: Callable[..., None] | None
|
||||
|
||||
@callback
|
||||
def init_async_add_entities(telegram: Telegram) -> None:
|
||||
def init_async_add_entities(telegram: dict[str, DSMRObject]) -> None:
|
||||
"""Add the sensor entities after the first telegram was received."""
|
||||
nonlocal add_entities_handler
|
||||
assert add_entities_handler is not None
|
||||
@@ -560,7 +560,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
@Throttle(min_time_between_updates)
|
||||
def update_entities_telegram(telegram: Telegram | None) -> None:
|
||||
def update_entities_telegram(telegram: dict[str, DSMRObject] | None) -> None:
|
||||
"""Update entities with latest telegram and trigger state update."""
|
||||
nonlocal initialized
|
||||
# Make all device entities aware of new telegram
|
||||
@@ -709,7 +709,7 @@ class DSMREntity(SensorEntity):
|
||||
self,
|
||||
entity_description: DSMRSensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
telegram: Telegram,
|
||||
telegram: dict[str, DSMRObject],
|
||||
device_class: SensorDeviceClass,
|
||||
native_unit_of_measurement: str | None,
|
||||
serial_id: str = "",
|
||||
@@ -720,7 +720,7 @@ class DSMREntity(SensorEntity):
|
||||
self._attr_device_class = device_class
|
||||
self._attr_native_unit_of_measurement = native_unit_of_measurement
|
||||
self._entry = entry
|
||||
self.telegram: Telegram | None = telegram
|
||||
self.telegram: dict[str, DSMRObject] | None = telegram
|
||||
|
||||
device_serial = entry.data[CONF_SERIAL_ID]
|
||||
device_name = DEVICE_NAME_ELECTRICITY
|
||||
@@ -750,7 +750,7 @@ class DSMREntity(SensorEntity):
|
||||
self._attr_unique_id = f"{device_serial}_{entity_description.key}"
|
||||
|
||||
@callback
|
||||
def update_data(self, telegram: Telegram | None) -> None:
|
||||
def update_data(self, telegram: dict[str, DSMRObject] | None) -> None:
|
||||
"""Update data."""
|
||||
self.telegram = telegram
|
||||
if self.hass and (
|
||||
|
||||
@@ -171,12 +171,12 @@
|
||||
},
|
||||
"issues": {
|
||||
"migrate_aux_heat": {
|
||||
"title": "Migration of Ecobee set_aux_heat action",
|
||||
"title": "Migration of Ecobee set_aux_heat service",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "The Ecobee `set_aux_heat` action has been migrated. A new `aux_heat_only` switch entity is available for each thermostat that supports a Heat Pump.\n\nUpdate any automations to use the new `aux_heat_only` switch entity. When this is done, fix this issue and restart Home Assistant.",
|
||||
"title": "Disable legacy Ecobee set_aux_heat action"
|
||||
"description": "The Ecobee `set_aux_heat` service has been migrated. A new `aux_heat_only` switch entity is available for each thermostat that supports a Heat Pump.\n\nUpdate any automations to use the new `aux_heat_only` switch entity. When this is done, fix this issue and restart Home Assistant.",
|
||||
"title": "Disable legacy Ecobee set_aux_heat service"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"fields": {
|
||||
"config_entry": {
|
||||
"name": "Config Entry",
|
||||
"description": "The config entry to use for this action."
|
||||
"description": "The config entry to use for this service."
|
||||
},
|
||||
"incl_vat": {
|
||||
"name": "Including VAT",
|
||||
|
||||
@@ -199,8 +199,7 @@ class Enigma2Device(MediaPlayerEntity):
|
||||
|
||||
async def async_mute_volume(self, mute: bool) -> None:
|
||||
"""Mute or unmute."""
|
||||
if mute != self._device.status.muted:
|
||||
await self._device.toggle_mute()
|
||||
await self._device.toggle_mute()
|
||||
|
||||
async def async_select_source(self, source: str) -> None:
|
||||
"""Select input source."""
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.7.2"]
|
||||
"requirements": ["env-canada==0.7.1"]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@@ -35,6 +37,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import device_info
|
||||
from .const import DOMAIN
|
||||
@@ -190,24 +193,53 @@ def get_forecast(ec_data, hourly) -> list[Forecast] | None:
|
||||
if not (half_days := ec_data.daily_forecasts):
|
||||
return None
|
||||
|
||||
def get_day_forecast(fcst: list[dict[str, str]]) -> Forecast:
|
||||
high_temp = int(fcst[0]["temperature"]) if len(fcst) == 2 else None
|
||||
return {
|
||||
ATTR_FORECAST_TIME: fcst[0]["timestamp"],
|
||||
ATTR_FORECAST_NATIVE_TEMP: high_temp,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: int(fcst[-1]["temperature"]),
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
|
||||
fcst[0]["precip_probability"]
|
||||
),
|
||||
ATTR_FORECAST_CONDITION: icon_code_to_condition(
|
||||
int(fcst[0]["icon_code"])
|
||||
),
|
||||
}
|
||||
today: Forecast = {
|
||||
ATTR_FORECAST_TIME: dt_util.now().isoformat(),
|
||||
ATTR_FORECAST_CONDITION: icon_code_to_condition(
|
||||
int(half_days[0]["icon_code"])
|
||||
),
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
|
||||
half_days[0]["precip_probability"]
|
||||
),
|
||||
}
|
||||
|
||||
i = 2 if half_days[0]["temperature_class"] == "high" else 1
|
||||
forecast_array.append(get_day_forecast(half_days[0:i]))
|
||||
for i in range(i, len(half_days) - 1, 2):
|
||||
forecast_array.append(get_day_forecast(half_days[i : i + 2])) # noqa: PERF401
|
||||
if half_days[0]["temperature_class"] == "high":
|
||||
today.update(
|
||||
{
|
||||
ATTR_FORECAST_NATIVE_TEMP: int(half_days[0]["temperature"]),
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[1]["temperature"]),
|
||||
}
|
||||
)
|
||||
half_days = half_days[2:]
|
||||
else:
|
||||
today.update(
|
||||
{
|
||||
ATTR_FORECAST_NATIVE_TEMP: None,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[0]["temperature"]),
|
||||
}
|
||||
)
|
||||
half_days = half_days[1:]
|
||||
|
||||
forecast_array.append(today)
|
||||
|
||||
for day, high, low in zip(
|
||||
range(1, 6), range(0, 9, 2), range(1, 10, 2), strict=False
|
||||
):
|
||||
forecast_array.append(
|
||||
{
|
||||
ATTR_FORECAST_TIME: (
|
||||
dt_util.now() + datetime.timedelta(days=day)
|
||||
).isoformat(),
|
||||
ATTR_FORECAST_NATIVE_TEMP: int(half_days[high]["temperature"]),
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: int(half_days[low]["temperature"]),
|
||||
ATTR_FORECAST_CONDITION: icon_code_to_condition(
|
||||
int(half_days[high]["icon_code"])
|
||||
),
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: int(
|
||||
half_days[high]["precip_probability"]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
else:
|
||||
forecast_array.extend(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"mdns_missing_mac": "Missing MAC address in MDNS properties.",
|
||||
"service_received": "Action received",
|
||||
"service_received": "Service received",
|
||||
"mqtt_missing_mac": "Missing MAC address in MQTT properties.",
|
||||
"mqtt_missing_api": "Missing API port in MQTT properties.",
|
||||
"mqtt_missing_ip": "Missing IP address in MQTT properties."
|
||||
@@ -53,7 +53,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"allow_service_calls": "Allow the device to perform Home Assistant actions."
|
||||
"allow_service_calls": "Allow the device to make Home Assistant service calls."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,8 +102,8 @@
|
||||
"description": "The API password for ESPHome is deprecated and the use of an API encryption key is recommended instead.\n\nRemove the API password and add an encryption key to your ESPHome device to resolve this issue."
|
||||
},
|
||||
"service_calls_not_allowed": {
|
||||
"title": "{name} is not permitted to perform Home Assistant actions",
|
||||
"description": "The ESPHome device attempted to perform a Home Assistant action, but this functionality is not enabled.\n\nIf you trust this device and want to allow it to perfom Home Assistant action, you can enable this functionality in the options flow."
|
||||
"title": "{name} is not permitted to call Home Assistant services",
|
||||
"description": "The ESPHome device attempted to make a Home Assistant service call, but this functionality is not enabled.\n\nIf you trust this device and want to allow it to make Home Assistant service calls, you can enable this functionality in the options flow."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@dgomes"],
|
||||
"dependencies": ["recorder"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/filter",
|
||||
"integration_type": "helper",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"services": {
|
||||
"ptz": {
|
||||
"name": "PTZ",
|
||||
"description": "Pan/Tilt action for Foscam camera.",
|
||||
"description": "Pan/Tilt service for Foscam camera.",
|
||||
"fields": {
|
||||
"movement": {
|
||||
"name": "Movement",
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"ptz_preset": {
|
||||
"name": "PTZ preset",
|
||||
"description": "PTZ Preset action for Foscam camera.",
|
||||
"description": "PTZ Preset service for Foscam camera.",
|
||||
"fields": {
|
||||
"preset_name": {
|
||||
"name": "Preset name",
|
||||
|
||||
@@ -165,10 +165,10 @@
|
||||
},
|
||||
"exceptions": {
|
||||
"config_entry_not_found": {
|
||||
"message": "Failed to perform action \"{service}\". Config entry for target not found"
|
||||
"message": "Failed to call service \"{service}\". Config entry for target not found"
|
||||
},
|
||||
"service_parameter_unknown": { "message": "Action or parameter unknown" },
|
||||
"service_not_supported": { "message": "Action not supported" },
|
||||
"service_parameter_unknown": { "message": "Service or parameter unknown" },
|
||||
"service_not_supported": { "message": "Service not supported" },
|
||||
"error_refresh_hosts_info": {
|
||||
"message": "Error refreshing hosts info"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"fields": {
|
||||
"agent_user_id": {
|
||||
"name": "Agent user ID",
|
||||
"description": "Only needed for automations. Specific Home Assistant user id (not username, ID in configuration > users > under username) to sync with Google Assistant. Do not need when you use this action through Home Assistant front end or API. Used in automation script or other place where context.user_id is missing."
|
||||
"description": "Only needed for automations. Specific Home Assistant user id (not username, ID in configuration > users > under username) to sync with Google Assistant. Do not need when you call this service through Home Assistant front end or API. Used in automation script or other place where context.user_id is missing."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,26 +14,6 @@
|
||||
"local_name": "B5178*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"local_name": "GV5121*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"local_name": "GV5122*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"local_name": "GV5123*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"local_name": "GV5125*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"local_name": "GV5126*",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 1,
|
||||
"service_uuid": "0000ec88-0000-1000-8000-00805f9b34fb",
|
||||
@@ -103,10 +83,6 @@
|
||||
"manufacturer_id": 19506,
|
||||
"service_uuid": "00001801-0000-1000-8000-00805f9b34fb",
|
||||
"connectable": false
|
||||
},
|
||||
{
|
||||
"manufacturer_id": 61320,
|
||||
"connectable": false
|
||||
}
|
||||
],
|
||||
"codeowners": ["@bdraco", "@PierreAronnax"],
|
||||
@@ -114,5 +90,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-ble==0.37.0"]
|
||||
"requirements": ["govee-ble==0.31.3"]
|
||||
}
|
||||
|
||||
@@ -194,7 +194,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
- Remove group.group entities not created by service calls and set them up again
|
||||
- Reload xxx.group platforms
|
||||
"""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if (conf := await component.async_prepare_reload(skip_reset=True)) is None:
|
||||
return
|
||||
|
||||
# Simplified + modified version of EntityPlatform.async_reset:
|
||||
# - group.group never retries setup
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"url": "[%key:common::config_flow::data::url%]",
|
||||
"name": "Override for Habitica’s username. Will be used for actions",
|
||||
"name": "Override for Habitica’s username. Will be used for service calls",
|
||||
"api_user": "Habitica’s API user ID",
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
},
|
||||
|
||||
@@ -289,7 +289,7 @@
|
||||
},
|
||||
"addon_update": {
|
||||
"name": "Update add-on.",
|
||||
"description": "Updates an add-on. This action should be used with caution since add-on updates can contain breaking changes. It is highly recommended that you review release notes/change logs before updating an add-on.",
|
||||
"description": "Updates an add-on. This service should be used with caution since add-on updates can contain breaking changes. It is highly recommended that you review release notes/change logs before updating an add-on.",
|
||||
"fields": {
|
||||
"addon": {
|
||||
"name": "[%key:component::hassio::services::addon_start::fields::addon::name%]",
|
||||
|
||||
@@ -7,9 +7,6 @@ from datetime import timedelta
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ENTITY_ID, CONF_STATE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device import (
|
||||
async_remove_stale_devices_links_keep_entity_device,
|
||||
)
|
||||
from homeassistant.helpers.template import Template
|
||||
|
||||
from .const import CONF_DURATION, CONF_END, CONF_START, PLATFORMS
|
||||
@@ -45,12 +42,6 @@ async def async_setup_entry(
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
async_remove_stale_devices_links_keep_entity_device(
|
||||
hass,
|
||||
entry.entry_id,
|
||||
entry.options[CONF_ENTITY_ID],
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.device import async_device_info_to_link_from_entity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.reload import async_setup_reload_service
|
||||
from homeassistant.helpers.template import Template
|
||||
@@ -112,9 +111,7 @@ async def async_setup_platform(
|
||||
await coordinator.async_refresh()
|
||||
if not coordinator.last_update_success:
|
||||
raise PlatformNotReady from coordinator.last_exception
|
||||
async_add_entities(
|
||||
[HistoryStatsSensor(hass, coordinator, sensor_type, name, unique_id, entity_id)]
|
||||
)
|
||||
async_add_entities([HistoryStatsSensor(coordinator, sensor_type, name, unique_id)])
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -126,13 +123,8 @@ async def async_setup_entry(
|
||||
|
||||
sensor_type: str = entry.options[CONF_TYPE]
|
||||
coordinator = entry.runtime_data
|
||||
entity_id: str = entry.options[CONF_ENTITY_ID]
|
||||
async_add_entities(
|
||||
[
|
||||
HistoryStatsSensor(
|
||||
hass, coordinator, sensor_type, entry.title, entry.entry_id, entity_id
|
||||
)
|
||||
]
|
||||
[HistoryStatsSensor(coordinator, sensor_type, entry.title, entry.entry_id)]
|
||||
)
|
||||
|
||||
|
||||
@@ -175,22 +167,16 @@ class HistoryStatsSensor(HistoryStatsSensorBase):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
coordinator: HistoryStatsUpdateCoordinator,
|
||||
sensor_type: str,
|
||||
name: str,
|
||||
unique_id: str | None,
|
||||
source_entity_id: str,
|
||||
) -> None:
|
||||
"""Initialize the HistoryStats sensor."""
|
||||
super().__init__(coordinator, name)
|
||||
self._attr_native_unit_of_measurement = UNITS[sensor_type]
|
||||
self._type = sensor_type
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_device_info = async_device_info_to_link_from_entity(
|
||||
hass,
|
||||
source_entity_id,
|
||||
)
|
||||
self._process_update()
|
||||
if self._type == CONF_TYPE_TIME:
|
||||
self._attr_device_class = SensorDeviceClass.DURATION
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.53", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.52", "babel==2.15.0"]
|
||||
}
|
||||
|
||||
@@ -133,15 +133,15 @@
|
||||
},
|
||||
"toggle": {
|
||||
"name": "Generic toggle",
|
||||
"description": "Generic action to toggle devices on/off under any domain."
|
||||
"description": "Generic service to toggle devices on/off under any domain."
|
||||
},
|
||||
"turn_on": {
|
||||
"name": "Generic turn on",
|
||||
"description": "Generic action to turn devices on under any domain."
|
||||
"description": "Generic service to turn devices on under any domain."
|
||||
},
|
||||
"turn_off": {
|
||||
"name": "Generic turn off",
|
||||
"description": "Generic action to turn devices off under any domain."
|
||||
"description": "Generic service to turn devices off under any domain."
|
||||
},
|
||||
"update_entity": {
|
||||
"name": "Update entity",
|
||||
@@ -205,19 +205,19 @@
|
||||
"message": "Unknown error when validating config for {domain} from integration {p_name} - {error}."
|
||||
},
|
||||
"service_not_found": {
|
||||
"message": "Action {domain}.{service} not found."
|
||||
"message": "Service {domain}.{service} not found."
|
||||
},
|
||||
"service_does_not_support_response": {
|
||||
"message": "An action which does not return responses can't be called with {return_response}."
|
||||
"message": "A service which does not return responses can't be called with {return_response}."
|
||||
},
|
||||
"service_lacks_response_request": {
|
||||
"message": "The action requires responses and must be called with {return_response}."
|
||||
"message": "The service call requires responses and must be called with {return_response}."
|
||||
},
|
||||
"service_reponse_invalid": {
|
||||
"message": "Failed to process the returned action response data, expected a dictionary, but got {response_data_type}."
|
||||
"message": "Failed to process the returned service response data, expected a dictionary, but got {response_data_type}."
|
||||
},
|
||||
"service_should_be_blocking": {
|
||||
"message": "A non blocking action call with argument {non_blocking_argument} can't be used together with argument {return_response}."
|
||||
"message": "A non blocking service call with argument {non_blocking_argument} can't be used together with argument {return_response}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
"unpair": {
|
||||
"name": "Unpair an accessory or bridge",
|
||||
"description": "Forcefully removes all pairings from an accessory to allow re-pairing. Use this action if the accessory is no longer responsive, and you want to avoid deleting and re-adding the entry. Room locations, and accessory preferences will be lost."
|
||||
"description": "Forcefully removes all pairings from an accessory to allow re-pairing. Use this service if the accessory is no longer responsive, and you want to avoid deleting and re-adding the entry. Room locations, and accessory preferences will be lost."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,8 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc]
|
||||
self._char_motion_detected = serv_motion.configure_char(
|
||||
CHAR_MOTION_DETECTED, value=False
|
||||
)
|
||||
self._async_update_motion_state(None, state)
|
||||
if not self.motion_is_event:
|
||||
self._async_update_motion_state(state)
|
||||
|
||||
self._char_doorbell_detected = None
|
||||
self._char_doorbell_detected_switch = None
|
||||
@@ -263,7 +264,9 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc]
|
||||
)
|
||||
serv_speaker = self.add_preload_service(SERV_SPEAKER)
|
||||
serv_speaker.configure_char(CHAR_MUTE, value=0)
|
||||
self._async_update_doorbell_state(None, state)
|
||||
|
||||
if not self.doorbell_is_event:
|
||||
self._async_update_doorbell_state(state)
|
||||
|
||||
@pyhap_callback # type: ignore[misc]
|
||||
@callback
|
||||
@@ -301,25 +304,20 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc]
|
||||
self, event: Event[EventStateChangedData]
|
||||
) -> None:
|
||||
"""Handle state change event listener callback."""
|
||||
if not state_changed_event_is_same_state(event) and (
|
||||
new_state := event.data["new_state"]
|
||||
):
|
||||
self._async_update_motion_state(event.data["old_state"], new_state)
|
||||
if not state_changed_event_is_same_state(event):
|
||||
self._async_update_motion_state(event.data["new_state"])
|
||||
|
||||
@callback
|
||||
def _async_update_motion_state(
|
||||
self, old_state: State | None, new_state: State
|
||||
) -> None:
|
||||
def _async_update_motion_state(self, new_state: State | None) -> None:
|
||||
"""Handle link motion sensor state change to update HomeKit value."""
|
||||
if not new_state:
|
||||
return
|
||||
|
||||
state = new_state.state
|
||||
char = self._char_motion_detected
|
||||
assert char is not None
|
||||
if self.motion_is_event:
|
||||
if (
|
||||
old_state is None
|
||||
or old_state.state == STATE_UNAVAILABLE
|
||||
or state in (STATE_UNKNOWN, STATE_UNAVAILABLE)
|
||||
):
|
||||
if state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"%s: Set linked motion %s sensor to True/False",
|
||||
@@ -350,21 +348,16 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc]
|
||||
if not state_changed_event_is_same_state(event) and (
|
||||
new_state := event.data["new_state"]
|
||||
):
|
||||
self._async_update_doorbell_state(event.data["old_state"], new_state)
|
||||
self._async_update_doorbell_state(new_state)
|
||||
|
||||
@callback
|
||||
def _async_update_doorbell_state(
|
||||
self, old_state: State | None, new_state: State
|
||||
) -> None:
|
||||
def _async_update_doorbell_state(self, new_state: State) -> None:
|
||||
"""Handle link doorbell sensor state change to update HomeKit value."""
|
||||
assert self._char_doorbell_detected
|
||||
assert self._char_doorbell_detected_switch
|
||||
state = new_state.state
|
||||
if state == STATE_ON or (
|
||||
self.doorbell_is_event
|
||||
and old_state is not None
|
||||
and old_state.state != STATE_UNAVAILABLE
|
||||
and state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
|
||||
self.doorbell_is_event and state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
|
||||
):
|
||||
self._char_doorbell_detected.set_value(DOORBELL_SINGLE_PRESS)
|
||||
self._char_doorbell_detected_switch.set_value(DOORBELL_SINGLE_PRESS)
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
},
|
||||
"suspend_integration": {
|
||||
"name": "Suspend integration",
|
||||
"description": "Suspends integration. Suspending logs the integration out from the router, and stops accessing it. Useful e.g. if accessing the router web interface from another source such as a web browser is temporarily required. Invoke the resume_integration action to resume.\n.",
|
||||
"description": "Suspends integration. Suspending logs the integration out from the router, and stops accessing it. Useful e.g. if accessing the router web interface from another source such as a web browser is temporarily required. Invoke the resume_integration service to resume.\n.",
|
||||
"fields": {
|
||||
"url": {
|
||||
"name": "[%key:common::config_flow::data::url%]",
|
||||
|
||||
@@ -9,6 +9,7 @@ from aiopvapi.rooms import Rooms
|
||||
from aiopvapi.scenes import Scenes
|
||||
from aiopvapi.shades import Shades
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_VERSION, CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
@@ -17,7 +18,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DOMAIN, HUB_EXCEPTIONS
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo, PowerviewEntryData
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
from .shade_data import PowerviewShadeData
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@@ -35,7 +36,7 @@ PLATFORMS = [
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: PowerviewConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Hunter Douglas PowerView from a config entry."""
|
||||
|
||||
config = entry.data
|
||||
@@ -99,7 +100,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: PowerviewConfigEntry) ->
|
||||
# populate raw shade data into the coordinator for diagnostics
|
||||
coordinator.data.store_group_data(shade_data)
|
||||
|
||||
entry.runtime_data = PowerviewEntryData(
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = PowerviewEntryData(
|
||||
api=pv_request,
|
||||
room_data=room_data.processed,
|
||||
scene_data=scene_data.processed,
|
||||
@@ -125,6 +126,8 @@ async def async_get_device_info(hub: Hub) -> PowerviewDeviceInfo:
|
||||
)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: PowerviewConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
return unload_ok
|
||||
|
||||
@@ -20,13 +20,15 @@ from homeassistant.components.button import (
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import ShadeEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -73,11 +75,13 @@ BUTTONS_SHADE: Final = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the hunter douglas advanced feature buttons."""
|
||||
pv_entry = entry.runtime_data
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[ButtonEntity] = []
|
||||
for shade in pv_entry.shade_data.values():
|
||||
room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "")
|
||||
|
||||
@@ -25,14 +25,15 @@ from homeassistant.components.cover import (
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .const import STATE_ATTRIBUTE_ROOM_NAME
|
||||
from .const import DOMAIN, STATE_ATTRIBUTE_ROOM_NAME
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import ShadeEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -48,13 +49,12 @@ SCAN_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the hunter douglas shades."""
|
||||
pv_entry = entry.runtime_data
|
||||
coordinator = pv_entry.coordinator
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator: PowerviewShadeUpdateCoordinator = pv_entry.coordinator
|
||||
|
||||
async def _async_initial_refresh() -> None:
|
||||
"""Force position refresh shortly after adding.
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import attr
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_CONFIGURATION_URL, CONF_HOST
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import REDACT_HUB_ADDRESS, REDACT_MAC_ADDRESS, REDACT_SERIAL_NUMBER
|
||||
from .model import PowerviewConfigEntry
|
||||
from .const import DOMAIN, REDACT_HUB_ADDRESS, REDACT_MAC_ADDRESS, REDACT_SERIAL_NUMBER
|
||||
from .model import PowerviewEntryData
|
||||
|
||||
REDACT_CONFIG = {
|
||||
CONF_HOST,
|
||||
@@ -24,9 +26,11 @@ REDACT_CONFIG = {
|
||||
ATTR_CONFIGURATION_URL,
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: PowerviewConfigEntry
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data = _async_get_diagnostics(hass, entry)
|
||||
@@ -43,7 +47,7 @@ async def async_get_config_entry_diagnostics(
|
||||
|
||||
|
||||
async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, entry: PowerviewConfigEntry, device: DeviceEntry
|
||||
hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a device entry."""
|
||||
data = _async_get_diagnostics(hass, entry)
|
||||
@@ -61,10 +65,10 @@ async def async_get_device_diagnostics(
|
||||
@callback
|
||||
def _async_get_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
entry: ConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
pv_entry = entry.runtime_data
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
shade_data = pv_entry.coordinator.data.get_all_raw_data()
|
||||
hub_info = async_redact_data(asdict(pv_entry.device_info), REDACT_CONFIG)
|
||||
return {"hub_info": hub_info, "shade_data": shade_data}
|
||||
|
||||
@@ -9,12 +9,8 @@ from aiopvapi.resources.room import Room
|
||||
from aiopvapi.resources.scene import Scene
|
||||
from aiopvapi.resources.shade import BaseShade
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
|
||||
type PowerviewConfigEntry = ConfigEntry[PowerviewEntryData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerviewEntryData:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
from aiopvapi.helpers.constants import ATTR_NAME, MOTION_VELOCITY
|
||||
@@ -12,13 +13,17 @@ from homeassistant.components.number import (
|
||||
NumberMode,
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import ShadeEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -52,12 +57,12 @@ NUMBERS: Final = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the hunter douglas number entities."""
|
||||
pv_entry = entry.runtime_data
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[PowerViewNumber] = []
|
||||
for shade in pv_entry.shade_data.values():
|
||||
room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "")
|
||||
|
||||
@@ -9,13 +9,14 @@ from aiopvapi.helpers.constants import ATTR_NAME
|
||||
from aiopvapi.resources.scene import Scene as PvScene
|
||||
|
||||
from homeassistant.components.scene import Scene
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import STATE_ATTRIBUTE_ROOM_NAME
|
||||
from .const import DOMAIN, STATE_ATTRIBUTE_ROOM_NAME
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import HDEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,12 +24,12 @@ RESYNC_DELAY = 60
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up powerview scene entries."""
|
||||
pv_entry = entry.runtime_data
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
pvscenes: list[PowerViewScene] = []
|
||||
for scene in pv_entry.scene_data.values():
|
||||
room_name = getattr(pv_entry.room_data.get(scene.room_id), ATTR_NAME, "")
|
||||
|
||||
@@ -4,19 +4,24 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, Final
|
||||
|
||||
from aiopvapi.helpers.constants import ATTR_NAME, FUNCTION_SET_POWER
|
||||
from aiopvapi.resources.shade import BaseShade
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import ShadeEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -52,12 +57,12 @@ DROPDOWNS: Final = [
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the hunter douglas select entities."""
|
||||
pv_entry = entry.runtime_data
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[PowerViewSelect] = []
|
||||
for shade in pv_entry.shade_data.values():
|
||||
if not shade.has_battery_info():
|
||||
|
||||
@@ -13,13 +13,15 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS, EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import PowerviewShadeUpdateCoordinator
|
||||
from .entity import ShadeEntity
|
||||
from .model import PowerviewConfigEntry, PowerviewDeviceInfo
|
||||
from .model import PowerviewDeviceInfo, PowerviewEntryData
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -77,12 +79,12 @@ SENSORS: Final = [
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: PowerviewConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the hunter douglas sensor entities."""
|
||||
pv_entry = entry.runtime_data
|
||||
|
||||
pv_entry: PowerviewEntryData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[PowerViewSensor] = []
|
||||
for shade in pv_entry.shade_data.values():
|
||||
room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import fields
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiopvapi.resources.model import PowerviewData
|
||||
@@ -10,6 +11,8 @@ from aiopvapi.resources.shade import BaseShade, ShadePosition
|
||||
|
||||
from .util import async_map_data_by_id
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
POSITION_FIELDS = [field for field in fields(ShadePosition) if field.name != "velocity"]
|
||||
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/incomfort",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["incomfortclient"],
|
||||
"requirements": ["incomfort-client==0.6.3-1"]
|
||||
"requirements": ["incomfort-client==0.6.3"]
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Remove all input booleans and load new ones from config."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
return
|
||||
await yaml_collection.async_load(
|
||||
[
|
||||
{CONF_ID: id_, **(conf or {})}
|
||||
|
||||
@@ -106,6 +106,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Remove all input buttons and load new ones from config."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
return
|
||||
await yaml_collection.async_load(
|
||||
[
|
||||
{CONF_ID: id_, **(conf or {})}
|
||||
|
||||
@@ -159,6 +159,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Reload yaml entities."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
conf = {DOMAIN: {}}
|
||||
await yaml_collection.async_load(
|
||||
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
|
||||
)
|
||||
|
||||
@@ -137,6 +137,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Reload yaml entities."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
conf = {DOMAIN: {}}
|
||||
await yaml_collection.async_load(
|
||||
[{CONF_ID: id_, **conf} for id_, conf in conf.get(DOMAIN, {}).items()]
|
||||
)
|
||||
|
||||
@@ -167,6 +167,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def reload_service_handler(service_call: ServiceCall) -> None:
|
||||
"""Reload yaml entities."""
|
||||
conf = await component.async_prepare_reload(skip_reset=True)
|
||||
if conf is None:
|
||||
conf = {DOMAIN: {}}
|
||||
await yaml_collection.async_load(
|
||||
[{CONF_ID: id_, **cfg} for id_, cfg in conf.get(DOMAIN, {}).items()]
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user