mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
2023.1.1 (#85277)
This commit is contained in:
commit
71ce7373a3
@ -69,6 +69,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
|
||||
return (
|
||||
self.vehicle.vehicle_location.location[0]
|
||||
if self.vehicle.is_vehicle_tracking_enabled
|
||||
and self.vehicle.vehicle_location.location
|
||||
else None
|
||||
)
|
||||
|
||||
@ -78,6 +79,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity):
|
||||
return (
|
||||
self.vehicle.vehicle_location.location[1]
|
||||
if self.vehicle.is_vehicle_tracking_enabled
|
||||
and self.vehicle.vehicle_location.location
|
||||
else None
|
||||
)
|
||||
|
||||
|
@ -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.10.4"],
|
||||
"requirements": ["bimmer_connected==0.12.0"],
|
||||
"codeowners": ["@gerard33", "@rikroe"],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling",
|
||||
|
@ -17,7 +17,7 @@
|
||||
"service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb"
|
||||
}
|
||||
],
|
||||
"requirements": ["bthome-ble==2.4.0"],
|
||||
"requirements": ["bthome-ble==2.4.1"],
|
||||
"dependencies": ["bluetooth"],
|
||||
"codeowners": ["@Ernst79"],
|
||||
"iot_class": "local_push"
|
||||
|
@ -28,6 +28,7 @@ from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
UnitOfEnergy,
|
||||
UnitOfVolume,
|
||||
)
|
||||
from homeassistant.core import CoreState, Event, HomeAssistant, callback
|
||||
@ -591,6 +592,21 @@ class DSMREntity(SensorEntity):
|
||||
"""Entity is only available if there is a telegram."""
|
||||
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
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of sensor, if available, translate if needed."""
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Rheem EcoNet Products",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/econet",
|
||||
"requirements": ["pyeconet==0.1.17"],
|
||||
"requirements": ["pyeconet==0.1.18"],
|
||||
"codeowners": ["@vangorra", "@w1ll1am23"],
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["paho_mqtt", "pyeconet"]
|
||||
|
@ -41,20 +41,20 @@ SUPPORTED_STATE_CLASSES = {
|
||||
SensorStateClass.TOTAL_INCREASING,
|
||||
}
|
||||
VALID_ENERGY_UNITS: set[str] = {
|
||||
UnitOfEnergy.WATT_HOUR,
|
||||
UnitOfEnergy.GIGA_JOULE,
|
||||
UnitOfEnergy.KILO_WATT_HOUR,
|
||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||
UnitOfEnergy.GIGA_JOULE,
|
||||
UnitOfEnergy.WATT_HOUR,
|
||||
}
|
||||
VALID_ENERGY_UNITS_GAS = {
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
*VALID_ENERGY_UNITS,
|
||||
}
|
||||
VALID_VOLUME_UNITS_WATER: set[str] = {
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
UnitOfVolume.GALLONS,
|
||||
UnitOfVolume.LITERS,
|
||||
|
@ -22,10 +22,10 @@ from .const import DOMAIN
|
||||
ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,)
|
||||
ENERGY_USAGE_UNITS = {
|
||||
sensor.SensorDeviceClass.ENERGY: (
|
||||
UnitOfEnergy.GIGA_JOULE,
|
||||
UnitOfEnergy.KILO_WATT_HOUR,
|
||||
UnitOfEnergy.MEGA_WATT_HOUR,
|
||||
UnitOfEnergy.WATT_HOUR,
|
||||
UnitOfEnergy.GIGA_JOULE,
|
||||
)
|
||||
}
|
||||
ENERGY_PRICE_UNITS = tuple(
|
||||
@ -39,12 +39,16 @@ GAS_USAGE_DEVICE_CLASSES = (
|
||||
)
|
||||
GAS_USAGE_UNITS = {
|
||||
sensor.SensorDeviceClass.ENERGY: (
|
||||
UnitOfEnergy.WATT_HOUR,
|
||||
UnitOfEnergy.GIGA_JOULE,
|
||||
UnitOfEnergy.KILO_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(
|
||||
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_UNITS = {
|
||||
sensor.SensorDeviceClass.WATER: (
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
UnitOfVolume.GALLONS,
|
||||
UnitOfVolume.LITERS,
|
||||
),
|
||||
|
@ -228,7 +228,6 @@ AQHI_SENSOR = ECSensorEntityDescription(
|
||||
key="aqhi",
|
||||
name="AQHI",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
native_unit_of_measurement="AQI",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
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]:
|
||||
device = registry.async_get(target)
|
||||
if device:
|
||||
coordinator = hass.data[DOMAIN][list(device.config_entries)[0]]
|
||||
# fully_method(coordinator.fully, *args, **kwargs) would make
|
||||
# test_services.py fail.
|
||||
await getattr(coordinator.fully, fully_method.__name__)(*args, **kwargs)
|
||||
for key in device.config_entries:
|
||||
entry = hass.config_entries.async_get_entry(key)
|
||||
if not entry:
|
||||
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:
|
||||
"""Load a URL on the Fully Kiosk Browser."""
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""DataUpdateCoordinator for LaCrosse View."""
|
||||
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
|
||||
|
||||
@ -30,7 +31,7 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
|
||||
) -> None:
|
||||
"""Initialize DataUpdateCoordinator for LaCrosse View."""
|
||||
self.api = api
|
||||
self.last_update = datetime.utcnow()
|
||||
self.last_update = time()
|
||||
self.username = entry.data["username"]
|
||||
self.password = entry.data["password"]
|
||||
self.hass = hass
|
||||
@ -45,26 +46,22 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]):
|
||||
|
||||
async def _async_update_data(self) -> list[Sensor]:
|
||||
"""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
|
||||
try:
|
||||
await self.api.login(self.username, self.password)
|
||||
except LoginError as 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:
|
||||
# Fetch last hour of data
|
||||
sensors = await self.api.get_sensors(
|
||||
location=Location(id=self.id, name=self.name),
|
||||
tz=self.hass.config.time_zone,
|
||||
start=str(int(yesterday_timestamp)),
|
||||
end=str(int(datetime.timestamp(now))),
|
||||
start=str(now - 3600),
|
||||
end=str(now),
|
||||
)
|
||||
except HTTPError as error:
|
||||
raise ConfigEntryNotReady from error
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Reolink IP NVR/camera",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||
"requirements": ["reolink-aio==0.1.1"],
|
||||
"requirements": ["reolink-aio==0.1.2"],
|
||||
"codeowners": ["@starkillerOG"],
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["reolink-aio"]
|
||||
|
@ -53,6 +53,8 @@ BLE_SCANNER_OPTIONS = [
|
||||
selector.SelectOptionDict(value=BLEScannerMode.PASSIVE, label="Passive"),
|
||||
]
|
||||
|
||||
INTERNAL_WIFI_AP_IP = "192.168.33.1"
|
||||
|
||||
|
||||
async def validate_input(
|
||||
hass: HomeAssistant,
|
||||
@ -217,7 +219,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
current_entry := await self.async_set_unique_id(mac)
|
||||
) and current_entry.data[CONF_HOST] == host:
|
||||
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(
|
||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||
|
@ -166,6 +166,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the SwitchBot API auth step."""
|
||||
errors = {}
|
||||
assert self._discovered_adv is not None
|
||||
description_placeholders = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
key_details = await self.hass.async_add_executor_job(
|
||||
@ -176,8 +177,10 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
except SwitchbotAccountConnectionError as 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"}
|
||||
description_placeholders = {"error_detail": str(ex)}
|
||||
else:
|
||||
return await self.async_step_lock_key(key_details)
|
||||
|
||||
@ -195,6 +198,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
),
|
||||
description_placeholders={
|
||||
"name": name_from_discovery(self._discovered_adv),
|
||||
**description_placeholders,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "switchbot",
|
||||
"name": "SwitchBot",
|
||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||
"requirements": ["PySwitchbot==0.36.1"],
|
||||
"requirements": ["PySwitchbot==0.36.2"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth"],
|
||||
"codeowners": [
|
||||
|
@ -40,7 +40,7 @@
|
||||
},
|
||||
"error": {
|
||||
"encryption_key_invalid": "Key ID or Encryption key is invalid",
|
||||
"auth_failed": "Authentication failed"
|
||||
"auth_failed": "Authentication failed: {error_detail}"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
|
@ -8,7 +8,7 @@
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"error": {
|
||||
"auth_failed": "Authentication failed",
|
||||
"auth_failed": "Authentication failed: {error_detail}",
|
||||
"encryption_key_invalid": "Key ID or Encryption key is invalid"
|
||||
},
|
||||
"flow_title": "{name} ({address})",
|
||||
@ -47,18 +47,7 @@
|
||||
"data": {
|
||||
"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": {
|
||||
@ -70,4 +59,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Tasmota",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
||||
"requirements": ["hatasmota==0.6.1"],
|
||||
"requirements": ["hatasmota==0.6.2"],
|
||||
"dependencies": ["mqtt"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"codeowners": ["@emontnemery"],
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
POWER_VOLT_AMPERE_REACTIVE,
|
||||
SIGNAL_STRENGTH_DECIBELS,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
UnitOfApparentPower,
|
||||
@ -217,8 +218,10 @@ SENSOR_UNIT_MAP = {
|
||||
hc.LIGHT_LUX: LIGHT_LUX,
|
||||
hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS,
|
||||
hc.PERCENTAGE: PERCENTAGE,
|
||||
hc.POWER_FACTOR: None,
|
||||
hc.POWER_WATT: UnitOfPower.WATT,
|
||||
hc.PRESSURE_HPA: UnitOfPressure.HPA,
|
||||
hc.REACTIVE_POWER: POWER_VOLT_AMPERE_REACTIVE,
|
||||
hc.SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS,
|
||||
hc.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
hc.SPEED_KILOMETERS_PER_HOUR: UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
|
@ -65,6 +65,7 @@ class UnifiEntity(Entity, Generic[HandlerT, DataT]):
|
||||
self.entity_description = description
|
||||
|
||||
self._removed = False
|
||||
self._write_state = False
|
||||
|
||||
self._attr_available = description.available_fn(controller, 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}))
|
||||
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_write_ha_state()
|
||||
if self._write_state:
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def async_signal_reachable_callback(self) -> None:
|
||||
|
@ -217,6 +217,7 @@ class UnifiSensorEntity(SensorEntity, Generic[_HandlerT, _DataT]):
|
||||
self.async_on_remove(
|
||||
handler.subscribe(
|
||||
self.async_signalling_callback,
|
||||
id_filter=self._obj_id,
|
||||
)
|
||||
)
|
||||
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}))
|
||||
return
|
||||
|
||||
update_state = False
|
||||
|
||||
obj = description.object_fn(self.controller.api, self._obj_id)
|
||||
if (value := description.value_fn(self.controller, obj)) != self.native_value:
|
||||
self._attr_native_value = value
|
||||
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
||||
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
|
||||
def async_signal_reachable_callback(self) -> None:
|
||||
|
@ -361,6 +361,7 @@ class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]):
|
||||
self.async_on_remove(
|
||||
handler.subscribe(
|
||||
self.async_signalling_callback,
|
||||
id_filter=self._obj_id,
|
||||
)
|
||||
)
|
||||
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}))
|
||||
return
|
||||
|
||||
update_state = False
|
||||
|
||||
if not description.only_event_for_state_change:
|
||||
obj = description.object_fn(self.controller.api, self._obj_id)
|
||||
self._attr_is_on = description.is_on_fn(self.controller.api, obj)
|
||||
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
||||
self.async_write_ha_state()
|
||||
if (is_on := description.is_on_fn(self.controller.api, obj)) != self.is_on:
|
||||
self._attr_is_on = is_on
|
||||
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
|
||||
def async_signal_reachable_callback(self) -> None:
|
||||
|
@ -163,6 +163,12 @@ class UnifiDeviceUpdateEntity(UnifiEntity[HandlerT, DataT], UpdateEntity):
|
||||
description = self.entity_description
|
||||
|
||||
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_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."""
|
||||
|
||||
_attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT
|
||||
_attr_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_should_poll = True # BaseZhaEntity defaults to False
|
||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 1
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.1.0"
|
||||
version = "2023.1.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -40,7 +40,7 @@ PyRMVtransport==0.3.3
|
||||
PySocks==1.7.1
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.36.1
|
||||
PySwitchbot==0.36.2
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -422,7 +422,7 @@ beautifulsoup4==4.11.1
|
||||
bellows==0.34.5
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer_connected==0.10.4
|
||||
bimmer_connected==0.12.0
|
||||
|
||||
# homeassistant.components.bizkaibus
|
||||
bizkaibus==0.1.1
|
||||
@ -488,7 +488,7 @@ brunt==1.2.0
|
||||
bt_proximity==0.2.1
|
||||
|
||||
# homeassistant.components.bthome
|
||||
bthome-ble==2.4.0
|
||||
bthome-ble==2.4.1
|
||||
|
||||
# homeassistant.components.bt_home_hub_5
|
||||
bthomehub5-devicelist==0.1.1
|
||||
@ -858,7 +858,7 @@ hass-nabucasa==0.61.0
|
||||
hass_splunk==0.1.1
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
hatasmota==0.6.1
|
||||
hatasmota==0.6.2
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.4
|
||||
@ -1560,7 +1560,7 @@ pydroid-ipcam==2.0.0
|
||||
pyebox==1.1.4
|
||||
|
||||
# homeassistant.components.econet
|
||||
pyeconet==0.1.17
|
||||
pyeconet==0.1.18
|
||||
|
||||
# homeassistant.components.edimax
|
||||
pyedimax==0.2.1
|
||||
@ -2190,7 +2190,7 @@ regenmaschine==2022.11.0
|
||||
renault-api==0.1.11
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.1.1
|
||||
reolink-aio==0.1.2
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==5.2
|
||||
|
@ -36,7 +36,7 @@ PyRMVtransport==0.3.3
|
||||
PySocks==1.7.1
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.36.1
|
||||
PySwitchbot==0.36.2
|
||||
|
||||
# homeassistant.components.transport_nsw
|
||||
PyTransportNSW==0.1.1
|
||||
@ -349,7 +349,7 @@ beautifulsoup4==4.11.1
|
||||
bellows==0.34.5
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer_connected==0.10.4
|
||||
bimmer_connected==0.12.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==2.13.0
|
||||
@ -392,7 +392,7 @@ brother==2.1.1
|
||||
brunt==1.2.0
|
||||
|
||||
# homeassistant.components.bthome
|
||||
bthome-ble==2.4.0
|
||||
bthome-ble==2.4.1
|
||||
|
||||
# homeassistant.components.buienradar
|
||||
buienradar==1.0.5
|
||||
@ -647,7 +647,7 @@ habitipy==0.2.0
|
||||
hass-nabucasa==0.61.0
|
||||
|
||||
# homeassistant.components.tasmota
|
||||
hatasmota==0.6.1
|
||||
hatasmota==0.6.2
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.4
|
||||
@ -1106,7 +1106,7 @@ pydexcom==0.2.3
|
||||
pydroid-ipcam==2.0.0
|
||||
|
||||
# homeassistant.components.econet
|
||||
pyeconet==0.1.17
|
||||
pyeconet==0.1.18
|
||||
|
||||
# homeassistant.components.efergy
|
||||
pyefergy==22.1.1
|
||||
@ -1529,7 +1529,7 @@ regenmaschine==2022.11.0
|
||||
renault-api==0.1.11
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.1.1
|
||||
reolink-aio==0.1.2
|
||||
|
||||
# homeassistant.components.python_script
|
||||
restrictedpython==5.2
|
||||
|
@ -4,7 +4,6 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
from bimmer_connected.account import MyBMWAccount
|
||||
from bimmer_connected.api.utils import log_to_to_file
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.bmw_connected_drive.const import (
|
||||
@ -64,15 +63,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
|
||||
}
|
||||
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
|
||||
for vehicle_base in [vehicle for brand in vehicles.values() for vehicle in brand]:
|
||||
vehicle_state_path = (
|
||||
@ -93,14 +83,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
|
||||
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:
|
||||
"""Mock a fully setup config entry and all components based on fixtures."""
|
||||
|
@ -26,6 +26,7 @@ from homeassistant.const import (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
VOLUME_CUBIC_METERS,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
)
|
||||
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)
|
||||
|
||||
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."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aioshelly.exceptions import (
|
||||
@ -12,6 +13,7 @@ import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.components.shelly import config_flow
|
||||
from homeassistant.components.shelly.const import (
|
||||
CONF_BLE_SCANNER_MODE,
|
||||
DOMAIN,
|
||||
@ -704,6 +706,30 @@ async def test_zeroconf_already_configured(hass):
|
||||
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):
|
||||
"""Test we abort if device firmware is unsupported."""
|
||||
with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported):
|
||||
|
@ -481,7 +481,7 @@ async def test_user_setup_wolock_auth(hass):
|
||||
|
||||
with patch(
|
||||
"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["flow_id"],
|
||||
@ -494,6 +494,7 @@ async def test_user_setup_wolock_auth(hass):
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "lock_auth"
|
||||
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(
|
||||
"homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key",
|
||||
|
Loading…
x
Reference in New Issue
Block a user