Merge pull request #50417 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-05-10 13:58:00 -07:00 committed by GitHub
commit 5aaead54da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 216 additions and 81 deletions

View File

@ -201,11 +201,15 @@ def _monitor_events(hass, name, api, event_codes):
while True:
api.available_flag.wait()
try:
for code, start in api.event_actions("All", retries=5):
event_data = {"camera": name, "event": code, "payload": start}
for code, payload in api.event_actions("All", retries=5):
event_data = {"camera": name, "event": code, "payload": payload}
hass.bus.fire("amcrest", event_data)
if code in event_codes:
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)
dispatcher_send(hass, signal, start)
except AmcrestError as error:

View File

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [
"adb-shell[async]==0.3.1",
"androidtv[async]==0.0.58",
"androidtv[async]==0.0.59",
"pure-python-adb[async]==0.3.0.dev0"
],
"codeowners": ["@JeffLIrion"],

View File

@ -3,7 +3,7 @@
"name": "Denon AVR Network Receivers",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/denonavr",
"requirements": ["denonavr==0.10.6"],
"requirements": ["denonavr==0.10.7"],
"codeowners": ["@scarface-4711", "@starkillerOG"],
"ssdp": [
{

View File

@ -6,10 +6,15 @@ import math
from aioesphomeapi import SensorInfo, SensorState, TextSensorInfo, TextSensorState
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.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt
from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry
@ -74,6 +79,8 @@ class EsphomeSensor(EsphomeEntity, SensorEntity):
return None
if self._state.missing_state:
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}"
@property

View File

@ -393,26 +393,29 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
router.subscriptions.clear()
# Set up device registry
device_data = {}
sw_version = None
if router.data.get(KEY_DEVICE_INFORMATION):
device_info = router.data[KEY_DEVICE_INFORMATION]
sw_version = device_info.get("SoftwareVersion")
if device_info.get("DeviceName"):
device_data["model"] = device_info["DeviceName"]
if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get("SoftwareVersion")
if sw_version:
device_data["sw_version"] = sw_version
device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections=router.device_connections,
identifiers=router.device_identifiers,
name=router.device_name,
manufacturer="Huawei",
**device_data,
)
if router.device_identifiers or router.device_connections:
device_data = {}
sw_version = None
if router.data.get(KEY_DEVICE_INFORMATION):
device_info = router.data[KEY_DEVICE_INFORMATION]
sw_version = device_info.get("SoftwareVersion")
if device_info.get("DeviceName"):
device_data["model"] = device_info["DeviceName"]
if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get(
"SoftwareVersion"
)
if sw_version:
device_data["sw_version"] = sw_version
device_registry = await dr.async_get_registry(hass)
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections=router.device_connections,
identifiers=router.device_identifiers,
name=router.device_name,
manufacturer="Huawei",
**device_data,
)
# Forward config entry setup to platforms
hass.config_entries.async_setup_platforms(config_entry, CONFIG_ENTRY_PLATFORMS)

View File

@ -5,7 +5,6 @@ import threading
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient
from pymodbus.constants import Defaults
from pymodbus.exceptions import ModbusException
from pymodbus.pdu import ExceptionResponse, IllegalFunctionRequest
from pymodbus.transaction import ModbusRtuFramer
from homeassistant.const import (
@ -237,8 +236,8 @@ class ModbusHub:
result = self._client.read_coils(address, count, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return None
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "bits"):
self._log_error(result)
return None
self._in_error = False
@ -251,9 +250,8 @@ class ModbusHub:
try:
result = self._client.read_discrete_inputs(address, count, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return None
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "bits"):
self._log_error(result)
return None
self._in_error = False
@ -266,9 +264,8 @@ class ModbusHub:
try:
result = self._client.read_input_registers(address, count, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return None
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "registers"):
self._log_error(result)
return None
self._in_error = False
@ -281,9 +278,8 @@ class ModbusHub:
try:
result = self._client.read_holding_registers(address, count, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return None
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "registers"):
self._log_error(result)
return None
self._in_error = False
@ -296,9 +292,8 @@ class ModbusHub:
try:
result = self._client.write_coil(address, value, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return False
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "value"):
self._log_error(result)
return False
self._in_error = False
@ -311,9 +306,8 @@ class ModbusHub:
try:
result = self._client.write_coils(address, values, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return False
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "count"):
self._log_error(result)
return False
self._in_error = False
@ -326,9 +320,8 @@ class ModbusHub:
try:
result = self._client.write_register(address, value, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return False
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "value"):
self._log_error(result)
return False
self._in_error = False
@ -341,9 +334,8 @@ class ModbusHub:
try:
result = self._client.write_registers(address, values, **kwargs)
except ModbusException as exception_error:
self._log_error(exception_error)
return False
if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
result = exception_error
if not hasattr(result, "count"):
self._log_error(result)
return False
self._in_error = False

View File

@ -3,7 +3,7 @@
"name": "OVO Energy",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ovo_energy",
"requirements": ["ovoenergy==1.1.11"],
"requirements": ["ovoenergy==1.1.12"],
"codeowners": ["@timmo001"],
"iot_class": "cloud_polling"
}

