mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Merge pull request #50417 from home-assistant/rc
This commit is contained in:
commit
5aaead54da
@ -201,11 +201,15 @@ def _monitor_events(hass, name, api, event_codes):
|
|||||||
while True:
|
while True:
|
||||||
api.available_flag.wait()
|
api.available_flag.wait()
|
||||||
try:
|
try:
|
||||||
for code, start in api.event_actions("All", retries=5):
|
for code, payload in api.event_actions("All", retries=5):
|
||||||
event_data = {"camera": name, "event": code, "payload": start}
|
event_data = {"camera": name, "event": code, "payload": payload}
|
||||||
hass.bus.fire("amcrest", event_data)
|
hass.bus.fire("amcrest", event_data)
|
||||||
if code in event_codes:
|
if code in event_codes:
|
||||||
signal = service_signal(SERVICE_EVENT, name, code)
|
signal = service_signal(SERVICE_EVENT, name, code)
|
||||||
|
start = any(
|
||||||
|
str(key).lower() == "action" and str(val).lower() == "start"
|
||||||
|
for key, val in payload.items()
|
||||||
|
)
|
||||||
_LOGGER.debug("Sending signal: '%s': %s", signal, start)
|
_LOGGER.debug("Sending signal: '%s': %s", signal, start)
|
||||||
dispatcher_send(hass, signal, start)
|
dispatcher_send(hass, signal, start)
|
||||||
except AmcrestError as error:
|
except AmcrestError as error:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"adb-shell[async]==0.3.1",
|
"adb-shell[async]==0.3.1",
|
||||||
"androidtv[async]==0.0.58",
|
"androidtv[async]==0.0.59",
|
||||||
"pure-python-adb[async]==0.3.0.dev0"
|
"pure-python-adb[async]==0.3.0.dev0"
|
||||||
],
|
],
|
||||||
"codeowners": ["@JeffLIrion"],
|
"codeowners": ["@JeffLIrion"],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Denon AVR Network Receivers",
|
"name": "Denon AVR Network Receivers",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||||
"requirements": ["denonavr==0.10.6"],
|
"requirements": ["denonavr==0.10.7"],
|
||||||
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -6,10 +6,15 @@ import math
|
|||||||
from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState
|
from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import DEVICE_CLASSES, SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
DEVICE_CLASS_TIMESTAMP,
|
||||||
|
DEVICE_CLASSES,
|
||||||
|
SensorEntity,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util import dt
|
||||||
|
|
||||||
from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry
|
from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry
|
||||||
|
|
||||||
@ -74,6 +79,8 @@ class EsphomeSensor(EsphomeEntity, SensorEntity):
|
|||||||
return None
|
return None
|
||||||
if self._state.missing_state:
|
if self._state.missing_state:
|
||||||
return None
|
return None
|
||||||
|
if self.device_class == DEVICE_CLASS_TIMESTAMP:
|
||||||
|
return dt.utc_from_timestamp(self._state.state).isoformat()
|
||||||
return f"{self._state.state:.{self._static_info.accuracy_decimals}f}"
|
return f"{self._state.state:.{self._static_info.accuracy_decimals}f}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -393,26 +393,29 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||||||
router.subscriptions.clear()
|
router.subscriptions.clear()
|
||||||
|
|
||||||
# Set up device registry
|
# Set up device registry
|
||||||
device_data = {}
|
if router.device_identifiers or router.device_connections:
|
||||||
sw_version = None
|
device_data = {}
|
||||||
if router.data.get(KEY_DEVICE_INFORMATION):
|
sw_version = None
|
||||||
device_info = router.data[KEY_DEVICE_INFORMATION]
|
if router.data.get(KEY_DEVICE_INFORMATION):
|
||||||
sw_version = device_info.get("SoftwareVersion")
|
device_info = router.data[KEY_DEVICE_INFORMATION]
|
||||||
if device_info.get("DeviceName"):
|
sw_version = device_info.get("SoftwareVersion")
|
||||||
device_data["model"] = device_info["DeviceName"]
|
if device_info.get("DeviceName"):
|
||||||
if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
|
device_data["model"] = device_info["DeviceName"]
|
||||||
sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get("SoftwareVersion")
|
if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
|
||||||
if sw_version:
|
sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get(
|
||||||
device_data["sw_version"] = sw_version
|
"SoftwareVersion"
|
||||||
device_registry = await dr.async_get_registry(hass)
|
)
|
||||||
device_registry.async_get_or_create(
|
if sw_version:
|
||||||
config_entry_id=config_entry.entry_id,
|
device_data["sw_version"] = sw_version
|
||||||
connections=router.device_connections,
|
device_registry = await dr.async_get_registry(hass)
|
||||||
identifiers=router.device_identifiers,
|
device_registry.async_get_or_create(
|
||||||
name=router.device_name,
|
config_entry_id=config_entry.entry_id,
|
||||||
manufacturer="Huawei",
|
connections=router.device_connections,
|
||||||
**device_data,
|
identifiers=router.device_identifiers,
|
||||||
)
|
name=router.device_name,
|
||||||
|
manufacturer="Huawei",
|
||||||
|
**device_data,
|
||||||
|
)
|
||||||
|
|
||||||
# Forward config entry setup to platforms
|
# Forward config entry setup to platforms
|
||||||
hass.config_entries.async_setup_platforms(config_entry, CONFIG_ENTRY_PLATFORMS)
|
hass.config_entries.async_setup_platforms(config_entry, CONFIG_ENTRY_PLATFORMS)
|
||||||
|
@ -5,7 +5,6 @@ import threading
|
|||||||
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
|
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
|
||||||
from pymodbus.constants import Defaults
|
from pymodbus.constants import Defaults
|
||||||
from pymodbus.exceptions import ModbusException
|
from pymodbus.exceptions import ModbusException
|
||||||
from pymodbus.pdu import ExceptionResponse, IllegalFunctionRequest
|
|
||||||
from pymodbus.transaction import ModbusRtuFramer
|
from pymodbus.transaction import ModbusRtuFramer
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -237,8 +236,8 @@ class ModbusHub:
|
|||||||
result = self._client.read_coils(address, count, **kwargs)
|
result = self._client.read_coils(address, count, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
self._log_error(exception_error)
|
||||||
return None
|
result = exception_error
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
if not hasattr(result, "bits"):
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return None
|
return None
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -251,9 +250,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.read_discrete_inputs(address, count, **kwargs)
|
result = self._client.read_discrete_inputs(address, count, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return None
|
if not hasattr(result, "bits"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return None
|
return None
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -266,9 +264,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.read_input_registers(address, count, **kwargs)
|
result = self._client.read_input_registers(address, count, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return None
|
if not hasattr(result, "registers"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return None
|
return None
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -281,9 +278,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.read_holding_registers(address, count, **kwargs)
|
result = self._client.read_holding_registers(address, count, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return None
|
if not hasattr(result, "registers"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return None
|
return None
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -296,9 +292,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.write_coil(address, value, **kwargs)
|
result = self._client.write_coil(address, value, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return False
|
if not hasattr(result, "value"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return False
|
return False
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -311,9 +306,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.write_coils(address, values, **kwargs)
|
result = self._client.write_coils(address, values, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return False
|
if not hasattr(result, "count"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return False
|
return False
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -326,9 +320,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.write_register(address, value, **kwargs)
|
result = self._client.write_register(address, value, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return False
|
if not hasattr(result, "value"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return False
|
return False
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
@ -341,9 +334,8 @@ class ModbusHub:
|
|||||||
try:
|
try:
|
||||||
result = self._client.write_registers(address, values, **kwargs)
|
result = self._client.write_registers(address, values, **kwargs)
|
||||||
except ModbusException as exception_error:
|
except ModbusException as exception_error:
|
||||||
self._log_error(exception_error)
|
result = exception_error
|
||||||
return False
|
if not hasattr(result, "count"):
|
||||||
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
|
|
||||||
self._log_error(result)
|
self._log_error(result)
|
||||||
return False
|
return False
|
||||||
self._in_error = False
|
self._in_error = False
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "OVO Energy",
|
"name": "OVO Energy",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ovo_energy",
|
"documentation": "https://www.home-assistant.io/integrations/ovo_energy",
|
||||||
"requirements": ["ovoenergy==1.1.11"],
|
"requirements": ["ovoenergy==1.1.12"],
|
||||||
"codeowners": ["@timmo001"],
|
"codeowners": ["@timmo001"],
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "philips_js",
|
"domain": "philips_js",
|
||||||
"name": "Philips TV",
|
"name": "Philips TV",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/philips_js",
|
"documentation": "https://www.home-assistant.io/integrations/philips_js",
|
||||||
"requirements": ["ha-philipsjs==2.7.0"],
|
"requirements": ["ha-philipsjs==2.7.3"],
|
||||||
"codeowners": ["@elupus"],
|
"codeowners": ["@elupus"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
|
@ -418,9 +418,8 @@ class RachioZone(RachioSwitch):
|
|||||||
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
|
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self._controller.rachio.zone.start(
|
# The API limit is 3 hours, and requires an int be passed
|
||||||
self.zone_id, manual_run_time.total_seconds()
|
self._controller.rachio.zone.start(self.zone_id, manual_run_time.seconds)
|
||||||
)
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Watering %s on %s for %s",
|
"Watering %s on %s for %s",
|
||||||
self.name,
|
self.name,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Sonos",
|
"name": "Sonos",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||||
"requirements": ["pysonos==0.0.44"],
|
"requirements": ["pysonos==0.0.45"],
|
||||||
"after_dependencies": ["plex"],
|
"after_dependencies": ["plex"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -498,7 +498,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
if new_status == "TRANSITIONING":
|
if new_status == "TRANSITIONING":
|
||||||
return
|
return
|
||||||
|
|
||||||
self._play_mode = event.current_play_mode if event else self.soco.play_mode
|
self._play_mode = (
|
||||||
|
variables["current_play_mode"] if variables else self.soco.play_mode
|
||||||
|
)
|
||||||
self._uri = None
|
self._uri = None
|
||||||
self._media_duration = None
|
self._media_duration = None
|
||||||
self._media_image_url = None
|
self._media_image_url = None
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
from asyncio import gather
|
from asyncio import gather
|
||||||
import contextlib
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
@ -223,7 +224,11 @@ class SonosSpeaker:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._poll_timer = self.hass.helpers.event.async_track_time_interval(
|
self._poll_timer = self.hass.helpers.event.async_track_time_interval(
|
||||||
async_dispatcher_send(self.hass, f"{SONOS_ENTITY_UPDATE}-{self.soco.uid}"),
|
partial(
|
||||||
|
async_dispatcher_send,
|
||||||
|
self.hass,
|
||||||
|
f"{SONOS_ENTITY_UPDATE}-{self.soco.uid}",
|
||||||
|
),
|
||||||
SCAN_INTERVAL,
|
SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ async def async_setup_platform(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the system monitor sensors."""
|
"""Set up the system monitor sensors."""
|
||||||
entities = []
|
entities = []
|
||||||
sensor_registry: dict[str, SensorData] = {}
|
sensor_registry: dict[tuple[str, str], SensorData] = {}
|
||||||
|
|
||||||
for resource in config[CONF_RESOURCES]:
|
for resource in config[CONF_RESOURCES]:
|
||||||
type_ = resource[CONF_TYPE]
|
type_ = resource[CONF_TYPE]
|
||||||
@ -225,7 +225,9 @@ async def async_setup_platform(
|
|||||||
_LOGGER.warning("Cannot read CPU / processor temperature information")
|
_LOGGER.warning("Cannot read CPU / processor temperature information")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sensor_registry[type_] = SensorData(argument, None, None, None, None)
|
sensor_registry[(type_, argument)] = SensorData(
|
||||||
|
argument, None, None, None, None
|
||||||
|
)
|
||||||
entities.append(SystemMonitorSensor(sensor_registry, type_, argument))
|
entities.append(SystemMonitorSensor(sensor_registry, type_, argument))
|
||||||
|
|
||||||
scan_interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
scan_interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
@ -236,7 +238,7 @@ async def async_setup_platform(
|
|||||||
|
|
||||||
async def async_setup_sensor_registry_updates(
|
async def async_setup_sensor_registry_updates(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
sensor_registry: dict[str, SensorData],
|
sensor_registry: dict[tuple[str, str], SensorData],
|
||||||
scan_interval: datetime.timedelta,
|
scan_interval: datetime.timedelta,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update the registry and create polling."""
|
"""Update the registry and create polling."""
|
||||||
@ -245,11 +247,11 @@ async def async_setup_sensor_registry_updates(
|
|||||||
|
|
||||||
def _update_sensors() -> None:
|
def _update_sensors() -> None:
|
||||||
"""Update sensors and store the result in the registry."""
|
"""Update sensors and store the result in the registry."""
|
||||||
for type_, data in sensor_registry.items():
|
for (type_, argument), data in sensor_registry.items():
|
||||||
try:
|
try:
|
||||||
state, value, update_time = _update(type_, data)
|
state, value, update_time = _update(type_, data)
|
||||||
except Exception as ex: # pylint: disable=broad-except
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error updating sensor: %s", type_)
|
_LOGGER.exception("Error updating sensor: %s (%s)", type_, argument)
|
||||||
data.last_exception = ex
|
data.last_exception = ex
|
||||||
else:
|
else:
|
||||||
data.state = state
|
data.state = state
|
||||||
@ -295,7 +297,7 @@ class SystemMonitorSensor(SensorEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
sensor_registry: dict[str, SensorData],
|
sensor_registry: dict[tuple[str, str], SensorData],
|
||||||
sensor_type: str,
|
sensor_type: str,
|
||||||
argument: str = "",
|
argument: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -304,6 +306,7 @@ class SystemMonitorSensor(SensorEntity):
|
|||||||
self._name: str = f"{self.sensor_type[SENSOR_TYPE_NAME]} {argument}".rstrip()
|
self._name: str = f"{self.sensor_type[SENSOR_TYPE_NAME]} {argument}".rstrip()
|
||||||
self._unique_id: str = slugify(f"{sensor_type}_{argument}")
|
self._unique_id: str = slugify(f"{sensor_type}_{argument}")
|
||||||
self._sensor_registry = sensor_registry
|
self._sensor_registry = sensor_registry
|
||||||
|
self._argument: str = argument
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -353,7 +356,7 @@ class SystemMonitorSensor(SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def data(self) -> SensorData:
|
def data(self) -> SensorData:
|
||||||
"""Return registry entry for the data."""
|
"""Return registry entry for the data."""
|
||||||
return self._sensor_registry[self._type]
|
return self._sensor_registry[(self._type, self._argument)]
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""When entity is added to hass."""
|
"""When entity is added to hass."""
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Tasmota",
|
"name": "Tasmota",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
||||||
"requirements": ["hatasmota==0.2.11"],
|
"requirements": ["hatasmota==0.2.12"],
|
||||||
"dependencies": ["mqtt"],
|
"dependencies": ["mqtt"],
|
||||||
"mqtt": ["tasmota/discovery/#"],
|
"mqtt": ["tasmota/discovery/#"],
|
||||||
"codeowners": ["@emontnemery"],
|
"codeowners": ["@emontnemery"],
|
||||||
|
@ -140,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
config = config_entry.data
|
config = config_entry.data
|
||||||
# Because users can have multiple accounts, we always create a new session so they have separate cookies
|
# Because users can have multiple accounts, we always create a new session so they have separate cookies
|
||||||
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE})
|
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
|
||||||
email = config_entry.title
|
email = config_entry.title
|
||||||
if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
|
if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
|
||||||
scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]
|
scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]
|
||||||
|
@ -150,7 +150,7 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE})
|
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
controller = TeslaAPI(
|
controller = TeslaAPI(
|
||||||
|
@ -111,7 +111,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigType):
|
|||||||
|
|
||||||
async def async_unload_entry(hass, entry):
|
async def async_unload_entry(hass, entry):
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
platforms = [platform for platform in PLATFORMS if platform in hass.data[DOMAIN]]
|
platforms = [platform for platform in PLATFORMS if hass.data[DOMAIN].get(platform)]
|
||||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms)
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, platforms)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN].clear()
|
hass.data[DOMAIN].clear()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 2021
|
MAJOR_VERSION = 2021
|
||||||
MINOR_VERSION = 5
|
MINOR_VERSION = 5
|
||||||
PATCH_VERSION = "1"
|
PATCH_VERSION = "2"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 8, 0)
|
REQUIRED_PYTHON_VER = (3, 8, 0)
|
||||||
|
@ -254,7 +254,7 @@ ambiclimate==0.2.1
|
|||||||
amcrest==1.7.2
|
amcrest==1.7.2
|
||||||
|
|
||||||
# homeassistant.components.androidtv
|
# homeassistant.components.androidtv
|
||||||
androidtv[async]==0.0.58
|
androidtv[async]==0.0.59
|
||||||
|
|
||||||
# homeassistant.components.anel_pwrctrl
|
# homeassistant.components.anel_pwrctrl
|
||||||
anel_pwrctrl-homeassistant==0.0.1.dev2
|
anel_pwrctrl-homeassistant==0.0.1.dev2
|
||||||
@ -479,7 +479,7 @@ defusedxml==0.6.0
|
|||||||
deluge-client==1.7.1
|
deluge-client==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.10.6
|
denonavr==0.10.7
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.17.3
|
devolo-home-control-api==0.17.3
|
||||||
@ -720,7 +720,7 @@ guppy3==3.1.0
|
|||||||
ha-ffmpeg==3.0.2
|
ha-ffmpeg==3.0.2
|
||||||
|
|
||||||
# homeassistant.components.philips_js
|
# homeassistant.components.philips_js
|
||||||
ha-philipsjs==2.7.0
|
ha-philipsjs==2.7.3
|
||||||
|
|
||||||
# homeassistant.components.habitica
|
# homeassistant.components.habitica
|
||||||
habitipy==0.2.0
|
habitipy==0.2.0
|
||||||
@ -735,7 +735,7 @@ hass-nabucasa==0.43.0
|
|||||||
hass_splunk==0.1.1
|
hass_splunk==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.2.11
|
hatasmota==0.2.12
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.10.2
|
hdate==0.10.2
|
||||||
@ -1088,7 +1088,7 @@ oru==0.1.11
|
|||||||
orvibo==1.1.1
|
orvibo==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.ovo_energy
|
# homeassistant.components.ovo_energy
|
||||||
ovoenergy==1.1.11
|
ovoenergy==1.1.12
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
# homeassistant.components.shiftr
|
# homeassistant.components.shiftr
|
||||||
@ -1741,7 +1741,7 @@ pysnmp==4.4.12
|
|||||||
pysoma==0.0.10
|
pysoma==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
pysonos==0.0.44
|
pysonos==0.0.45
|
||||||
|
|
||||||
# homeassistant.components.spc
|
# homeassistant.components.spc
|
||||||
pyspcwebgw==0.4.0
|
pyspcwebgw==0.4.0
|
||||||
|
@ -167,7 +167,7 @@ airly==1.1.0
|
|||||||
ambiclimate==0.2.1
|
ambiclimate==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.androidtv
|
# homeassistant.components.androidtv
|
||||||
androidtv[async]==0.0.58
|
androidtv[async]==0.0.59
|
||||||
|
|
||||||
# homeassistant.components.apns
|
# homeassistant.components.apns
|
||||||
apns2==0.3.0
|
apns2==0.3.0
|
||||||
@ -264,7 +264,7 @@ debugpy==1.2.1
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.10.6
|
denonavr==0.10.7
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.17.3
|
devolo-home-control-api==0.17.3
|
||||||
@ -393,7 +393,7 @@ guppy3==3.1.0
|
|||||||
ha-ffmpeg==3.0.2
|
ha-ffmpeg==3.0.2
|
||||||
|
|
||||||
# homeassistant.components.philips_js
|
# homeassistant.components.philips_js
|
||||||
ha-philipsjs==2.7.0
|
ha-philipsjs==2.7.3
|
||||||
|
|
||||||
# homeassistant.components.habitica
|
# homeassistant.components.habitica
|
||||||
habitipy==0.2.0
|
habitipy==0.2.0
|
||||||
@ -405,7 +405,7 @@ hangups==0.4.11
|
|||||||
hass-nabucasa==0.43.0
|
hass-nabucasa==0.43.0
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.2.11
|
hatasmota==0.2.12
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.10.2
|
hdate==0.10.2
|
||||||
@ -576,7 +576,7 @@ onvif-zeep-async==1.0.0
|
|||||||
openerz-api==0.1.0
|
openerz-api==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.ovo_energy
|
# homeassistant.components.ovo_energy
|
||||||
ovoenergy==1.1.11
|
ovoenergy==1.1.12
|
||||||
|
|
||||||
# homeassistant.components.mqtt
|
# homeassistant.components.mqtt
|
||||||
# homeassistant.components.shiftr
|
# homeassistant.components.shiftr
|
||||||
@ -959,7 +959,7 @@ pysmartthings==0.7.6
|
|||||||
pysoma==0.0.10
|
pysoma==0.0.10
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
pysonos==0.0.44
|
pysonos==0.0.45
|
||||||
|
|
||||||
# homeassistant.components.spc
|
# homeassistant.components.spc
|
||||||
pyspcwebgw==0.4.0
|
pyspcwebgw==0.4.0
|
||||||
|
@ -43,6 +43,15 @@ DEFAULT_SENSOR_CONFIG = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BAD_INDEXED_SENSOR_CONFIG_3 = {
|
||||||
|
"sn": {
|
||||||
|
"Time": "2020-09-25T12:47:15",
|
||||||
|
"ENERGY": {
|
||||||
|
"ApparentPower": [7.84, 1.23, 2.34],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
INDEXED_SENSOR_CONFIG = {
|
INDEXED_SENSOR_CONFIG = {
|
||||||
"sn": {
|
"sn": {
|
||||||
"Time": "2020-09-25T12:47:15",
|
"Time": "2020-09-25T12:47:15",
|
||||||
@ -224,6 +233,117 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
|
|||||||
assert state.state == "7.8"
|
assert state.state == "7.8"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bad_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
|
||||||
|
"""Test state update via MQTT where sensor is not matching configuration."""
|
||||||
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
sensor_config = copy.deepcopy(BAD_INDEXED_SENSOR_CONFIG_3)
|
||||||
|
mac = config["mac"]
|
||||||
|
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
f"{DEFAULT_PREFIX}/{mac}/config",
|
||||||
|
json.dumps(config),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
f"{DEFAULT_PREFIX}/{mac}/sensors",
|
||||||
|
json.dumps(sensor_config),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
# Test periodic state update
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass, "tasmota_49A3BC/tele/SENSOR", '{"ENERGY":{"ApparentPower":[1.2,3.4,5.6]}}'
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "1.2"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == "3.4"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == "5.6"
|
||||||
|
|
||||||
|
# Test periodic state update with too few values
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass, "tasmota_49A3BC/tele/SENSOR", '{"ENERGY":{"ApparentPower":[7.8,9.0]}}'
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "7.8"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == "9.0"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass, "tasmota_49A3BC/tele/SENSOR", '{"ENERGY":{"ApparentPower":2.3}}'
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "2.3"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
# Test polled state update
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"tasmota_49A3BC/stat/STATUS10",
|
||||||
|
'{"StatusSNS":{"ENERGY":{"ApparentPower":[1.2,3.4,5.6]}}}',
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "1.2"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == "3.4"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == "5.6"
|
||||||
|
|
||||||
|
# Test polled state update with too few values
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"tasmota_49A3BC/stat/STATUS10",
|
||||||
|
'{"StatusSNS":{"ENERGY":{"ApparentPower":[7.8,9.0]}}}',
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "7.8"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == "9.0"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
async_fire_mqtt_message(
|
||||||
|
hass,
|
||||||
|
"tasmota_49A3BC/stat/STATUS10",
|
||||||
|
'{"StatusSNS":{"ENERGY":{"ApparentPower":2.3}}}',
|
||||||
|
)
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_0")
|
||||||
|
assert state.state == "2.3"
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_1")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
state = hass.states.get("sensor.tasmota_energy_apparentpower_2")
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("status_sensor_disabled", [False])
|
@pytest.mark.parametrize("status_sensor_disabled", [False])
|
||||||
async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
|
async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
|
||||||
"""Test state update via MQTT."""
|
"""Test state update via MQTT."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user