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", "domain": "bmw_connected_drive",
"name": "BMW Connected Drive", "name": "BMW Connected Drive",
"documentation": "https://www.home-assistant.io/integrations/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": [], "dependencies": [],
"codeowners": ["@gerard33", "@rikroe"] "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}" name = f"{cube.room_by_id(device.room_id).name} {device.name}"
# Only add Window Shutters # Only add Window Shutters
if device.is_windowshutter(): if cube.is_windowshutter(device):
devices.append(MaxCubeShutter(handler, name, device.rf_address)) devices.append(MaxCubeShutter(handler, name, device.rf_address))
if devices: if devices:

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import asyncio
from uuid import UUID from uuid import UUID
from simplipy import API from simplipy import API
from simplipy.entity import EntityTypes
from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError
from simplipy.websocket import ( from simplipy.websocket import (
EVENT_CAMERA_MOTION_DETECTED, EVENT_CAMERA_MOTION_DETECTED,
@ -590,6 +591,13 @@ class SimpliSafeEntity(CoordinatorEntity):
else: else:
self._serial = system.serial 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 = { self._attrs = {
ATTR_LAST_EVENT_INFO: simplisafe.initial_event_to_use[system.system_id].get( ATTR_LAST_EVENT_INFO: simplisafe.initial_event_to_use[system.system_id].get(
"info" "info"
@ -597,9 +605,7 @@ class SimpliSafeEntity(CoordinatorEntity):
ATTR_LAST_EVENT_SENSOR_NAME: simplisafe.initial_event_to_use[ ATTR_LAST_EVENT_SENSOR_NAME: simplisafe.initial_event_to_use[
system.system_id system.system_id
].get("sensorName"), ].get("sensorName"),
ATTR_LAST_EVENT_SENSOR_TYPE: simplisafe.initial_event_to_use[ ATTR_LAST_EVENT_SENSOR_TYPE: sensor_type.name,
system.system_id
].get("sensorType"),
ATTR_LAST_EVENT_TIMESTAMP: simplisafe.initial_event_to_use[ ATTR_LAST_EVENT_TIMESTAMP: simplisafe.initial_event_to_use[
system.system_id system.system_id
].get("eventTimestamp"), ].get("eventTimestamp"),
@ -724,3 +730,23 @@ class SimpliSafeEntity(CoordinatorEntity):
@callback @callback
def async_update_from_websocket_event(self, event): def async_update_from_websocket_event(self, event):
"""Update the entity with the provided websocket 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_DOOR,
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_MOISTURE, DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.core import callback from homeassistant.core import callback
from . import SimpliSafeEntity from . import SimpliSafeBaseSensor
from .const import DATA_CLIENT, DOMAIN, LOGGER from .const import DATA_CLIENT, DOMAIN, LOGGER
SUPPORTED_BATTERY_SENSOR_TYPES = [ SUPPORTED_BATTERY_SENSOR_TYPES = [
EntityTypes.carbon_monoxide, EntityTypes.carbon_monoxide,
EntityTypes.entry, EntityTypes.entry,
EntityTypes.leak, EntityTypes.leak,
EntityTypes.lock, EntityTypes.lock_keypad,
EntityTypes.smoke, EntityTypes.smoke,
EntityTypes.temperature, EntityTypes.temperature,
] ]
SUPPORTED_SENSOR_TYPES = [ TRIGGERED_SENSOR_TYPES = {
EntityTypes.entry,
EntityTypes.carbon_monoxide,
EntityTypes.smoke,
EntityTypes.leak,
]
HA_SENSOR_TYPES = {
EntityTypes.entry: DEVICE_CLASS_DOOR,
EntityTypes.carbon_monoxide: DEVICE_CLASS_GAS, 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, EntityTypes.leak: DEVICE_CLASS_MOISTURE,
} EntityTypes.motion: DEVICE_CLASS_MOTION,
EntityTypes.smoke: DEVICE_CLASS_SMOKE,
SENSOR_MODELS = {
EntityTypes.entry: "Entry Sensor",
EntityTypes.carbon_monoxide: "Carbon Monoxide Detector",
EntityTypes.smoke: "Smoke Detector",
EntityTypes.leak: "Water Sensor",
} }
@ -56,37 +46,34 @@ async def async_setup_entry(hass, entry, async_add_entities):
continue continue
for sensor in system.sensors.values(): for sensor in system.sensors.values():
if sensor.type in SUPPORTED_SENSOR_TYPES: if sensor.type in TRIGGERED_SENSOR_TYPES:
sensors.append(SimpliSafeBinarySensor(simplisafe, system, sensor)) sensors.append(
TriggeredBinarySensor(
simplisafe,
system,
sensor,
TRIGGERED_SENSOR_TYPES[sensor.type],
)
)
if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES: if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES:
sensors.append(SimpliSafeSensorBattery(simplisafe, system, sensor)) sensors.append(BatteryBinarySensor(simplisafe, system, sensor))
async_add_entities(sensors) async_add_entities(sensors)
class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity): class TriggeredBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
"""Define a SimpliSafe binary sensor entity.""" """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.""" """Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) super().__init__(simplisafe, system, sensor)
self._system = system self._device_class = device_class
self._sensor = sensor
self._is_on = False self._is_on = False
@property @property
def device_class(self): def device_class(self):
"""Return type of sensor.""" """Return type of sensor."""
return HA_SENSOR_TYPES[self._sensor.type] return self._device_class
@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
@property @property
def is_on(self): def is_on(self):
@ -99,19 +86,14 @@ class SimpliSafeBinarySensor(SimpliSafeEntity, BinarySensorEntity):
self._is_on = self._sensor.triggered self._is_on = self._sensor.triggered
class SimpliSafeSensorBattery(SimpliSafeEntity, BinarySensorEntity): class BatteryBinarySensor(SimpliSafeBaseSensor, BinarySensorEntity):
"""Define a SimpliSafe battery binary sensor entity.""" """Define a SimpliSafe battery binary sensor entity."""
def __init__(self, simplisafe, system, sensor): def __init__(self, simplisafe, system, sensor):
"""Initialize.""" """Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) super().__init__(simplisafe, system, sensor)
self._sensor = sensor
self._is_low = False 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 @property
def device_class(self): def device_class(self):
"""Return type of sensor.""" """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.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT
from homeassistant.core import callback from homeassistant.core import callback
from . import SimpliSafeEntity from . import SimpliSafeBaseSensor
from .const import DATA_CLIENT, DOMAIN, LOGGER 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) async_add_entities(sensors)
class SimplisafeFreezeSensor(SimpliSafeEntity): class SimplisafeFreezeSensor(SimpliSafeBaseSensor):
"""Define a SimpliSafe freeze sensor entity.""" """Define a SimpliSafe freeze sensor entity."""
def __init__(self, simplisafe, system, sensor): def __init__(self, simplisafe, system, sensor):
"""Initialize.""" """Initialize."""
super().__init__(simplisafe, system, sensor.name, serial=sensor.serial) super().__init__(simplisafe, system, sensor)
self._sensor = sensor
self._state = None self._state = None
self._device_info["identifiers"] = {(DOMAIN, sensor.serial)}
self._device_info["model"] = "Freeze Sensor"
self._device_info["name"] = sensor.name
@property @property
def device_class(self): def device_class(self):
"""Return type of sensor.""" """Return type of sensor."""

View File

@ -3,7 +3,7 @@
"name": "Tasmota (beta)", "name": "Tasmota (beta)",
"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.0.25"], "requirements": ["hatasmota==0.0.25.1"],
"dependencies": ["mqtt"], "dependencies": ["mqtt"],
"mqtt": ["tasmota/discovery/#"], "mqtt": ["tasmota/discovery/#"],
"codeowners": ["@emontnemery"] "codeowners": ["@emontnemery"]

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 0 MAJOR_VERSION = 0
MINOR_VERSION = 117 MINOR_VERSION = 117
PATCH_VERSION = "4" PATCH_VERSION = "5"
__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, 7, 1) REQUIRED_PYTHON_VER = (3, 7, 1)

View File

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

View File

@ -367,7 +367,7 @@ hangups==0.4.11
hass-nabucasa==0.37.1 hass-nabucasa==0.37.1
# homeassistant.components.tasmota # homeassistant.components.tasmota
hatasmota==0.0.25 hatasmota==0.0.25.1
# homeassistant.components.jewish_calendar # homeassistant.components.jewish_calendar
hdate==0.9.12 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( async def help_test_availability_when_connection_lost(
hass, hass,
mqtt_client_mock, 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 homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED
from .conftest import setup_tasmota_helper 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.async_mock import patch
from tests.common import async_fire_mqtt_message from tests.common import async_fire_mqtt_message
@ -132,6 +132,29 @@ async def test_device_discover(
assert device_entry.sw_version == config["sw"] 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( async def test_device_update(
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
): ):