View File

@ -2,7 +2,7 @@
"domain": "philips_js",
"name": "Philips TV",
"documentation": "https://www.home-assistant.io/integrations/philips_js",
"requirements": ["ha-philipsjs==2.7.0"],
"requirements": ["ha-philipsjs==2.7.3"],
"codeowners": ["@elupus"],
"config_flow": true,
"iot_class": "local_polling"

View File

@ -418,9 +418,8 @@ class RachioZone(RachioSwitch):
CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS
)
)
self._controller.rachio.zone.start(
self.zone_id, manual_run_time.total_seconds()
)
# The API limit is 3 hours, and requires an int be passed
self._controller.rachio.zone.start(self.zone_id, manual_run_time.seconds)
_LOGGER.debug(
"Watering %s on %s for %s",
self.name,

View File

@ -3,7 +3,7 @@
"name": "Sonos",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/sonos",
"requirements": ["pysonos==0.0.44"],
"requirements": ["pysonos==0.0.45"],
"after_dependencies": ["plex"],
"ssdp": [
{

View File

@ -498,7 +498,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
if new_status == "TRANSITIONING":
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._media_duration = None
self._media_image_url = None

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from asyncio import gather
import contextlib
import datetime
from functools import partial
import logging
from typing import Any, Callable
@ -223,7 +224,11 @@ class SonosSpeaker:
return
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,
)

View File

@ -203,7 +203,7 @@ async def async_setup_platform(
) -> None:
"""Set up the system monitor sensors."""
entities = []
sensor_registry: dict[str, SensorData] = {}
sensor_registry: dict[tuple[str, str], SensorData] = {}
for resource in config[CONF_RESOURCES]:
type_ = resource[CONF_TYPE]
@ -225,7 +225,9 @@ async def async_setup_platform(
_LOGGER.warning("Cannot read CPU / processor temperature information")
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))
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(
hass: HomeAssistant,
sensor_registry: dict[str, SensorData],
sensor_registry: dict[tuple[str, str], SensorData],
scan_interval: datetime.timedelta,
) -> None:
"""Update the registry and create polling."""
@ -245,11 +247,11 @@ async def async_setup_sensor_registry_updates(
def _update_sensors() -> None:
"""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:
state, value, update_time = _update(type_, data)
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
else:
data.state = state
@ -295,7 +297,7 @@ class SystemMonitorSensor(SensorEntity):
def __init__(
self,
sensor_registry: dict[str, SensorData],
sensor_registry: dict[tuple[str, str], SensorData],
sensor_type: str,
argument: str = "",
) -> None:
@ -304,6 +306,7 @@ class SystemMonitorSensor(SensorEntity):
self._name: str = f"{self.sensor_type[SENSOR_TYPE_NAME]} {argument}".rstrip()
self._unique_id: str = slugify(f"{sensor_type}_{argument}")
self._sensor_registry = sensor_registry
self._argument: str = argument
@property
def name(self) -> str:
@ -353,7 +356,7 @@ class SystemMonitorSensor(SensorEntity):
@property
def data(self) -> SensorData:
"""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:
"""When entity is added to hass."""

View File

@ -3,7 +3,7 @@
"name": "Tasmota",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/tasmota",
"requirements": ["hatasmota==0.2.11"],
"requirements": ["hatasmota==0.2.12"],
"dependencies": ["mqtt"],
"mqtt": ["tasmota/discovery/#"],
"codeowners": ["@emontnemery"],

View File

@ -140,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
hass.data.setdefault(DOMAIN, {})
config = config_entry.data
# 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
if email in hass.data[DOMAIN] and CONF_SCAN_INTERVAL in hass.data[DOMAIN][email]:
scan_interval = hass.data[DOMAIN][email][CONF_SCAN_INTERVAL]

View File

@ -150,7 +150,7 @@ async def validate_input(hass: core.HomeAssistant, data):
"""
config = {}
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE})
async_client = httpx.AsyncClient(headers={USER_AGENT: SERVER_SOFTWARE}, timeout=60)
try:
controller = TeslaAPI(

View File

@ -111,7 +111,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigType):
async def async_unload_entry(hass, 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)
if unload_ok:
hass.data[DOMAIN].clear()

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 2021
MINOR_VERSION = 5
PATCH_VERSION = "1"
PATCH_VERSION = "2"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 8, 0)

View File

@ -254,7 +254,7 @@ ambiclimate==0.2.1
amcrest==1.7.2
# homeassistant.components.androidtv
androidtv[async]==0.0.58
androidtv[async]==0.0.59
# homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2
@ -479,7 +479,7 @@ defusedxml==0.6.0
deluge-client==1.7.1
# homeassistant.components.denonavr
denonavr==0.10.6
denonavr==0.10.7
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.17.3
@ -720,7 +720,7 @@ guppy3==3.1.0
ha-ffmpeg==3.0.2
# homeassistant.components.philips_js
ha-philipsjs==2.7.0
ha-philipsjs==2.7.3
# homeassistant.components.habitica
habitipy==0.2.0
@ -735,7 +735,7 @@ hass-nabucasa==0.43.0
hass_splunk==0.1.1
# homeassistant.components.tasmota
hatasmota==0.2.11
hatasmota==0.2.12
# homeassistant.components.jewish_calendar
hdate==0.10.2
@ -1088,7 +1088,7 @@ oru==0.1.11
orvibo==1.1.1
# homeassistant.components.ovo_energy
ovoenergy==1.1.11
ovoenergy==1.1.12
# homeassistant.components.mqtt
# homeassistant.components.shiftr
@ -1741,7 +1741,7 @@ pysnmp==4.4.12
pysoma==0.0.10
# homeassistant.components.sonos
pysonos==0.0.44
pysonos==0.0.45
# homeassistant.components.spc
pyspcwebgw==0.4.0

View File

@ -167,7 +167,7 @@ airly==1.1.0
ambiclimate==0.2.1
# homeassistant.components.androidtv
androidtv[async]==0.0.58
androidtv[async]==0.0.59
# homeassistant.components.apns
apns2==0.3.0
@ -264,7 +264,7 @@ debugpy==1.2.1
defusedxml==0.6.0
# homeassistant.components.denonavr
denonavr==0.10.6
denonavr==0.10.7
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.17.3
@ -393,7 +393,7 @@ guppy3==3.1.0
ha-ffmpeg==3.0.2
# homeassistant.components.philips_js
ha-philipsjs==2.7.0
ha-philipsjs==2.7.3
# homeassistant.components.habitica
habitipy==0.2.0
@ -405,7 +405,7 @@ hangups==0.4.11
hass-nabucasa==0.43.0
# homeassistant.components.tasmota
hatasmota==0.2.11
hatasmota==0.2.12
# homeassistant.components.jewish_calendar
hdate==0.10.2
@ -576,7 +576,7 @@ onvif-zeep-async==1.0.0
openerz-api==0.1.0
# homeassistant.components.ovo_energy
ovoenergy==1.1.11
ovoenergy==1.1.12
# homeassistant.components.mqtt
# homeassistant.components.shiftr
@ -959,7 +959,7 @@ pysmartthings==0.7.6
pysoma==0.0.10
# homeassistant.components.sonos
pysonos==0.0.44
pysonos==0.0.45
# homeassistant.components.spc
pyspcwebgw==0.4.0

View File

@ -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 = {
"sn": {
"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"
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])
async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota):
"""Test state update via MQTT."""