mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 00:27:19 +00:00
2023.1.1 (#85277)
This commit is contained in:
commit
71ce7373a3
@ -69,6 +69,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
|
|||||||
return (
|
return (
|
||||||
self.vehicle.vehicle_location.location[0]
|
self.vehicle.vehicle_location.location[0]
|
||||||
if self.vehicle.is_vehicle_tracking_enabled
|
if self.vehicle.is_vehicle_tracking_enabled
|
||||||
|
and self.vehicle.vehicle_location.location
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
|
|||||||
return (
|
return (
|
||||||
self.vehicle.vehicle_location.location[1]
|
self.vehicle.vehicle_location.location[1]
|
||||||
if self.vehicle.is_vehicle_tracking_enabled
|
if self.vehicle.is_vehicle_tracking_enabled
|
||||||
|
and self.vehicle.vehicle_location.location
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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.10.4"],
|
"requirements": ["bimmer_connected==0.12.0"],
|
||||||
"codeowners": ["@gerard33", "@rikroe"],
|
"codeowners": ["@gerard33", "@rikroe"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb"
|
"service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requirements": ["bthome-ble==2.4.0"],
|
"requirements": ["bthome-ble==2.4.1"],
|
||||||
"dependencies": ["bluetooth"],
|
"dependencies": ["bluetooth"],
|
||||||
"codeowners": ["@Ernst79"],
|
"codeowners": ["@Ernst79"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -28,6 +28,7 @@ from homeassistant.const import (
|
|||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
UnitOfEnergy,
|
||||||
UnitOfVolume,
|
UnitOfVolume,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CoreState, Event, HomeAssistant, callback
|
from homeassistant.core import CoreState, Event, HomeAssistant, callback
|
||||||
@ -591,6 +592,21 @@ class DSMREntity(SensorEntity):
|
|||||||
"""Entity is only available if there is a telegram."""
|
"""Entity is only available if there is a telegram."""
|
||||||
return self.telegram is not None
|
return self.telegram is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self) -> SensorDeviceClass | None:
|
||||||
|
"""Return the device class of this entity."""
|
||||||
|
device_class = super().device_class
|
||||||
|
|
||||||
|
# Override device class for gas sensors providing energy units, like
|
||||||
|
# kWh, MWh, GJ, etc. In those cases, the class should be energy, not gas
|
||||||
|
with suppress(ValueError):
|
||||||
|
if device_class == SensorDeviceClass.GAS and UnitOfEnergy(
|
||||||
|
str(self.native_unit_of_measurement)
|
||||||
|
):
|
||||||
|
return SensorDeviceClass.ENERGY
|
||||||
|
|
||||||
|
return device_class
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType:
|
def native_value(self) -> StateType:
|
||||||
"""Return the state of sensor, if available, translate if needed."""
|
"""Return the state of sensor, if available, translate if needed."""
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Rheem EcoNet Products",
|
"name": "Rheem EcoNet Products",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/econet",
|
"documentation": "https://www.home-assistant.io/integrations/econet",
|
||||||
"requirements": ["pyeconet==0.1.17"],
|
"requirements": ["pyeconet==0.1.18"],
|
||||||
"codeowners": ["@vangorra", "@w1ll1am23"],
|
"codeowners": ["@vangorra", "@w1ll1am23"],
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["paho_mqtt", "pyeconet"]
|
"loggers": ["paho_mqtt", "pyeconet"]
|
||||||
|
@ -41,20 +41,20 @@ SUPPORTED_STATE_CLASSES = {
|
|||||||
SensorStateClass.TOTAL_INCREASING,
|
SensorStateClass.TOTAL_INCREASING,
|
||||||
}
|
}
|
||||||
VALID_ENERGY_UNITS: set[str] = {
|
VALID_ENERGY_UNITS: set[str] = {
|
||||||
UnitOfEnergy.WATT_HOUR,
|
UnitOfEnergy.GIGA_JOULE,
|
||||||
UnitOfEnergy.KILO_WATT_HOUR,
|
UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
UnitOfEnergy.GIGA_JOULE,
|
UnitOfEnergy.WATT_HOUR,
|
||||||
}
|
}
|
||||||
VALID_ENERGY_UNITS_GAS = {
|
VALID_ENERGY_UNITS_GAS = {
|
||||||
UnitOfVolume.CUBIC_FEET,
|
|
||||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||||
|
UnitOfVolume.CUBIC_FEET,
|
||||||
UnitOfVolume.CUBIC_METERS,
|
UnitOfVolume.CUBIC_METERS,
|
||||||
*VALID_ENERGY_UNITS,
|
*VALID_ENERGY_UNITS,
|
||||||
}
|
}
|
||||||
VALID_VOLUME_UNITS_WATER: set[str] = {
|
VALID_VOLUME_UNITS_WATER: set[str] = {
|
||||||
UnitOfVolume.CUBIC_FEET,
|
|
||||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||||
|
UnitOfVolume.CUBIC_FEET,
|
||||||
UnitOfVolume.CUBIC_METERS,
|
UnitOfVolume.CUBIC_METERS,
|
||||||
UnitOfVolume.GALLONS,
|
UnitOfVolume.GALLONS,
|
||||||
UnitOfVolume.LITERS,
|
UnitOfVolume.LITERS,
|
||||||
|
@ -22,10 +22,10 @@ from .const import DOMAIN
|
|||||||
ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,)
|
ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,)
|
||||||
ENERGY_USAGE_UNITS = {
|
ENERGY_USAGE_UNITS = {
|
||||||
sensor.SensorDeviceClass.ENERGY: (
|
sensor.SensorDeviceClass.ENERGY: (
|
||||||
|
UnitOfEnergy.GIGA_JOULE,
|
||||||
UnitOfEnergy.KILO_WATT_HOUR,
|
UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
UnitOfEnergy.WATT_HOUR,
|
UnitOfEnergy.WATT_HOUR,
|
||||||
UnitOfEnergy.GIGA_JOULE,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ENERGY_PRICE_UNITS = tuple(
|
ENERGY_PRICE_UNITS = tuple(
|
||||||
@ -39,12 +39,16 @@ GAS_USAGE_DEVICE_CLASSES = (
|
|||||||
)
|
)
|
||||||
GAS_USAGE_UNITS = {
|
GAS_USAGE_UNITS = {
|
||||||
sensor.SensorDeviceClass.ENERGY: (
|
sensor.SensorDeviceClass.ENERGY: (
|
||||||
UnitOfEnergy.WATT_HOUR,
|
UnitOfEnergy.GIGA_JOULE,
|
||||||
UnitOfEnergy.KILO_WATT_HOUR,
|
UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||||
UnitOfEnergy.GIGA_JOULE,
|
UnitOfEnergy.WATT_HOUR,
|
||||||
|
),
|
||||||
|
sensor.SensorDeviceClass.GAS: (
|
||||||
|
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||||
|
UnitOfVolume.CUBIC_FEET,
|
||||||
|
UnitOfVolume.CUBIC_METERS,
|
||||||
),
|
),
|
||||||
sensor.SensorDeviceClass.GAS: (UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET),
|
|
||||||
}
|
}
|
||||||
GAS_PRICE_UNITS = tuple(
|
GAS_PRICE_UNITS = tuple(
|
||||||
f"/{unit}" for units in GAS_USAGE_UNITS.values() for unit in units
|
f"/{unit}" for units in GAS_USAGE_UNITS.values() for unit in units
|
||||||
@ -54,8 +58,9 @@ GAS_PRICE_UNIT_ERROR = "entity_unexpected_unit_gas_price"
|
|||||||
WATER_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.WATER,)
|
WATER_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.WATER,)
|
||||||
WATER_USAGE_UNITS = {
|
WATER_USAGE_UNITS = {
|
||||||
sensor.SensorDeviceClass.WATER: (
|
sensor.SensorDeviceClass.WATER: (
|
||||||
UnitOfVolume.CUBIC_METERS,
|
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||||
UnitOfVolume.CUBIC_FEET,
|
UnitOfVolume.CUBIC_FEET,
|
||||||
|
UnitOfVolume.CUBIC_METERS,
|
||||||
UnitOfVolume.GALLONS,
|
UnitOfVolume.GALLONS,
|
||||||
UnitOfVolume.LITERS,
|
UnitOfVolume.LITERS,
|
||||||
),
|
),
|
||||||
|
@ -228,7 +228,6 @@ AQHI_SENSOR = ECSensorEntityDescription(
|
|||||||
key="aqhi",
|
key="aqhi",
|
||||||
name="AQHI",
|
name="AQHI",
|
||||||
device_class=SensorDeviceClass.AQI,
|
device_class=SensorDeviceClass.AQI,
|
||||||
native_unit_of_measurement="AQI",
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
value_fn=_get_aqhi_value,
|
value_fn=_get_aqhi_value,
|
||||||
)
|
)
|
||||||
|
@ -47,10 +47,19 @@ async def async_setup_services(hass: HomeAssistant) -> None:
|
|||||||
for target in call.data[ATTR_DEVICE_ID]:
|
for target in call.data[ATTR_DEVICE_ID]:
|
||||||
device = registry.async_get(target)
|
device = registry.async_get(target)
|
||||||
if device:
|
if device:
|
||||||
coordinator = hass.data[DOMAIN][list(device.config_entries)[0]]
|
for key in device.config_entries:
|
||||||
# fully_method(coordinator.fully, *args, **kwargs) would make
|
entry = hass.config_entries.async_get_entry(key)
|
||||||
# test_services.py fail.
|
if not entry:
|
||||||
await getattr(coordinator.fully, fully_method.__name__)(*args, **kwargs)
|
continue
|
||||||
|
if entry.domain != DOMAIN:
|
||||||
|
continue
|
||||||
|
coordinator = hass.data[DOMAIN][key]
|
||||||
|
# fully_method(coordinator.fully, *args, **kwargs) would make
|
||||||
|
# test_services.py fail.
|
||||||
|
await getattr(coordinator.fully, fully_method.__name__)(
|
||||||
|
*args, **kwargs
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
async def async_load_url(call: ServiceCall) -> None:
|
async def async_load_url(call: ServiceCall) -> None:
|
||||||
"""Load a URL on the Fully Kiosk Browser."""
|
"""Load a URL on the Fully Kiosk Browser."""
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"""DataUpdateCoordinator for LaCrosse View."""
|
"""DataUpdateCoordinator for LaCrosse View."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
|
from time import time
|
||||||
|
|
||||||
from lacrosse_view import HTTPError, LaCrosse, Location, LoginError, Sensor
|
from lacrosse_view import HTTPError, LaCrosse, Location, LoginError, Sensor
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize DataUpdateCoordinator for LaCrosse View."""
|
"""Initialize DataUpdateCoordinator for LaCrosse View."""
|
||||||
self.api = api
|
self.api = api
|
||||||
self.last_update = datetime.utcnow()
|
self.last_update = time()
|
||||||
self.username = entry.data["username"]
|
self.username = entry.data["username"]
|
||||||
self.password = entry.data["password"]
|
self.password = entry.data["password"]
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
@ -45,26 +46,22 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> list[Sensor]:
|
async def _async_update_data(self) -> list[Sensor]:
|
||||||
"""Get the data for LaCrosse View."""
|
"""Get the data for LaCrosse View."""
|
||||||
now = datetime.utcnow()
|
now = int(time())
|
||||||
|
|
||||||
if self.last_update < now - timedelta(minutes=59): # Get new token
|
if self.last_update < now - 59 * 60: # Get new token once in a hour
|
||||||
self.last_update = now
|
self.last_update = now
|
||||||
try:
|
try:
|
||||||
await self.api.login(self.username, self.password)
|
await self.api.login(self.username, self.password)
|
||||||
except LoginError as error:
|
except LoginError as error:
|
||||||
raise ConfigEntryAuthFailed from error
|
raise ConfigEntryAuthFailed from error
|
||||||
|
|
||||||
# Get the timestamp for yesterday at 6 PM (this is what is used in the app, i noticed it when proxying the request)
|
|
||||||
yesterday = now - timedelta(days=1)
|
|
||||||
yesterday = yesterday.replace(hour=18, minute=0, second=0, microsecond=0)
|
|
||||||
yesterday_timestamp = datetime.timestamp(yesterday)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Fetch last hour of data
|
||||||
sensors = await self.api.get_sensors(
|
sensors = await self.api.get_sensors(
|
||||||
location=Location(id=self.id, name=self.name),
|
location=Location(id=self.id, name=self.name),
|
||||||
tz=self.hass.config.time_zone,
|
tz=self.hass.config.time_zone,
|
||||||
start=str(int(yesterday_timestamp)),
|
start=str(now - 3600),
|
||||||
end=str(int(datetime.timestamp(now))),
|
end=str(now),
|
||||||
)
|
)
|
||||||
except HTTPError as error:
|
except HTTPError as error:
|
||||||
raise ConfigEntryNotReady from error
|
raise ConfigEntryNotReady from error
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Reolink IP NVR/camera",
|
"name": "Reolink IP NVR/camera",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"requirements": ["reolink-aio==0.1.1"],
|
"requirements": ["reolink-aio==0.1.2"],
|
||||||
"codeowners": ["@starkillerOG"],
|
"codeowners": ["@starkillerOG"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["reolink-aio"]
|
"loggers": ["reolink-aio"]
|
||||||
|
@ -53,6 +53,8 @@ BLE_SCANNER_OPTIONS = [
|
|||||||
selector.SelectOptionDict(value=BLEScannerMode.PASSIVE, label="Passive"),
|
selector.SelectOptionDict(value=BLEScannerMode.PASSIVE, label="Passive"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
INTERNAL_WIFI_AP_IP = "192.168.33.1"
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(
|
async def validate_input(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -217,7 +219,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
current_entry := await self.async_set_unique_id(mac)
|
current_entry := await self.async_set_unique_id(mac)
|
||||||
) and current_entry.data[CONF_HOST] == host:
|
) and current_entry.data[CONF_HOST] == host:
|
||||||
await async_reconnect_soon(self.hass, current_entry)
|
await async_reconnect_soon(self.hass, current_entry)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: host})
|
if host == INTERNAL_WIFI_AP_IP:
|
||||||
|
# If the device is broadcasting the internal wifi ap ip
|
||||||
|
# we can't connect to it, so we should not update the
|
||||||
|
# entry with the new host as it will be unreachable
|
||||||
|
#
|
||||||
|
# This is a workaround for a bug in the firmware 0.12 (and older?)
|
||||||
|
# which should be removed once the firmware is fixed
|
||||||
|
# and the old version is no longer in use
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
else:
|
||||||
|
self._abort_if_unique_id_configured({CONF_HOST: host})
|
||||||
|
|
||||||
async def async_step_zeroconf(
|
async def async_step_zeroconf(
|
||||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||||
|
@ -166,6 +166,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle the SwitchBot API auth step."""
|
"""Handle the SwitchBot API auth step."""
|
||||||
errors = {}
|
errors = {}
|
||||||
assert self._discovered_adv is not None
|
assert self._discovered_adv is not None
|
||||||
|
description_placeholders = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
try:
|
try:
|
||||||
key_details = await self.hass.async_add_executor_job(
|
key_details = await self.hass.async_add_executor_job(
|
||||||
@ -176,8 +177,10 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
except SwitchbotAccountConnectionError as ex:
|
except SwitchbotAccountConnectionError as ex:
|
||||||
raise AbortFlow("cannot_connect") from ex
|
raise AbortFlow("cannot_connect") from ex
|
||||||
except SwitchbotAuthenticationError:
|
except SwitchbotAuthenticationError as ex:
|
||||||
|
_LOGGER.debug("Authentication failed: %s", ex, exc_info=True)
|
||||||
errors = {"base": "auth_failed"}
|
errors = {"base": "auth_failed"}
|
||||||
|
description_placeholders = {"error_detail": str(ex)}
|
||||||
else:
|
else:
|
||||||
return await self.async_step_lock_key(key_details)
|
return await self.async_step_lock_key(key_details)
|
||||||
|
|
||||||
@ -195,6 +198,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
),
|
),
|
||||||
description_placeholders={
|
description_placeholders={
|
||||||
"name": name_from_discovery(self._discovered_adv),
|
"name": name_from_discovery(self._discovered_adv),
|
||||||
|
**description_placeholders,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "switchbot",
|
"domain": "switchbot",
|
||||||
"name": "SwitchBot",
|
"name": "SwitchBot",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||||
"requirements": ["PySwitchbot==0.36.1"],
|
"requirements": ["PySwitchbot==0.36.2"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["bluetooth"],
|
"dependencies": ["bluetooth"],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"encryption_key_invalid": "Key ID or Encryption key is invalid",
|
"encryption_key_invalid": "Key ID or Encryption key is invalid",
|
||||||
"auth_failed": "Authentication failed"
|
"auth_failed": "Authentication failed: {error_detail}"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"unknown": "Unexpected error"
|
"unknown": "Unexpected error"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"auth_failed": "Authentication failed",
|
"auth_failed": "Authentication failed: {error_detail}",
|
||||||
"encryption_key_invalid": "Key ID or Encryption key is invalid"
|
"encryption_key_invalid": "Key ID or Encryption key is invalid"
|
||||||
},
|
},
|
||||||
"flow_title": "{name} ({address})",
|
"flow_title": "{name} ({address})",
|
||||||
@ -47,18 +47,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"address": "Device address"
|
"address": "Device address"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"lock_key": {
|
|
||||||
"description": "The {name} device requires encryption key, details on how to obtain it can be found in the documentation.",
|
|
||||||
"data": {
|
|
||||||
"key_id": "Key ID",
|
|
||||||
"encryption_key": "Encryption key"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"key_id_invalid": "Key ID or Encryption key is invalid",
|
|
||||||
"encryption_key_invalid": "Key ID or Encryption key is invalid"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -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.6.1"],
|
"requirements": ["hatasmota==0.6.2"],
|
||||||
"dependencies": ["mqtt"],
|
"dependencies": ["mqtt"],
|
||||||
"mqtt": ["tasmota/discovery/#"],
|
"mqtt": ["tasmota/discovery/#"],
|
||||||
"codeowners": ["@emontnemery"],
|
"codeowners": ["@emontnemery"],
|
||||||
|
@ -21,6 +21,7 @@ from homeassistant.const import (
|
|||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
LIGHT_LUX,
|
LIGHT_LUX,
|
||||||
PERCENTAGE,
|
PERCENTAGE,
|
||||||
|
POWER_VOLT_AMPERE_REACTIVE,
|
||||||
SIGNAL_STRENGTH_DECIBELS,
|
SIGNAL_STRENGTH_DECIBELS,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
UnitOfApparentPower,
|
UnitOfApparentPower,
|
||||||
@ -217,8 +218,10 @@ SENSOR_UNIT_MAP = {
|
|||||||
hc.LIGHT_LUX: LIGHT_LUX,
|
hc.LIGHT_LUX: LIGHT_LUX,
|
||||||
hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS,
|
hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS,
|
||||||
hc.PERCENTAGE: PERCENTAGE,
|
hc.PERCENTAGE: PERCENTAGE,
|
||||||
|
hc.POWER_FACTOR: None,
|
||||||
hc.POWER_WATT: UnitOfPower.WATT,
|
hc.POWER_WATT: UnitOfPower.WATT,
|
||||||
hc.PRESSURE_HPA: UnitOfPressure.HPA,
|
hc.PRESSURE_HPA: UnitOfPressure.HPA,
|
||||||
|
hc.REACTIVE_POWER: POWER_VOLT_AMPERE_REACTIVE,
|
||||||
hc.SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS,
|
hc.SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS,
|
||||||
hc.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
hc.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
hc.SPEED_KILOMETERS_PER_HOUR: UnitOfSpeed.KILOMETERS_PER_HOUR,
|
hc.SPEED_KILOMETERS_PER_HOUR: UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
|
@ -65,6 +65,7 @@ class UnifiEntity(Entity, Generic[HandlerT, DataT]):
|
|||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
||||||
self._removed = False
|
self._removed = False
|
||||||
|
self._write_state = False
|
||||||
|
|
||||||
self._attr_available = description.available_fn(controller, obj_id)
|
self._attr_available = description.available_fn(controller, obj_id)
|
||||||
self._attr_device_info = description.device_info_fn(controller.api, obj_id)
|
self._attr_device_info = description.device_info_fn(controller.api, obj_id)
|
||||||
@ -117,9 +118,14 @@ class UnifiEntity(Entity, Generic[HandlerT, DataT]):
|
|||||||
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
||||||
return
|
return
|
||||||
|
|
||||||
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
if (
|
||||||
|
available := description.available_fn(self.controller, self._obj_id)
|
||||||
|
) != self.available:
|
||||||
|
self._attr_available = available
|
||||||
|
self._write_state = True
|
||||||
self.async_update_state(event, obj_id)
|
self.async_update_state(event, obj_id)
|
||||||
self.async_write_ha_state()
|
if self._write_state:
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_signal_reachable_callback(self) -> None:
|
def async_signal_reachable_callback(self) -> None:
|
||||||
|
@ -217,6 +217,7 @@ class UnifiSensorEntity(SensorEntity, Generic[_HandlerT, _DataT]):
|
|||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
handler.subscribe(
|
handler.subscribe(
|
||||||
self.async_signalling_callback,
|
self.async_signalling_callback,
|
||||||
|
id_filter=self._obj_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
@ -253,11 +254,19 @@ class UnifiSensorEntity(SensorEntity, Generic[_HandlerT, _DataT]):
|
|||||||
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
update_state = False
|
||||||
|
|
||||||
obj = description.object_fn(self.controller.api, self._obj_id)
|
obj = description.object_fn(self.controller.api, self._obj_id)
|
||||||
if (value := description.value_fn(self.controller, obj)) != self.native_value:
|
if (value := description.value_fn(self.controller, obj)) != self.native_value:
|
||||||
self._attr_native_value = value
|
self._attr_native_value = value
|
||||||
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
update_state = True
|
||||||
self.async_write_ha_state()
|
if (
|
||||||
|
available := description.available_fn(self.controller, self._obj_id)
|
||||||
|
) != self.available:
|
||||||
|
self._attr_available = available
|
||||||
|
update_state = True
|
||||||
|
if update_state:
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_signal_reachable_callback(self) -> None:
|
def async_signal_reachable_callback(self) -> None:
|
||||||
|
@ -361,6 +361,7 @@ class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]):
|
|||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
handler.subscribe(
|
handler.subscribe(
|
||||||
self.async_signalling_callback,
|
self.async_signalling_callback,
|
||||||
|
id_filter=self._obj_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
@ -410,11 +411,20 @@ class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]):
|
|||||||
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
update_state = False
|
||||||
|
|
||||||
if not description.only_event_for_state_change:
|
if not description.only_event_for_state_change:
|
||||||
obj = description.object_fn(self.controller.api, self._obj_id)
|
obj = description.object_fn(self.controller.api, self._obj_id)
|
||||||
self._attr_is_on = description.is_on_fn(self.controller.api, obj)
|
if (is_on := description.is_on_fn(self.controller.api, obj)) != self.is_on:
|
||||||
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
self._attr_is_on = is_on
|
||||||
self.async_write_ha_state()
|
update_state = True
|
||||||
|
if (
|
||||||
|
available := description.available_fn(self.controller, self._obj_id)
|
||||||
|
) != self.available:
|
||||||
|
self._attr_available = available
|
||||||
|
update_state = True
|
||||||
|
if update_state:
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_signal_reachable_callback(self) -> None:
|
def async_signal_reachable_callback(self) -> None:
|
||||||
|
@ -163,6 +163,12 @@ class UnifiDeviceUpdateEntity(UnifiEntity[HandlerT, DataT], UpdateEntity):
|
|||||||
description = self.entity_description
|
description = self.entity_description
|
||||||
|
|
||||||
obj = description.object_fn(self.controller.api, self._obj_id)
|
obj = description.object_fn(self.controller.api, self._obj_id)
|
||||||
self._attr_in_progress = description.state_fn(self.controller.api, obj)
|
if (
|
||||||
|
in_progress := description.state_fn(self.controller.api, obj)
|
||||||
|
) != self.in_progress:
|
||||||
|
self._attr_in_progress = in_progress
|
||||||
|
self._write_state = True
|
||||||
self._attr_installed_version = obj.version
|
self._attr_installed_version = obj.version
|
||||||
self._attr_latest_version = obj.upgrade_to_firmware or obj.version
|
self._attr_latest_version = obj.upgrade_to_firmware or obj.version
|
||||||
|
if self.installed_version != self.latest_version:
|
||||||
|
self._write_state = True
|
||||||
|
@ -755,7 +755,6 @@ class RSSISensor(Sensor, id_suffix="rssi"):
|
|||||||
"""RSSI sensor for a device."""
|
"""RSSI sensor for a device."""
|
||||||
|
|
||||||
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
|
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
|
||||||
_attr_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH
|
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
_attr_entity_registry_enabled_default = False
|
_attr_entity_registry_enabled_default = False
|
||||||
_attr_should_poll = True # BaseZhaEntity defaults to False
|
_attr_should_poll = True # BaseZhaEntity defaults to False
|
||||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 1
|
MINOR_VERSION: Final = 1
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.1.0"
|
version = "2023.1.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -40,7 +40,7 @@ PyRMVtransport==0.3.3
|
|||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.36.1
|
PySwitchbot==0.36.2
|
||||||
|
|
||||||
# homeassistant.components.transport_nsw
|
# homeassistant.components.transport_nsw
|
||||||
PyTransportNSW==0.1.1
|
PyTransportNSW==0.1.1
|
||||||
@ -422,7 +422,7 @@ beautifulsoup4==4.11.1
|
|||||||
bellows==0.34.5
|
bellows==0.34.5
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer_connected==0.10.4
|
bimmer_connected==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.bizkaibus
|
# homeassistant.components.bizkaibus
|
||||||
bizkaibus==0.1.1
|
bizkaibus==0.1.1
|
||||||
@ -488,7 +488,7 @@ brunt==1.2.0
|
|||||||
bt_proximity==0.2.1
|
bt_proximity==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==2.4.0
|
bthome-ble==2.4.1
|
||||||
|
|
||||||
# homeassistant.components.bt_home_hub_5
|
# homeassistant.components.bt_home_hub_5
|
||||||
bthomehub5-devicelist==0.1.1
|
bthomehub5-devicelist==0.1.1
|
||||||
@ -858,7 +858,7 @@ hass-nabucasa==0.61.0
|
|||||||
hass_splunk==0.1.1
|
hass_splunk==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.6.1
|
hatasmota==0.6.2
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.10.4
|
hdate==0.10.4
|
||||||
@ -1560,7 +1560,7 @@ pydroid-ipcam==2.0.0
|
|||||||
pyebox==1.1.4
|
pyebox==1.1.4
|
||||||
|
|
||||||
# homeassistant.components.econet
|
# homeassistant.components.econet
|
||||||
pyeconet==0.1.17
|
pyeconet==0.1.18
|
||||||
|
|
||||||
# homeassistant.components.edimax
|
# homeassistant.components.edimax
|
||||||
pyedimax==0.2.1
|
pyedimax==0.2.1
|
||||||
@ -2190,7 +2190,7 @@ regenmaschine==2022.11.0
|
|||||||
renault-api==0.1.11
|
renault-api==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.1.1
|
reolink-aio==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.python_script
|
# homeassistant.components.python_script
|
||||||
restrictedpython==5.2
|
restrictedpython==5.2
|
||||||
|
@ -36,7 +36,7 @@ PyRMVtransport==0.3.3
|
|||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.36.1
|
PySwitchbot==0.36.2
|
||||||
|
|
||||||
# homeassistant.components.transport_nsw
|
# homeassistant.components.transport_nsw
|
||||||
PyTransportNSW==0.1.1
|
PyTransportNSW==0.1.1
|
||||||
@ -349,7 +349,7 @@ beautifulsoup4==4.11.1
|
|||||||
bellows==0.34.5
|
bellows==0.34.5
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer_connected==0.10.4
|
bimmer_connected==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==2.13.0
|
bleak-retry-connector==2.13.0
|
||||||
@ -392,7 +392,7 @@ brother==2.1.1
|
|||||||
brunt==1.2.0
|
brunt==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.bthome
|
# homeassistant.components.bthome
|
||||||
bthome-ble==2.4.0
|
bthome-ble==2.4.1
|
||||||
|
|
||||||
# homeassistant.components.buienradar
|
# homeassistant.components.buienradar
|
||||||
buienradar==1.0.5
|
buienradar==1.0.5
|
||||||
@ -647,7 +647,7 @@ habitipy==0.2.0
|
|||||||
hass-nabucasa==0.61.0
|
hass-nabucasa==0.61.0
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.6.1
|
hatasmota==0.6.2
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.10.4
|
hdate==0.10.4
|
||||||
@ -1106,7 +1106,7 @@ pydexcom==0.2.3
|
|||||||
pydroid-ipcam==2.0.0
|
pydroid-ipcam==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.econet
|
# homeassistant.components.econet
|
||||||
pyeconet==0.1.17
|
pyeconet==0.1.18
|
||||||
|
|
||||||
# homeassistant.components.efergy
|
# homeassistant.components.efergy
|
||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
@ -1529,7 +1529,7 @@ regenmaschine==2022.11.0
|
|||||||
renault-api==0.1.11
|
renault-api==0.1.11
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.1.1
|
reolink-aio==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.python_script
|
# homeassistant.components.python_script
|
||||||
restrictedpython==5.2
|
restrictedpython==5.2
|
||||||
|
@ -4,7 +4,6 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bimmer_connected.account import MyBMWAccount
|
from bimmer_connected.account import MyBMWAccount
|
||||||
from bimmer_connected.api.utils import log_to_to_file
|
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.bmw_connected_drive.const import (
|
from homeassistant.components.bmw_connected_drive.const import (
|
||||||
@ -64,15 +63,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
|
|||||||
}
|
}
|
||||||
fetched_at = utcnow()
|
fetched_at = utcnow()
|
||||||
|
|
||||||
# simulate storing fingerprints
|
|
||||||
if account.config.log_response_path:
|
|
||||||
for brand in ["bmw", "mini"]:
|
|
||||||
log_to_to_file(
|
|
||||||
json.dumps(vehicles[brand]),
|
|
||||||
account.config.log_response_path,
|
|
||||||
f"vehicles_v2_{brand}",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a vehicle with base + specific state as provided by state/VIN API
|
# Create a vehicle with base + specific state as provided by state/VIN API
|
||||||
for vehicle_base in [vehicle for brand in vehicles.values() for vehicle in brand]:
|
for vehicle_base in [vehicle for brand in vehicles.values() for vehicle in brand]:
|
||||||
vehicle_state_path = (
|
vehicle_state_path = (
|
||||||
@ -93,14 +83,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
|
|||||||
fetched_at,
|
fetched_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
# simulate storing fingerprints
|
|
||||||
if account.config.log_response_path:
|
|
||||||
log_to_to_file(
|
|
||||||
json.dumps(vehicle_state),
|
|
||||||
account.config.log_response_path,
|
|
||||||
f"state_{vehicle_base['vin']}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry:
|
async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry:
|
||||||
"""Mock a fully setup config entry and all components based on fixtures."""
|
"""Mock a fully setup config entry and all components based on fixtures."""
|
||||||
|
@ -26,6 +26,7 @@ from homeassistant.const import (
|
|||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
VOLUME_CUBIC_METERS,
|
VOLUME_CUBIC_METERS,
|
||||||
|
UnitOfEnergy,
|
||||||
UnitOfPower,
|
UnitOfPower,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@ -804,3 +805,57 @@ async def test_reconnect(hass, dsmr_connection_fixture):
|
|||||||
await hass.config_entries.async_unload(mock_entry.entry_id)
|
await hass.config_entries.async_unload(mock_entry.entry_id)
|
||||||
|
|
||||||
assert mock_entry.state == config_entries.ConfigEntryState.NOT_LOADED
|
assert mock_entry.state == config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_gas_meter_providing_energy_reading(hass, dsmr_connection_fixture):
|
||||||
|
"""Test that gas providing energy readings use the correct device class."""
|
||||||
|
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||||
|
|
||||||
|
from dsmr_parser.obis_references import GAS_METER_READING
|
||||||
|
from dsmr_parser.objects import MBusObject
|
||||||
|
|
||||||
|
entry_data = {
|
||||||
|
"port": "/dev/ttyUSB0",
|
||||||
|
"dsmr_version": "2.2",
|
||||||
|
"precision": 4,
|
||||||
|
"reconnect_interval": 30,
|
||||||
|
"serial_id": "1234",
|
||||||
|
"serial_id_gas": "5678",
|
||||||
|
}
|
||||||
|
entry_options = {
|
||||||
|
"time_between_update": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
telegram = {
|
||||||
|
GAS_METER_READING: MBusObject(
|
||||||
|
[
|
||||||
|
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||||
|
{"value": Decimal(123.456), "unit": UnitOfEnergy.GIGA_JOULE},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_entry = MockConfigEntry(
|
||||||
|
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||||
|
telegram_callback(telegram)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
|
||||||
|
assert gas_consumption.state == "123.456"
|
||||||
|
assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY
|
||||||
|
assert (
|
||||||
|
gas_consumption.attributes.get(ATTR_STATE_CLASS)
|
||||||
|
== SensorStateClass.TOTAL_INCREASING
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
gas_consumption.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== UnitOfEnergy.GIGA_JOULE
|
||||||
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Test the Shelly config flow."""
|
"""Test the Shelly config flow."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import replace
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from aioshelly.exceptions import (
|
from aioshelly.exceptions import (
|
||||||
@ -12,6 +13,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components import zeroconf
|
from homeassistant.components import zeroconf
|
||||||
|
from homeassistant.components.shelly import config_flow
|
||||||
from homeassistant.components.shelly.const import (
|
from homeassistant.components.shelly.const import (
|
||||||
CONF_BLE_SCANNER_MODE,
|
CONF_BLE_SCANNER_MODE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -704,6 +706,30 @@ async def test_zeroconf_already_configured(hass):
|
|||||||
assert entry.data["host"] == "1.1.1.1"
|
assert entry.data["host"] == "1.1.1.1"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_with_wifi_ap_ip(hass):
|
||||||
|
"""Test we ignore the Wi-FI AP IP."""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain="shelly", unique_id="test-mac", data={"host": "2.2.2.2"}
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"aioshelly.common.get_info",
|
||||||
|
return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False},
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
data=replace(DISCOVERY_INFO, host=config_flow.INTERNAL_WIFI_AP_IP),
|
||||||
|
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
# Test config entry was not updated with the wifi ap ip
|
||||||
|
assert entry.data["host"] == "2.2.2.2"
|
||||||
|
|
||||||
|
|
||||||
async def test_zeroconf_firmware_unsupported(hass):
|
async def test_zeroconf_firmware_unsupported(hass):
|
||||||
"""Test we abort if device firmware is unsupported."""
|
"""Test we abort if device firmware is unsupported."""
|
||||||
with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported):
|
with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported):
|
||||||
|
@ -481,7 +481,7 @@ async def test_user_setup_wolock_auth(hass):
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key",
|
"homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key",
|
||||||
side_effect=SwitchbotAuthenticationError,
|
side_effect=SwitchbotAuthenticationError("error from api"),
|
||||||
):
|
):
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -494,6 +494,7 @@ async def test_user_setup_wolock_auth(hass):
|
|||||||
assert result["type"] == FlowResultType.FORM
|
assert result["type"] == FlowResultType.FORM
|
||||||
assert result["step_id"] == "lock_auth"
|
assert result["step_id"] == "lock_auth"
|
||||||
assert result["errors"] == {"base": "auth_failed"}
|
assert result["errors"] == {"base": "auth_failed"}
|
||||||
|
assert "error from api" in result["description_placeholders"]["error_detail"]
|
||||||
|
|
||||||
with patch_async_setup_entry() as mock_setup_entry, patch(
|
with patch_async_setup_entry() as mock_setup_entry, patch(
|
||||||
"homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key",
|
"homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user