Merge pull request #42874 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2020-11-05 16:33:21 +01:00 committed by GitHub
commit 22f16759e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 153 additions and 89 deletions

View File

@ -2,7 +2,7 @@
"domain": "bmw_connected_drive",
"name": "BMW Connected Drive",
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"requirements": ["bimmer_connected==0.7.11"],
"requirements": ["bimmer_connected==0.7.12"],
"dependencies": [],
"codeowners": ["@gerard33", "@rikroe"]
}

View File

@ -16,7 +16,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
name = f"{cube.room_by_id(device.room_id).name} {device.name}"
# Only add Window Shutters
if device.is_windowshutter():
if cube.is_windowshutter(device):
devices.append(MaxCubeShutter(handler, name, device.rf_address))
if devices:

View File

@ -70,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
for device in cube.devices:
name = f"{cube.room_by_id(device.room_id).name} {device.name}"
if device.is_thermostat() or device.is_wallthermostat():
if cube.is_thermostat(device) or cube.is_wallthermostat(device):
devices.append(MaxCubeClimate(handler, name, device.rf_address))
if devices:
@ -180,11 +180,11 @@ class MaxCubeClimate(ClimateEntity):
device = cube.device_by_rf(self._rf_address)
valve = 0
if device.is_thermostat():
if cube.is_thermostat(device):
valve = device.valve_position
elif device.is_wallthermostat():
elif cube.is_wallthermostat(device):
for device in cube.devices_by_room(cube.room_by_id(device.room_id)):
if device.is_thermostat() and device.valve_position > 0:
if cube.is_thermostat(device) and device.valve_position > 0:
valve = device.valve_position
break
else:
@ -287,7 +287,7 @@ class MaxCubeClimate(ClimateEntity):
cube = self._cubehandle.cube
device = cube.device_by_rf(self._rf_address)
if not device.is_thermostat():
if not cube.is_thermostat(device):
return {}
return {ATTR_VALVE_POSITION: device.valve_position}

View File

@ -45,10 +45,10 @@ class NetatmoBase(Entity):
data_class["name"],
signal_name,
self.async_update_callback,
LAT_NE=data_class["LAT_NE"],
LON_NE=data_class["LON_NE"],
LAT_SW=data_class["LAT_SW"],
LON_SW=data_class["LON_SW"],
lat_ne=data_class["lat_ne"],
lon_ne=data_class["lon_ne"],
lat_sw=data_class["lat_sw"],
lon_sw=data_class["lon_sw"],
)
else:

View File

