mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +00:00
Merge pull request #42874 from home-assistant/rc
This commit is contained in:
commit
22f16759e5
@ -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"]
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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}"
|
||||
|
@ -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."""
|
||||
|
@ -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."""
|
||||
|
@ -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"]
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
):
|
||||
|
Loading…
x
Reference in New Issue
Block a user