@ -202,10 +202,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
PUBLICDATA_DATA_CLASS_NAME,
signal_name,
None,
LAT_NE=area.lat_ne,
LON_NE=area.lon_ne,
LAT_SW=area.lat_sw,
LON_SW=area.lon_sw,
lat_ne=area.lat_ne,
lon_ne=area.lon_ne,
lat_sw=area.lat_sw,
lon_sw=area.lon_sw,
)
for sensor_type in SUPPORTED_PUBLIC_SENSOR_TYPES:
new_entities.append(
@ -473,10 +473,10 @@ class NetatmoPublicSensor(NetatmoBase):
self._data_classes.append(
{
"name": PUBLICDATA_DATA_CLASS_NAME,
"LAT_NE": area.lat_ne,
"LON_NE": area.lon_ne,
"LAT_SW": area.lat_sw,
"LON_SW": area.lon_sw,
"lat_ne": area.lat_ne,
"lon_ne": area.lon_ne,
"lat_sw": area.lat_sw,
"lon_sw": area.lon_sw,
"area_name": area.area_name,
SIGNAL_NAME: self._signal_name,
}
@ -563,10 +563,10 @@ class NetatmoPublicSensor(NetatmoBase):
self._data_classes = [
{
"name": PUBLICDATA_DATA_CLASS_NAME,
"LAT_NE": area.lat_ne,
"LON_NE": area.lon_ne,
"LAT_SW": area.lat_sw,
"LON_SW": area.lon_sw,
"lat_ne": area.lat_ne,
"lon_ne": area.lon_ne,
"lat_sw": area.lat_sw,
"lon_sw": area.lon_sw,
"area_name": area.area_name,
SIGNAL_NAME: self._signal_name,
}
@ -577,10 +577,10 @@ class NetatmoPublicSensor(NetatmoBase):
PUBLICDATA_DATA_CLASS_NAME,
self._signal_name,
self.async_update_callback,
LAT_NE=area.lat_ne,
LON_NE=area.lon_ne,
LAT_SW=area.lat_sw,
LON_SW=area.lon_sw,
lat_ne=area.lat_ne,
lon_ne=area.lon_ne,
lat_sw=area.lat_sw,
lon_sw=area.lon_sw,
)
@callback

View File

@ -3,6 +3,7 @@ import asyncio
from uuid import UUID
from simplipy import API
from simplipy.entity import EntityTypes
from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError
from simplipy.websocket import (
EVENT_CAMERA_MOTION_DETECTED,
@ -590,6 +591,13 @@ class SimpliSafeEntity(CoordinatorEntity):
else:
self._serial = system.serial
try:
sensor_type = EntityTypes(
simplisafe.initial_event_to_use[system.system_id].get("sensorType")
)
except ValueError:
sensor_type = EntityTypes.unknown
self._attrs = {
ATTR_LAST_EVENT_INFO: simplisafe.initial_event_to_use[system.system_id].get(
"info"
@ -597,9 +605,7 @@ class SimpliSafeEntity(CoordinatorEntity):
ATTR_LAST_EVENT_SENSOR_NAME: simplisafe.initial_event_to_use[
system.system_id
].get("sensorName"),
ATTR_LAST_EVENT_SENSOR_TYPE: simplisafe.initial_event_to_use[
system.system_id
].get("sensorType"),
ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type.name,
ATTR_LAST_EVENT_TIMESTAMP: simplisafe.initial_event_to_use[
system.system_id
].get("eventTimestamp"),
@ -724,3 +730,23 @@ class SimpliSafeEntity(CoordinatorEntity):
@callback
def async_update_from_websocket_event(self, event):
"""Update the entity with the provided websocket event."""
class SimpliSafeBaseSensor(SimpliSafeEntity):
"""Define a SimpliSafe base (binary) sensor."""
def __init__(self, simplisafe, system, sensor):
"""Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
self._device_info["model"] = sensor.type.name
self._device_info["name"] = sensor.name
self._sensor = sensor
self._sensor_type_human_name = " ".join(
[w.title() for w in self._sensor.type.name.split("_")]
)
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._system.address} {self._name} {self._sensor_type_human_name}"

View File

@ -6,42 +6,32 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
BinarySensorEntity,
)
from homeassistant.core import callback
from . import SimpliSafeEntity
from . import SimpliSafeBaseSensor
from .const import DATA_CLIENT, DOMAIN, LOGGER
SUPPORTED_BATTERY_SENSOR_TYPES = [
EntityTypes.carbon_monoxide,
EntityTypes.entry,
EntityTypes.leak,
EntityTypes.lock,
EntityTypes.lock_keypad,
EntityTypes.smoke,
EntityTypes.temperature,
]
SUPPORTED_SENSOR_TYPES = [
EntityTypes.entry,
EntityTypes.carbon_monoxide,
EntityTypes.smoke,
EntityTypes.leak,
]
HA_SENSOR_TYPES = {
EntityTypes.entry: DEVICE_CLASS_DOOR,
TRIGGERED_SENSOR_TYPES = {
EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS,
EntityTypes.smoke: DEVICE_CLASS_SMOKE,
EntityTypes.entry: DEVICE_CLASS_DOOR,
EntityTypes.glass_break: DEVICE_CLASS_SAFETY,
EntityTypes.leak: DEVICE_CLASS_MOISTURE,
}
SENSOR_MODELS = {
EntityTypes.entry: "Entry Sensor",
EntityTypes.carbon_monoxide: "Carbon Monoxide Detector",
EntityTypes.smoke: "Smoke Detector",
EntityTypes.leak: "Water Sensor",
EntityTypes.motion: DEVICE_CLASS_MOTION,
EntityTypes.smoke: DEVICE_CLASS_SMOKE,
}
@ -56,37 +46,34 @@ async def async_setup_entry(hass, entry, async_add_entities):
continue
for sensor in system.sensors.values():
if sensor.type in SUPPORTED_SENSOR_TYPES:
sensors.append(SimpliSafeBinarySensor(simplisafe, system, sensor))
if sensor.type in TRIGGERED_SENSOR_TYPES:
sensors.append(
TriggeredBinarySensor(
simplisafe,
system,
sensor,
TRIGGERED_SENSOR_TYPES[sensor.type],
)
)
if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES:
sensors.append(SimpliSafeSensorBattery(simplisafe, system, sensor))
sensors.append(BatteryBinarySensor(simplisafe, system, sensor))
async_add_entities(sensors)
class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity):
"""Define a SimpliSafe binary sensor entity."""
class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
"""Define a binary sensor related to whether an entity has been triggered."""
def __init__(self, simplisafe, system, sensor):
def __init__(self, simplisafe, system, sensor, device_class):
"""Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
self._system = system
self._sensor = sensor
super().__init__(simplisafe, system, sensor)
self._device_class = device_class
self._is_on = False
@property
def device_class(self):
"""Return type of sensor."""
return HA_SENSOR_TYPES[self._sensor.type]
@property
def device_info(self):
"""Return device registry information for this entity."""
info = super().device_info
info["identifiers"] = {(DOMAIN, self._sensor.serial)}
info["model"] = SENSOR_MODELS[self._sensor.type]
info["name"] = self._sensor.name
return info
return self._device_class
@property
def is_on(self):
@ -99,19 +86,14 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity):
self._is_on = self._sensor.triggered
class SimpliSafeSensorBattery(SimpliSafeEntity, BinarySensorEntity):
class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
"""Define a SimpliSafe battery binary sensor entity."""
def __init__(self, simplisafe, system, sensor):
"""Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
self._sensor = sensor
super().__init__(simplisafe, system, sensor)
self._is_low = False
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
self._device_info["model"] = SENSOR_MODELS[sensor.type]
self._device_info["name"] = sensor.name
@property
def device_class(self):
"""Return type of sensor."""

View File

@ -4,7 +4,7 @@ from simplipy.entity import EntityTypes
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT
from homeassistant.core import callback
from . import SimpliSafeEntity
from . import SimpliSafeBaseSensor
from .const import DATA_CLIENT, DOMAIN, LOGGER
@ -25,19 +25,14 @@ async def async_setup_entry(hass, entry, async_add_entities):
async_add_entities(sensors)
class SimplisafeFreezeSensor(SimpliSafeEntity):
class SimplisafeFreezeSensor(SimpliSafeBaseSensor):
"""Define a SimpliSafe freeze sensor entity."""
def __init__(self, simplisafe, system, sensor):
"""Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial)
self._sensor = sensor
super().__init__(simplisafe, system, sensor)
self._state = None
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
self._device_info["model"] = "Freeze Sensor"
self._device_info["name"] = sensor.name
@property
def device_class(self):
"""Return type of sensor."""

View File

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

View File

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

View File

@ -342,7 +342,7 @@ beautifulsoup4==4.9.1
bellows==0.20.3
# homeassistant.components.bmw_connected_drive
bimmer_connected==0.7.11
bimmer_connected==0.7.12
# homeassistant.components.bizkaibus
bizkaibus==0.1.1
@ -732,7 +732,7 @@ hass-nabucasa==0.37.1
hass_splunk==0.1.1
# homeassistant.components.tasmota
hatasmota==0.0.25
hatasmota==0.0.25.1
# homeassistant.components.jewish_calendar
hdate==0.9.12

View File

@ -367,7 +367,7 @@ hangups==0.4.11
hass-nabucasa==0.37.1
# homeassistant.components.tasmota
hatasmota==0.0.25
hatasmota==0.0.25.1
# homeassistant.components.jewish_calendar
hdate==0.9.12

View File

@ -58,6 +58,44 @@ DEFAULT_CONFIG = {
}
DEFAULT_CONFIG_9_0_0_4 = {
"ip": "192.168.15.10",
"dn": "Tasmota",
"fn": ["Test", "Beer", "Milk", "Four", None],
"hn": "tasmota_49A3BC-0956",
"if": 0, # iFan
"lk": 1, # RGB + white channels linked to a single light
"mac": "00000049A3BC",
"md": "Sonoff Basic",
"ofln": "Offline",
"onln": "Online",
"state": ["OFF", "ON", "TOGGLE", "HOLD"],
"sw": "8.4.0.2",
"swn": [None, None, None, None, None],
"t": "tasmota_49A3BC",
"ft": "%topic%/%prefix%/",
"tp": ["cmnd", "stat", "tele"],
"rl": [0, 0, 0, 0, 0, 0, 0, 0],
"swc": [-1, -1, -1, -1, -1, -1, -1, -1],
"btn": [0, 0, 0, 0],
"so": {
"4": 0, # Return MQTT response as RESULT or %COMMAND%
"11": 0, # Swap button single and double press functionality
"13": 0, # Allow immediate action on single button press
"17": 1, # Show Color string as hex or comma-separated
"20": 0, # Update of Dimmer/Color/CT without turning power on
"30": 0, # Enforce Home Assistant auto-discovery as light
"68": 0, # Multi-channel PWM instead of a single light
"73": 0, # Enable Buttons decoupling and send multi-press and hold MQTT messages
"82": 0, # Reduce the CT range from 153..500 to 200.380
"114": 0, # Enable sending switch MQTT messages
},
"ty": 0, # Tuya MCU
"lt_st": 0,
"ver": 1,
}
async def help_test_availability_when_connection_lost(
hass,
mqtt_client_mock,

View File

@ -6,7 +6,7 @@ from homeassistant.components.tasmota.const import DEFAULT_PREFIX
from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED
from .conftest import setup_tasmota_helper
from .test_common import DEFAULT_CONFIG
from .test_common import DEFAULT_CONFIG, DEFAULT_CONFIG_9_0_0_4
from tests.async_mock import patch
from tests.common import async_fire_mqtt_message
@ -132,6 +132,29 @@ async def test_device_discover(
assert device_entry.sw_version == config["sw"]
async def test_device_discover_deprecated(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):
"""Test setting up a device with deprecated discovery message."""
config = copy.deepcopy(DEFAULT_CONFIG_9_0_0_4)
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
# Verify device and registry entries are created
device_entry = device_reg.async_get_device(set(), {("mac", mac)})
assert device_entry is not None
assert device_entry.manufacturer == "Tasmota"
assert device_entry.model == config["md"]
assert device_entry.name == config["dn"]
assert device_entry.sw_version == config["sw"]
async def test_device_update(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
):