mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
2023.11.2 (#103737)
This commit is contained in:
commit
a3319262ac
@ -9,5 +9,5 @@
|
||||
},
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["jaraco.abode", "lomond"],
|
||||
"requirements": ["jaraco.abode==3.3.0"]
|
||||
"requirements": ["jaraco.abode==3.3.0", "jaraco.functools==3.9.0"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.3.1"]
|
||||
"requirements": ["aioairzone-cloud==0.3.5"]
|
||||
}
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.22.2"]
|
||||
"requirements": ["blinkpy==0.22.3"]
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
||||
"bleak-retry-connector==3.3.0",
|
||||
"bluetooth-adapters==0.16.1",
|
||||
"bluetooth-auto-recovery==1.2.3",
|
||||
"bluetooth-data-tools==1.13.0",
|
||||
"bluetooth-data-tools==1.14.0",
|
||||
"dbus-fast==2.12.0"
|
||||
]
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ class DSMRConnection:
|
||||
self._protocol = protocol
|
||||
self._telegram: dict[str, DSMRObject] = {}
|
||||
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
|
||||
if dsmr_version == "5B":
|
||||
self._equipment_identifier = obis_ref.BELGIUM_EQUIPMENT_IDENTIFIER
|
||||
if dsmr_version == "5L":
|
||||
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
|
||||
if dsmr_version == "Q3D":
|
||||
|
@ -34,6 +34,3 @@ DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}
|
||||
|
||||
DSMR_PROTOCOL = "dsmr_protocol"
|
||||
RFXTRX_DSMR_PROTOCOL = "rfxtrx_dsmr_protocol"
|
||||
|
||||
# Temp obis until sensors replaced by mbus variants
|
||||
BELGIUM_5MIN_GAS_METER_READING = r"\d-\d:24\.2\.3.+?\r\n"
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["dsmr_parser"],
|
||||
"requirements": ["dsmr-parser==1.3.0"]
|
||||
"requirements": ["dsmr-parser==1.3.1"]
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import (
|
||||
BELGIUM_5MIN_GAS_METER_READING,
|
||||
CONF_DSMR_VERSION,
|
||||
CONF_PRECISION,
|
||||
CONF_PROTOCOL,
|
||||
@ -382,16 +381,6 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
|
||||
device_class=SensorDeviceClass.GAS,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
DSMRSensorEntityDescription(
|
||||
key="belgium_5min_gas_meter_reading",
|
||||
translation_key="gas_meter_reading",
|
||||
obis_reference=BELGIUM_5MIN_GAS_METER_READING,
|
||||
dsmr_versions={"5B"},
|
||||
is_gas=True,
|
||||
force_update=True,
|
||||
device_class=SensorDeviceClass.GAS,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
DSMRSensorEntityDescription(
|
||||
key="gas_meter_reading",
|
||||
translation_key="gas_meter_reading",
|
||||
@ -405,6 +394,31 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
|
||||
)
|
||||
|
||||
|
||||
def add_gas_sensor_5B(telegram: dict[str, DSMRObject]) -> DSMRSensorEntityDescription:
|
||||
"""Return correct entity for 5B Gas meter."""
|
||||
ref = None
|
||||
if obis_references.BELGIUM_MBUS1_METER_READING2 in telegram:
|
||||
ref = obis_references.BELGIUM_MBUS1_METER_READING2
|
||||
elif obis_references.BELGIUM_MBUS2_METER_READING2 in telegram:
|
||||
ref = obis_references.BELGIUM_MBUS2_METER_READING2
|
||||
elif obis_references.BELGIUM_MBUS3_METER_READING2 in telegram:
|
||||
ref = obis_references.BELGIUM_MBUS3_METER_READING2
|
||||
elif obis_references.BELGIUM_MBUS4_METER_READING2 in telegram:
|
||||
ref = obis_references.BELGIUM_MBUS4_METER_READING2
|
||||
elif ref is None:
|
||||
ref = obis_references.BELGIUM_MBUS1_METER_READING2
|
||||
return DSMRSensorEntityDescription(
|
||||
key="belgium_5min_gas_meter_reading",
|
||||
translation_key="gas_meter_reading",
|
||||
obis_reference=ref,
|
||||
dsmr_versions={"5B"},
|
||||
is_gas=True,
|
||||
force_update=True,
|
||||
device_class=SensorDeviceClass.GAS,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
@ -438,6 +452,10 @@ async def async_setup_entry(
|
||||
return (entity_description.device_class, UNIT_CONVERSION[uom])
|
||||
return (entity_description.device_class, uom)
|
||||
|
||||
all_sensors = SENSORS
|
||||
if dsmr_version == "5B":
|
||||
all_sensors += (add_gas_sensor_5B(telegram),)
|
||||
|
||||
entities.extend(
|
||||
[
|
||||
DSMREntity(
|
||||
@ -448,7 +466,7 @@ async def async_setup_entry(
|
||||
telegram, description
|
||||
), # type: ignore[arg-type]
|
||||
)
|
||||
for description in SENSORS
|
||||
for description in all_sensors
|
||||
if (
|
||||
description.dsmr_versions is None
|
||||
or dsmr_version in description.dsmr_versions
|
||||
|
@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.14.1"],
|
||||
"requirements": ["pyenphase==1.14.2"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
@ -596,6 +596,10 @@ def _async_setup_device_registry(
|
||||
model = project_name[1]
|
||||
hw_version = device_info.project_version
|
||||
|
||||
suggested_area = None
|
||||
if device_info.suggested_area:
|
||||
suggested_area = device_info.suggested_area
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
@ -606,6 +610,7 @@ def _async_setup_device_registry(
|
||||
model=model,
|
||||
sw_version=sw_version,
|
||||
hw_version=hw_version,
|
||||
suggested_area=suggested_area,
|
||||
)
|
||||
return device_entry.id
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
"loggers": ["aioesphomeapi", "noiseprotocol"],
|
||||
"requirements": [
|
||||
"async-interrupt==1.1.1",
|
||||
"aioesphomeapi==18.1.0",
|
||||
"bluetooth-data-tools==1.13.0",
|
||||
"aioesphomeapi==18.2.4",
|
||||
"bluetooth-data-tools==1.14.0",
|
||||
"esphome-dashboard-api==1.2.3"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
|
@ -139,9 +139,9 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
return self._device_information["fwVersion"]
|
||||
|
||||
@property
|
||||
def serial_number(self) -> str:
|
||||
def serial_number(self) -> str | None:
|
||||
"""Return the serial number for the device."""
|
||||
return self._device_information["serialNumber"]
|
||||
return self._device_information.get("serialNumber")
|
||||
|
||||
@property
|
||||
def pending_info_alerts_count(self) -> int:
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20231030.1"]
|
||||
"requirements": ["home-assistant-frontend==20231030.2"]
|
||||
}
|
||||
|
@ -122,12 +122,12 @@ class KNXExposeSensor:
|
||||
"""Extract value from state."""
|
||||
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||
value = self.expose_default
|
||||
elif self.expose_attribute is not None:
|
||||
_attr = state.attributes.get(self.expose_attribute)
|
||||
value = _attr if _attr is not None else self.expose_default
|
||||
else:
|
||||
value = (
|
||||
state.state
|
||||
if self.expose_attribute is None
|
||||
else state.attributes.get(self.expose_attribute, self.expose_default)
|
||||
)
|
||||
value = state.state
|
||||
|
||||
if self.expose_type == "binary":
|
||||
if value in (1, STATE_ON, "True"):
|
||||
return True
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluetooth-data-tools==1.13.0", "ld2410-ble==0.1.1"]
|
||||
"requirements": ["bluetooth-data-tools==1.14.0", "ld2410-ble==0.1.1"]
|
||||
}
|
||||
|
@ -32,5 +32,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["bluetooth-data-tools==1.13.0", "led-ble==1.0.1"]
|
||||
"requirements": ["bluetooth-data-tools==1.14.0", "led-ble==1.0.1"]
|
||||
}
|
||||
|
@ -28,9 +28,10 @@ def setup_platform(
|
||||
|
||||
data = hass.data[LUPUSEC_DOMAIN]
|
||||
|
||||
devices = []
|
||||
device_types = [CONST.TYPE_SWITCH]
|
||||
|
||||
for device in data.lupusec.get_devices(generic_type=CONST.TYPE_SWITCH):
|
||||
devices = []
|
||||
for device in data.lupusec.get_devices(generic_type=device_types):
|
||||
devices.append(LupusecSwitch(data, device))
|
||||
|
||||
add_entities(devices)
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["yt_dlp"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["yt-dlp==2023.9.24"]
|
||||
"requirements": ["yt-dlp==2023.10.13"]
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ class SlaveSensor(
|
||||
self._attr_unique_id = f"{self._attr_unique_id}_{idx}"
|
||||
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
||||
self._attr_device_class = entry.get(CONF_DEVICE_CLASS)
|
||||
self._attr_available = False
|
||||
super().__init__(coordinator)
|
||||
|
||||
|
@ -63,8 +63,8 @@ PARM_IS_LEGAL = namedtuple(
|
||||
],
|
||||
)
|
||||
# PARM_IS_LEGAL defines if the keywords:
|
||||
# count: ..
|
||||
# structure: ..
|
||||
# count:
|
||||
# structure:
|
||||
# swap: byte
|
||||
# swap: word
|
||||
# swap: word_byte (identical to swap: word)
|
||||
@ -84,7 +84,7 @@ DEFAULT_STRUCT_FORMAT = {
|
||||
DataType.INT64: ENTRY("q", 4, PARM_IS_LEGAL(False, False, True, True, True)),
|
||||
DataType.UINT64: ENTRY("Q", 4, PARM_IS_LEGAL(False, False, True, True, True)),
|
||||
DataType.FLOAT64: ENTRY("d", 4, PARM_IS_LEGAL(False, False, True, True, True)),
|
||||
DataType.STRING: ENTRY("s", -1, PARM_IS_LEGAL(True, False, False, False, False)),
|
||||
DataType.STRING: ENTRY("s", -1, PARM_IS_LEGAL(True, False, False, True, False)),
|
||||
DataType.CUSTOM: ENTRY("?", 0, PARM_IS_LEGAL(True, True, False, False, False)),
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,6 @@ from .mixins import (
|
||||
MqttAvailability,
|
||||
MqttEntity,
|
||||
async_setup_entity_entry_helper,
|
||||
validate_sensor_entity_category,
|
||||
write_state_on_attr_change,
|
||||
)
|
||||
from .models import MqttValueTemplate, ReceiveMessage
|
||||
@ -56,7 +55,7 @@ DEFAULT_PAYLOAD_ON = "ON"
|
||||
DEFAULT_FORCE_UPDATE = False
|
||||
CONF_EXPIRE_AFTER = "expire_after"
|
||||
|
||||
_PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
||||
PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
||||
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
|
||||
@ -68,12 +67,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
|
||||
}
|
||||
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
|
||||
|
||||
DISCOVERY_SCHEMA = vol.All(
|
||||
validate_sensor_entity_category,
|
||||
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA_MODERN = vol.All(validate_sensor_entity_category, _PLATFORM_SCHEMA_BASE)
|
||||
DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyatmo"],
|
||||
"requirements": ["pyatmo==7.5.0"]
|
||||
"requirements": ["pyatmo==7.6.0"]
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from aiohttp import BasicAuth
|
||||
from python_opensky import OpenSky
|
||||
from python_opensky.exceptions import OpenSkyUnauthenticatedError
|
||||
from python_opensky.exceptions import OpenSkyError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
@ -28,7 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
),
|
||||
contributing_user=entry.options.get(CONF_CONTRIBUTING_USER, False),
|
||||
)
|
||||
except OpenSkyUnauthenticatedError as exc:
|
||||
except OpenSkyError as exc:
|
||||
raise ConfigEntryNotReady from exc
|
||||
|
||||
coordinator = OpenSkyDataUpdateCoordinator(hass, client)
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/private_ble_device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluetooth-data-tools==1.13.0"]
|
||||
"requirements": ["bluetooth-data-tools==1.14.0"]
|
||||
}
|
||||
|
@ -113,7 +113,9 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
raise AbortFlow("already_configured")
|
||||
|
||||
# check if the camera is reachable at the new IP
|
||||
host = ReolinkHost(self.hass, existing_entry.data, existing_entry.options)
|
||||
new_config = dict(existing_entry.data)
|
||||
new_config[CONF_HOST] = discovery_info.ip
|
||||
host = ReolinkHost(self.hass, new_config, existing_entry.options)
|
||||
try:
|
||||
await host.api.get_state("GetLocalLink")
|
||||
await host.api.logout()
|
||||
|
@ -211,7 +211,9 @@ async def _async_create_bridge_with_updated_data(
|
||||
partial(getmac.get_mac_address, ip=host)
|
||||
)
|
||||
|
||||
if mac:
|
||||
if mac and mac != "none":
|
||||
# Samsung sometimes returns a value of "none" for the mac address
|
||||
# this should be ignored
|
||||
LOGGER.info("Updated mac to %s for %s", mac, host)
|
||||
updated_data[CONF_MAC] = mac
|
||||
else:
|
||||
|
@ -219,7 +219,10 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._title = f"{self._name} ({self._model})"
|
||||
self._udn = _strip_uuid(dev_info.get("udn", info["id"]))
|
||||
if mac := mac_from_device_info(info):
|
||||
self._mac = mac
|
||||
# Samsung sometimes returns a value of "none" for the mac address
|
||||
# this should be ignored - but also shouldn't trigger getmac
|
||||
if mac != "none":
|
||||
self._mac = mac
|
||||
elif mac := await self.hass.async_add_executor_job(
|
||||
partial(getmac.get_mac_address, ip=self._host)
|
||||
):
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["smarttub"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-smarttub==0.0.33"]
|
||||
"requirements": ["python-smarttub==0.0.35"]
|
||||
}
|
||||
|
@ -89,10 +89,14 @@ class SmartTubSensor(SmartTubSensorBase, SensorEntity):
|
||||
"""Generic class for SmartTub status sensors."""
|
||||
|
||||
@property
|
||||
def native_value(self) -> str:
|
||||
def native_value(self) -> str | None:
|
||||
"""Return the current state of the sensor."""
|
||||
if self._state is None:
|
||||
return None
|
||||
|
||||
if isinstance(self._state, Enum):
|
||||
return self._state.name.lower()
|
||||
|
||||
return self._state.lower()
|
||||
|
||||
|
||||
|
@ -32,5 +32,5 @@ async def async_get_config_entry_diagnostics(
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: TailscaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
# Round-trip via JSON to trigger serialization
|
||||
devices = [json.loads(device.json()) for device in coordinator.data.values()]
|
||||
devices = [json.loads(device.to_json()) for device in coordinator.data.values()]
|
||||
return async_redact_data({"devices": devices}, TO_REDACT)
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["tailscale==0.2.0"]
|
||||
"requirements": ["tailscale==0.6.0"]
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up TPLink from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
try:
|
||||
device: SmartDevice = await Discover.discover_single(host)
|
||||
device: SmartDevice = await Discover.discover_single(host, timeout=10)
|
||||
except SmartDeviceException as ex:
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
|
@ -240,7 +240,7 @@ class TractiveClient:
|
||||
self._config_entry.data[CONF_EMAIL],
|
||||
)
|
||||
return
|
||||
except KeyError as error:
|
||||
except (KeyError, TypeError) as error:
|
||||
_LOGGER.error("Error while listening for events: %s", error)
|
||||
continue
|
||||
except aiotractive.exceptions.TractiveError:
|
||||
@ -284,11 +284,16 @@ class TractiveClient:
|
||||
)
|
||||
|
||||
def _send_wellness_update(self, event: dict[str, Any]) -> None:
|
||||
sleep_day = None
|
||||
sleep_night = None
|
||||
if isinstance(event["sleep"], dict):
|
||||
sleep_day = event["sleep"]["minutes_day_sleep"]
|
||||
sleep_night = event["sleep"]["minutes_night_sleep"]
|
||||
payload = {
|
||||
ATTR_ACTIVITY_LABEL: event["wellness"].get("activity_label"),
|
||||
ATTR_CALORIES: event["activity"]["calories"],
|
||||
ATTR_MINUTES_DAY_SLEEP: event["sleep"]["minutes_day_sleep"],
|
||||
ATTR_MINUTES_NIGHT_SLEEP: event["sleep"]["minutes_night_sleep"],
|
||||
ATTR_MINUTES_DAY_SLEEP: sleep_day,
|
||||
ATTR_MINUTES_NIGHT_SLEEP: sleep_night,
|
||||
ATTR_MINUTES_REST: event["activity"]["minutes_rest"],
|
||||
ATTR_SLEEP_LABEL: event["wellness"].get("sleep_label"),
|
||||
}
|
||||
|
@ -71,7 +71,8 @@ class WeatherFlowSensorEntityDescription(
|
||||
|
||||
def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
|
||||
"""Return the parsed sensor value."""
|
||||
raw_sensor_data = getattr(device, self.key)
|
||||
if (raw_sensor_data := getattr(device, self.key)) is None:
|
||||
return None
|
||||
return self.raw_data_conv_fn(raw_sensor_data)
|
||||
|
||||
|
||||
@ -371,14 +372,17 @@ class WeatherFlowSensorEntity(SensorEntity):
|
||||
return self.device.last_report
|
||||
return None
|
||||
|
||||
@property
|
||||
def native_value(self) -> datetime | StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.get_native_value(self.device)
|
||||
def _async_update_state(self) -> None:
|
||||
"""Update entity state."""
|
||||
value = self.entity_description.get_native_value(self.device)
|
||||
self._attr_available = value is not None
|
||||
self._attr_native_value = value
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to events."""
|
||||
self._async_update_state()
|
||||
for event in self.entity_description.event_subscriptions:
|
||||
self.async_on_remove(
|
||||
self.device.on(event, lambda _: self.async_write_ha_state())
|
||||
self.device.on(event, lambda _: self._async_update_state())
|
||||
)
|
||||
|
@ -37,11 +37,15 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[_T]):
|
||||
_default_update_interval: timedelta | None = UPDATE_INTERVAL
|
||||
_last_valid_update: datetime | None = None
|
||||
webhooks_connected: bool = False
|
||||
coordinator_name: str = ""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||
"""Initialize the Withings data coordinator."""
|
||||
super().__init__(
|
||||
hass, LOGGER, name="Withings", update_interval=self._default_update_interval
|
||||
hass,
|
||||
LOGGER,
|
||||
name=f"Withings {self.coordinator_name}",
|
||||
update_interval=self._default_update_interval,
|
||||
)
|
||||
self._client = client
|
||||
self.notification_categories: set[NotificationCategory] = set()
|
||||
@ -77,6 +81,8 @@ class WithingsMeasurementDataUpdateCoordinator(
|
||||
):
|
||||
"""Withings measurement coordinator."""
|
||||
|
||||
coordinator_name: str = "measurements"
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||
"""Initialize the Withings data coordinator."""
|
||||
super().__init__(hass, client)
|
||||
@ -109,6 +115,8 @@ class WithingsSleepDataUpdateCoordinator(
|
||||
):
|
||||
"""Withings sleep coordinator."""
|
||||
|
||||
coordinator_name: str = "sleep"
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||
"""Initialize the Withings data coordinator."""
|
||||
super().__init__(hass, client)
|
||||
@ -147,12 +155,16 @@ class WithingsSleepDataUpdateCoordinator(
|
||||
)
|
||||
if not response:
|
||||
return None
|
||||
return response[0]
|
||||
|
||||
return sorted(
|
||||
response, key=lambda sleep_summary: sleep_summary.end_date, reverse=True
|
||||
)[0]
|
||||
|
||||
|
||||
class WithingsBedPresenceDataUpdateCoordinator(WithingsDataUpdateCoordinator[None]):
|
||||
"""Withings bed presence coordinator."""
|
||||
|
||||
coordinator_name: str = "bed presence"
|
||||
in_bed: bool | None = None
|
||||
_default_update_interval = None
|
||||
|
||||
@ -178,6 +190,7 @@ class WithingsBedPresenceDataUpdateCoordinator(WithingsDataUpdateCoordinator[Non
|
||||
class WithingsGoalsDataUpdateCoordinator(WithingsDataUpdateCoordinator[Goals]):
|
||||
"""Withings goals coordinator."""
|
||||
|
||||
coordinator_name: str = "goals"
|
||||
_default_update_interval = timedelta(hours=1)
|
||||
|
||||
def webhook_subscription_listener(self, connected: bool) -> None:
|
||||
@ -194,6 +207,7 @@ class WithingsActivityDataUpdateCoordinator(
|
||||
):
|
||||
"""Withings activity coordinator."""
|
||||
|
||||
coordinator_name: str = "activity"
|
||||
_previous_data: Activity | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||
@ -232,6 +246,7 @@ class WithingsWorkoutDataUpdateCoordinator(
|
||||
):
|
||||
"""Withings workout coordinator."""
|
||||
|
||||
coordinator_name: str = "workout"
|
||||
_previous_data: Workout | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||
|
@ -7,7 +7,7 @@ from typing import Final
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 11
|
||||
PATCH_VERSION: Final = "1"
|
||||
PATCH_VERSION: Final = "2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
@ -12,7 +12,7 @@ bleak-retry-connector==3.3.0
|
||||
bleak==0.21.1
|
||||
bluetooth-adapters==0.16.1
|
||||
bluetooth-auto-recovery==1.2.3
|
||||
bluetooth-data-tools==1.13.0
|
||||
bluetooth-data-tools==1.14.0
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.3.0
|
||||
cryptography==41.0.4
|
||||
@ -22,7 +22,7 @@ ha-av==10.1.1
|
||||
hass-nabucasa==0.74.0
|
||||
hassil==1.2.5
|
||||
home-assistant-bluetooth==1.10.4
|
||||
home-assistant-frontend==20231030.1
|
||||
home-assistant-frontend==20231030.2
|
||||
home-assistant-intents==2023.10.16
|
||||
httpx==0.25.0
|
||||
ifaddr==0.2.0
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.11.1"
|
||||
version = "2023.11.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -192,7 +192,7 @@ aio-georss-gdacs==0.8
|
||||
aioairq==0.2.4
|
||||
|
||||
# homeassistant.components.airzone_cloud
|
||||
aioairzone-cloud==0.3.1
|
||||
aioairzone-cloud==0.3.5
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.6.9
|
||||
@ -237,7 +237,7 @@ aioecowitt==2023.5.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==18.1.0
|
||||
aioesphomeapi==18.2.4
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@ -539,7 +539,7 @@ bleak==0.21.1
|
||||
blebox-uniapi==2.2.0
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.22.2
|
||||
blinkpy==0.22.3
|
||||
|
||||
# homeassistant.components.bitcoin
|
||||
blockchain==1.4.4
|
||||
@ -562,7 +562,7 @@ bluetooth-auto-recovery==1.2.3
|
||||
# homeassistant.components.ld2410_ble
|
||||
# homeassistant.components.led_ble
|
||||
# homeassistant.components.private_ble_device
|
||||
bluetooth-data-tools==1.13.0
|
||||
bluetooth-data-tools==1.14.0
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.2.1
|
||||
@ -701,7 +701,7 @@ dovado==0.4.1
|
||||
dremel3dpy==2.1.1
|
||||
|
||||
# homeassistant.components.dsmr
|
||||
dsmr-parser==1.3.0
|
||||
dsmr-parser==1.3.1
|
||||
|
||||
# homeassistant.components.dwd_weather_warnings
|
||||
dwdwfsapi==1.0.6
|
||||
@ -1007,7 +1007,7 @@ hole==0.8.0
|
||||
holidays==0.35
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20231030.1
|
||||
home-assistant-frontend==20231030.2
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.10.16
|
||||
@ -1094,6 +1094,9 @@ janus==1.0.0
|
||||
# homeassistant.components.abode
|
||||
jaraco.abode==3.3.0
|
||||
|
||||
# homeassistant.components.abode
|
||||
jaraco.functools==3.9.0
|
||||
|
||||
# homeassistant.components.jellyfin
|
||||
jellyfin-apiclient-python==1.9.2
|
||||
|
||||
@ -1597,7 +1600,7 @@ pyairvisual==2023.08.1
|
||||
pyatag==0.3.5.3
|
||||
|
||||
# homeassistant.components.netatmo
|
||||
pyatmo==7.5.0
|
||||
pyatmo==7.6.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.14.3
|
||||
@ -1693,7 +1696,7 @@ pyedimax==0.2.1
|
||||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.14.1
|
||||
pyenphase==1.14.2
|
||||
|
||||
# homeassistant.components.envisalink
|
||||
pyenvisalink==4.6
|
||||
@ -2184,7 +2187,7 @@ python-ripple-api==0.0.3
|
||||
python-roborock==0.35.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
python-smarttub==0.0.35
|
||||
|
||||
# homeassistant.components.songpal
|
||||
python-songpal==0.15.2
|
||||
@ -2533,7 +2536,7 @@ synology-srm==0.2.0
|
||||
systembridgeconnector==3.8.4
|
||||
|
||||
# homeassistant.components.tailscale
|
||||
tailscale==0.2.0
|
||||
tailscale==0.6.0
|
||||
|
||||
# homeassistant.components.tank_utility
|
||||
tank-utility==1.5.0
|
||||
@ -2779,7 +2782,7 @@ youless-api==1.0.1
|
||||
youtubeaio==1.1.5
|
||||
|
||||
# homeassistant.components.media_extractor
|
||||
yt-dlp==2023.9.24
|
||||
yt-dlp==2023.10.13
|
||||
|
||||
# homeassistant.components.zamg
|
||||
zamg==0.3.0
|
||||
|
@ -173,7 +173,7 @@ aio-georss-gdacs==0.8
|
||||
aioairq==0.2.4
|
||||
|
||||
# homeassistant.components.airzone_cloud
|
||||
aioairzone-cloud==0.3.1
|
||||
aioairzone-cloud==0.3.5
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.6.9
|
||||
@ -218,7 +218,7 @@ aioecowitt==2023.5.0
|
||||
aioemonitor==1.0.5
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==18.1.0
|
||||
aioesphomeapi==18.2.4
|
||||
|
||||
# homeassistant.components.flo
|
||||
aioflo==2021.11.0
|
||||
@ -460,7 +460,7 @@ bleak==0.21.1
|
||||
blebox-uniapi==2.2.0
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.22.2
|
||||
blinkpy==0.22.3
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.2.3
|
||||
@ -476,7 +476,7 @@ bluetooth-auto-recovery==1.2.3
|
||||
# homeassistant.components.ld2410_ble
|
||||
# homeassistant.components.led_ble
|
||||
# homeassistant.components.private_ble_device
|
||||
bluetooth-data-tools==1.13.0
|
||||
bluetooth-data-tools==1.14.0
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.2.1
|
||||
@ -572,7 +572,7 @@ discovery30303==0.2.1
|
||||
dremel3dpy==2.1.1
|
||||
|
||||
# homeassistant.components.dsmr
|
||||
dsmr-parser==1.3.0
|
||||
dsmr-parser==1.3.1
|
||||
|
||||
# homeassistant.components.dwd_weather_warnings
|
||||
dwdwfsapi==1.0.6
|
||||
@ -796,7 +796,7 @@ hole==0.8.0
|
||||
holidays==0.35
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20231030.1
|
||||
home-assistant-frontend==20231030.2
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.10.16
|
||||
@ -862,6 +862,9 @@ janus==1.0.0
|
||||
# homeassistant.components.abode
|
||||
jaraco.abode==3.3.0
|
||||
|
||||
# homeassistant.components.abode
|
||||
jaraco.functools==3.9.0
|
||||
|
||||
# homeassistant.components.jellyfin
|
||||
jellyfin-apiclient-python==1.9.2
|
||||
|
||||
@ -1215,7 +1218,7 @@ pyairvisual==2023.08.1
|
||||
pyatag==0.3.5.3
|
||||
|
||||
# homeassistant.components.netatmo
|
||||
pyatmo==7.5.0
|
||||
pyatmo==7.6.0
|
||||
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.14.3
|
||||
@ -1275,7 +1278,7 @@ pyeconet==0.1.22
|
||||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.14.1
|
||||
pyenphase==1.14.2
|
||||
|
||||
# homeassistant.components.everlights
|
||||
pyeverlights==0.1.0
|
||||
@ -1628,7 +1631,7 @@ python-qbittorrent==0.4.3
|
||||
python-roborock==0.35.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.33
|
||||
python-smarttub==0.0.35
|
||||
|
||||
# homeassistant.components.songpal
|
||||
python-songpal==0.15.2
|
||||
@ -1887,7 +1890,7 @@ switchbot-api==1.2.1
|
||||
systembridgeconnector==3.8.4
|
||||
|
||||
# homeassistant.components.tailscale
|
||||
tailscale==0.2.0
|
||||
tailscale==0.6.0
|
||||
|
||||
# homeassistant.components.tellduslive
|
||||
tellduslive==0.10.11
|
||||
@ -2076,7 +2079,7 @@ youless-api==1.0.1
|
||||
youtubeaio==1.1.5
|
||||
|
||||
# homeassistant.components.media_extractor
|
||||
yt-dlp==2023.9.24
|
||||
yt-dlp==2023.10.13
|
||||
|
||||
# homeassistant.components.zamg
|
||||
zamg==0.3.0
|
||||
|
@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch
|
||||
from dsmr_parser.clients.protocol import DSMRProtocol
|
||||
from dsmr_parser.clients.rfxtrx_protocol import RFXtrxDSMRProtocol
|
||||
from dsmr_parser.obis_references import (
|
||||
BELGIUM_EQUIPMENT_IDENTIFIER,
|
||||
EQUIPMENT_IDENTIFIER,
|
||||
EQUIPMENT_IDENTIFIER_GAS,
|
||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER,
|
||||
@ -81,6 +82,15 @@ async def dsmr_connection_send_validate_fixture(hass):
|
||||
|
||||
async def connection_factory(*args, **kwargs):
|
||||
"""Return mocked out Asyncio classes."""
|
||||
if args[1] == "5B":
|
||||
protocol.telegram = {
|
||||
BELGIUM_EQUIPMENT_IDENTIFIER: CosemObject(
|
||||
BELGIUM_EQUIPMENT_IDENTIFIER, [{"value": "12345678", "unit": ""}]
|
||||
),
|
||||
EQUIPMENT_IDENTIFIER_GAS: CosemObject(
|
||||
EQUIPMENT_IDENTIFIER_GAS, [{"value": "123456789", "unit": ""}]
|
||||
),
|
||||
}
|
||||
if args[1] == "5L":
|
||||
protocol.telegram = {
|
||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemObject(
|
||||
|
@ -215,6 +215,50 @@ async def test_setup_serial_rfxtrx(
|
||||
assert result["data"] == {**entry_data, **SERIAL_DATA}
|
||||
|
||||
|
||||
@patch("serial.tools.list_ports.comports", return_value=[com_port()])
|
||||
async def test_setup_5B(
|
||||
com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture
|
||||
) -> None:
|
||||
"""Test we can setup serial."""
|
||||
port = com_port()
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] is None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"type": "Serial"},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "setup_serial"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"port": port.device, "dsmr_version": "5B"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry_data = {
|
||||
"port": port.device,
|
||||
"dsmr_version": "5B",
|
||||
"protocol": "dsmr_protocol",
|
||||
"serial_id": "12345678",
|
||||
"serial_id_gas": "123456789",
|
||||
}
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == port.device
|
||||
assert result["data"] == entry_data
|
||||
|
||||
|
||||
@patch("serial.tools.list_ports.comports", return_value=[com_port()])
|
||||
async def test_setup_5L(
|
||||
com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture
|
||||
|
@ -8,10 +8,22 @@ import asyncio
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from itertools import chain, repeat
|
||||
from typing import Literal
|
||||
from unittest.mock import DEFAULT, MagicMock
|
||||
|
||||
from dsmr_parser.obis_references import (
|
||||
BELGIUM_MBUS1_METER_READING1,
|
||||
BELGIUM_MBUS1_METER_READING2,
|
||||
BELGIUM_MBUS2_METER_READING1,
|
||||
BELGIUM_MBUS2_METER_READING2,
|
||||
BELGIUM_MBUS3_METER_READING1,
|
||||
BELGIUM_MBUS3_METER_READING2,
|
||||
BELGIUM_MBUS4_METER_READING1,
|
||||
BELGIUM_MBUS4_METER_READING2,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.dsmr.const import BELGIUM_5MIN_GAS_METER_READING
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_OPTIONS,
|
||||
ATTR_STATE_CLASS,
|
||||
@ -483,6 +495,10 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
|
||||
from dsmr_parser.obis_references import (
|
||||
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
||||
BELGIUM_MAXIMUM_DEMAND_MONTH,
|
||||
BELGIUM_MBUS1_METER_READING2,
|
||||
BELGIUM_MBUS2_METER_READING2,
|
||||
BELGIUM_MBUS3_METER_READING2,
|
||||
BELGIUM_MBUS4_METER_READING2,
|
||||
ELECTRICITY_ACTIVE_TARIFF,
|
||||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject
|
||||
@ -500,13 +516,34 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
|
||||
}
|
||||
|
||||
telegram = {
|
||||
BELGIUM_5MIN_GAS_METER_READING: MBusObject(
|
||||
BELGIUM_5MIN_GAS_METER_READING,
|
||||
BELGIUM_MBUS1_METER_READING2: MBusObject(
|
||||
BELGIUM_MBUS1_METER_READING2,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||
{"value": Decimal(745.695), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
BELGIUM_MBUS2_METER_READING2: MBusObject(
|
||||
BELGIUM_MBUS2_METER_READING2,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642214)},
|
||||
{"value": Decimal(745.696), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
BELGIUM_MBUS3_METER_READING2: MBusObject(
|
||||
BELGIUM_MBUS3_METER_READING2,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642215)},
|
||||
{"value": Decimal(745.697), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
BELGIUM_MBUS4_METER_READING2: MBusObject(
|
||||
BELGIUM_MBUS4_METER_READING2,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642216)},
|
||||
{"value": Decimal(745.698), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
BELGIUM_CURRENT_AVERAGE_DEMAND: CosemObject(
|
||||
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
||||
[{"value": Decimal(1.75), "unit": "kW"}],
|
||||
@ -577,6 +614,115 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key1", "key2", "key3", "gas_value"),
|
||||
[
|
||||
(
|
||||
BELGIUM_MBUS1_METER_READING1,
|
||||
BELGIUM_MBUS2_METER_READING2,
|
||||
BELGIUM_MBUS3_METER_READING1,
|
||||
"745.696",
|
||||
),
|
||||
(
|
||||
BELGIUM_MBUS1_METER_READING2,
|
||||
BELGIUM_MBUS2_METER_READING1,
|
||||
BELGIUM_MBUS3_METER_READING2,
|
||||
"745.695",
|
||||
),
|
||||
(
|
||||
BELGIUM_MBUS4_METER_READING2,
|
||||
BELGIUM_MBUS2_METER_READING1,
|
||||
BELGIUM_MBUS3_METER_READING1,
|
||||
"745.695",
|
||||
),
|
||||
(
|
||||
BELGIUM_MBUS4_METER_READING1,
|
||||
BELGIUM_MBUS2_METER_READING1,
|
||||
BELGIUM_MBUS3_METER_READING2,
|
||||
"745.697",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_belgian_meter_alt(
|
||||
hass: HomeAssistant,
|
||||
dsmr_connection_fixture,
|
||||
key1: Literal,
|
||||
key2: Literal,
|
||||
key3: Literal,
|
||||
gas_value: str,
|
||||
) -> None:
|
||||
"""Test if Belgian meter is correctly parsed."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
||||
from dsmr_parser.objects import MBusObject
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "5B",
|
||||
"precision": 4,
|
||||
"reconnect_interval": 30,
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
entry_options = {
|
||||
"time_between_update": 0,
|
||||
}
|
||||
|
||||
telegram = {
|
||||
key1: MBusObject(
|
||||
key1,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||
{"value": Decimal(745.695), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
key2: MBusObject(
|
||||
key2,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642214)},
|
||||
{"value": Decimal(745.696), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
key3: MBusObject(
|
||||
key3,
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642215)},
|
||||
{"value": Decimal(745.697), "unit": "m3"},
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
|
||||
telegram_callback(telegram)
|
||||
|
||||
# after receiving telegram entities need to have the chance to be created
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# check if gas consumption is parsed correctly
|
||||
gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption")
|
||||
assert gas_consumption.state == gas_value
|
||||
assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.GAS
|
||||
assert (
|
||||
gas_consumption.attributes.get(ATTR_STATE_CLASS)
|
||||
== SensorStateClass.TOTAL_INCREASING
|
||||
)
|
||||
assert (
|
||||
gas_consumption.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
== UnitOfVolume.CUBIC_METERS
|
||||
)
|
||||
|
||||
|
||||
async def test_belgian_meter_low(hass: HomeAssistant, dsmr_connection_fixture) -> None:
|
||||
"""Test if Belgian meter is correctly parsed."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
@ -77,6 +77,17 @@ def mock_config_entry(hass) -> MockConfigEntry:
|
||||
return config_entry
|
||||
|
||||
|
||||
class BaseMockReconnectLogic(ReconnectLogic):
|
||||
"""Mock ReconnectLogic."""
|
||||
|
||||
def stop_callback(self) -> None:
|
||||
"""Stop the reconnect logic."""
|
||||
# For the purposes of testing, we don't want to wait
|
||||
# for the reconnect logic to finish trying to connect
|
||||
self._cancel_connect("forced disconnect from test")
|
||||
self._is_stopped = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_device_info() -> DeviceInfo:
|
||||
"""Return the default mocked device info."""
|
||||
@ -132,7 +143,10 @@ def mock_client(mock_device_info) -> APIClient:
|
||||
mock_client.address = "127.0.0.1"
|
||||
mock_client.api_version = APIVersion(99, 99)
|
||||
|
||||
with patch("homeassistant.components.esphome.APIClient", mock_client), patch(
|
||||
with patch(
|
||||
"homeassistant.components.esphome.manager.ReconnectLogic",
|
||||
BaseMockReconnectLogic,
|
||||
), patch("homeassistant.components.esphome.APIClient", mock_client), patch(
|
||||
"homeassistant.components.esphome.config_flow.APIClient", mock_client
|
||||
):
|
||||
yield mock_client
|
||||
@ -234,7 +248,7 @@ async def _mock_generic_device_entry(
|
||||
|
||||
try_connect_done = Event()
|
||||
|
||||
class MockReconnectLogic(ReconnectLogic):
|
||||
class MockReconnectLogic(BaseMockReconnectLogic):
|
||||
"""Mock ReconnectLogic."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -250,6 +264,13 @@ async def _mock_generic_device_entry(
|
||||
try_connect_done.set()
|
||||
return result
|
||||
|
||||
def stop_callback(self) -> None:
|
||||
"""Stop the reconnect logic."""
|
||||
# For the purposes of testing, we don't want to wait
|
||||
# for the reconnect logic to finish trying to connect
|
||||
self._cancel_connect("forced disconnect from test")
|
||||
self._is_stopped = True
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.esphome.manager.ReconnectLogic", MockReconnectLogic
|
||||
):
|
||||
|
@ -85,6 +85,14 @@ async def test_expose_attribute(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
hass.states.async_set(entity_id, "off", {})
|
||||
await knx.assert_telegram_count(0)
|
||||
|
||||
# Change attribute; keep state
|
||||
hass.states.async_set(entity_id, "on", {attribute: 1})
|
||||
await knx.assert_write("1/1/8", (1,))
|
||||
|
||||
# Change state to "off"; null attribute
|
||||
hass.states.async_set(entity_id, "off", {attribute: None})
|
||||
await knx.assert_telegram_count(0)
|
||||
|
||||
|
||||
async def test_expose_attribute_with_default(
|
||||
hass: HomeAssistant, knx: KNXTestKit
|
||||
@ -132,6 +140,14 @@ async def test_expose_attribute_with_default(
|
||||
hass.states.async_set(entity_id, "off", {})
|
||||
await knx.assert_write("1/1/8", (0,))
|
||||
|
||||
# Change state and attribute
|
||||
hass.states.async_set(entity_id, "on", {attribute: 1})
|
||||
await knx.assert_write("1/1/8", (1,))
|
||||
|
||||
# Change state to "off"; null attribute
|
||||
hass.states.async_set(entity_id, "off", {attribute: None})
|
||||
await knx.assert_write("1/1/8", (0,))
|
||||
|
||||
|
||||
async def test_expose_string(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
"""Test an expose to send string values of up to 14 bytes only."""
|
||||
|
@ -1,10 +1,11 @@
|
||||
"""Test the Litter-Robot time entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import time
|
||||
from datetime import datetime, time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from pylitterbot import LitterRobot3
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.time import DOMAIN as PLATFORM_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
@ -15,6 +16,7 @@ from .conftest import setup_integration
|
||||
SLEEP_START_TIME_ENTITY_ID = "time.test_sleep_mode_start_time"
|
||||
|
||||
|
||||
@pytest.mark.freeze_time(datetime(2023, 7, 1, 12))
|
||||
async def test_sleep_mode_start_time(
|
||||
hass: HomeAssistant, mock_account: MagicMock
|
||||
) -> None:
|
||||
|
@ -513,6 +513,20 @@ async def test_config_wrong_struct_sensor(
|
||||
False,
|
||||
"07-05-2020 14:35",
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_COUNT: 8,
|
||||
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
|
||||
CONF_DATA_TYPE: DataType.STRING,
|
||||
CONF_SWAP: CONF_SWAP_BYTE,
|
||||
CONF_SCALE: 1,
|
||||
CONF_OFFSET: 0,
|
||||
CONF_PRECISION: 0,
|
||||
},
|
||||
[0x3730, 0x302D, 0x2D35, 0x3032, 0x3032, 0x3120, 0x3A34, 0x3533],
|
||||
False,
|
||||
"07-05-2020 14:35",
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_COUNT: 8,
|
||||
|
@ -2163,15 +2163,6 @@ async def test_setup_manual_mqtt_with_invalid_config(
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
mqtt.DOMAIN: {
|
||||
"binary_sensor": {
|
||||
"name": "test",
|
||||
"state_topic": "test-topic",
|
||||
"entity_category": "config",
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
@patch(
|
||||
|
@ -572,6 +572,7 @@
|
||||
'read_smokedetector',
|
||||
'read_station',
|
||||
'read_thermostat',
|
||||
'read_mhs1',
|
||||
'write_bubendorff',
|
||||
'write_camera',
|
||||
'write_magellan',
|
||||
@ -579,6 +580,7 @@
|
||||
'write_presence',
|
||||
'write_smarther',
|
||||
'write_thermostat',
|
||||
'write_mhs1',
|
||||
]),
|
||||
'type': 'Bearer',
|
||||
}),
|
||||
|
@ -34,8 +34,10 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def reolink_connect(mock_get_source_ip: None) -> Generator[MagicMock, None, None]:
|
||||
"""Mock reolink connection."""
|
||||
def reolink_connect_class(
|
||||
mock_get_source_ip: None,
|
||||
) -> Generator[MagicMock, None, None]:
|
||||
"""Mock reolink connection and return both the host_mock and host_mock_class."""
|
||||
with patch(
|
||||
"homeassistant.components.reolink.host.webhook.async_register",
|
||||
return_value=True,
|
||||
@ -65,7 +67,15 @@ def reolink_connect(mock_get_source_ip: None) -> Generator[MagicMock, None, None
|
||||
host_mock.session_active = True
|
||||
host_mock.timeout = 60
|
||||
host_mock.renewtimer.return_value = 600
|
||||
yield host_mock
|
||||
yield host_mock_class
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def reolink_connect(
|
||||
reolink_connect_class: MagicMock,
|
||||
) -> Generator[MagicMock, None, None]:
|
||||
"""Mock reolink connection."""
|
||||
return reolink_connect_class.return_value
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -2,7 +2,7 @@
|
||||
from datetime import timedelta
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, call
|
||||
|
||||
import pytest
|
||||
from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError
|
||||
@ -12,6 +12,7 @@ from homeassistant.components import dhcp
|
||||
from homeassistant.components.reolink import DEVICE_UPDATE_INTERVAL, const
|
||||
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
||||
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
||||
from homeassistant.components.reolink.host import DEFAULT_TIMEOUT
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -380,41 +381,47 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) -> No
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("last_update_success", "attr", "value", "expected"),
|
||||
("last_update_success", "attr", "value", "expected", "host_call_list"),
|
||||
[
|
||||
(
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
TEST_HOST2,
|
||||
[TEST_HOST, TEST_HOST2],
|
||||
),
|
||||
(
|
||||
True,
|
||||
None,
|
||||
None,
|
||||
TEST_HOST,
|
||||
[TEST_HOST],
|
||||
),
|
||||
(
|
||||
False,
|
||||
"get_state",
|
||||
AsyncMock(side_effect=ReolinkError("Test error")),
|
||||
TEST_HOST,
|
||||
[TEST_HOST, TEST_HOST2],
|
||||
),
|
||||
(
|
||||
False,
|
||||
"mac_address",
|
||||
"aa:aa:aa:aa:aa:aa",
|
||||
TEST_HOST,
|
||||
[TEST_HOST, TEST_HOST2],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_dhcp_ip_update(
|
||||
hass: HomeAssistant,
|
||||
reolink_connect_class: MagicMock,
|
||||
reolink_connect: MagicMock,
|
||||
last_update_success: bool,
|
||||
attr: str,
|
||||
value: Any,
|
||||
expected: str,
|
||||
host_call_list: list[str],
|
||||
) -> None:
|
||||
"""Test dhcp discovery aborts if already configured where the IP is updated if appropriate."""
|
||||
config_entry = MockConfigEntry(
|
||||
@ -459,6 +466,22 @@ async def test_dhcp_ip_update(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_data
|
||||
)
|
||||
|
||||
expected_calls = []
|
||||
for host in host_call_list:
|
||||
expected_calls.append(
|
||||
call(
|
||||
host,
|
||||
TEST_USERNAME,
|
||||
TEST_PASSWORD,
|
||||
port=TEST_PORT,
|
||||
use_https=TEST_USE_HTTPS,
|
||||
protocol=DEFAULT_PROTOCOL,
|
||||
timeout=DEFAULT_TIMEOUT,
|
||||
)
|
||||
)
|
||||
|
||||
assert reolink_connect_class.call_args_list == expected_calls
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Tests for Samsung TV config flow."""
|
||||
from copy import deepcopy
|
||||
from ipaddress import ip_address
|
||||
from unittest.mock import ANY, AsyncMock, Mock, call, patch
|
||||
|
||||
@ -165,14 +166,6 @@ MOCK_DEVICE_INFO = {
|
||||
},
|
||||
"id": "123",
|
||||
}
|
||||
MOCK_DEVICE_INFO_2 = {
|
||||
"device": {
|
||||
"type": "Samsung SmartTV",
|
||||
"name": "fake2_name",
|
||||
"modelName": "fake2_model",
|
||||
},
|
||||
"id": "345",
|
||||
}
|
||||
|
||||
AUTODETECT_LEGACY = {
|
||||
"name": "HomeAssistant",
|
||||
@ -1968,3 +1961,56 @@ async def test_no_update_incorrect_udn_not_matching_mac_from_dhcp(
|
||||
assert result["step_id"] == "confirm"
|
||||
assert entry.data[CONF_MAC] == "aa:bb:ss:ss:dd:pp"
|
||||
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("remotews", "remoteencws_failing")
|
||||
async def test_ssdp_update_mac(hass: HomeAssistant) -> None:
|
||||
"""Ensure that MAC address is correctly updated from SSDP."""
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info",
|
||||
return_value=MOCK_DEVICE_INFO,
|
||||
):
|
||||
# entry was added
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA
|
||||
)
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
entry = result["result"]
|
||||
assert entry.data[CONF_MANUFACTURER] == DEFAULT_MANUFACTURER
|
||||
assert entry.data[CONF_MODEL] == "fake_model"
|
||||
assert entry.data[CONF_MAC] is None
|
||||
assert entry.unique_id == "123"
|
||||
|
||||
device_info = deepcopy(MOCK_DEVICE_INFO)
|
||||
device_info["device"]["wifiMac"] = "none"
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info",
|
||||
return_value=device_info,
|
||||
):
|
||||
# Updated
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == RESULT_ALREADY_CONFIGURED
|
||||
|
||||
# ensure mac wasn't updated with "none"
|
||||
assert entry.data[CONF_MAC] is None
|
||||
assert entry.unique_id == "123"
|
||||
|
||||
device_info = deepcopy(MOCK_DEVICE_INFO)
|
||||
device_info["device"]["wifiMac"] = "aa:bb:cc:dd:ee:ff"
|
||||
with patch(
|
||||
"homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info",
|
||||
return_value=device_info,
|
||||
):
|
||||
# Updated
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=MOCK_SSDP_DATA
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == RESULT_ALREADY_CONFIGURED
|
||||
|
||||
# ensure mac was updated with new wifiMac value
|
||||
assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
|
||||
assert entry.unique_id == "123"
|
||||
|
@ -2,6 +2,7 @@
|
||||
import pytest
|
||||
import smarttub
|
||||
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
@ -27,6 +28,27 @@ async def test_sensor(
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
# https://github.com/home-assistant/core/issues/102339
|
||||
async def test_null_blowoutcycle(
|
||||
spa,
|
||||
spa_state,
|
||||
config_entry,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test blowoutCycle having null value."""
|
||||
|
||||
spa_state.blowout_cycle = None
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = f"sensor.{spa.brand}_{spa.model}_blowout_cycle"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_primary_filtration(
|
||||
spa, spa_state, setup_entry, hass: HomeAssistant
|
||||
) -> None:
|
||||
|
@ -41,7 +41,7 @@ def mock_tailscale_config_flow() -> Generator[None, MagicMock, None]:
|
||||
"homeassistant.components.tailscale.config_flow.Tailscale", autospec=True
|
||||
) as tailscale_mock:
|
||||
tailscale = tailscale_mock.return_value
|
||||
tailscale.devices.return_value = Devices.parse_raw(
|
||||
tailscale.devices.return_value = Devices.from_json(
|
||||
load_fixture("tailscale/devices.json")
|
||||
).devices
|
||||
yield tailscale
|
||||
@ -54,7 +54,7 @@ def mock_tailscale(request: pytest.FixtureRequest) -> Generator[None, MagicMock,
|
||||
if hasattr(request, "param") and request.param:
|
||||
fixture = request.param
|
||||
|
||||
devices = Devices.parse_raw(load_fixture(fixture)).devices
|
||||
devices = Devices.from_json(load_fixture(fixture)).devices
|
||||
with patch(
|
||||
"homeassistant.components.tailscale.coordinator.Tailscale", autospec=True
|
||||
) as tailscale_mock:
|
||||
|
87
tests/components/tailscale/snapshots/test_diagnostics.ambr
Normal file
87
tests/components/tailscale/snapshots/test_diagnostics.ambr
Normal file
@ -0,0 +1,87 @@
|
||||
# serializer version: 1
|
||||
# name: test_diagnostics
|
||||
dict({
|
||||
'devices': list([
|
||||
dict({
|
||||
'addresses': '**REDACTED**',
|
||||
'advertised_routes': list([
|
||||
]),
|
||||
'authorized': True,
|
||||
'blocks_incoming_connections': False,
|
||||
'client_connectivity': dict({
|
||||
'client_supports': dict({
|
||||
'hair_pinning': False,
|
||||
'ipv6': False,
|
||||
'pcp': False,
|
||||
'pmp': False,
|
||||
'udp': True,
|
||||
'upnp': False,
|
||||
}),
|
||||
'endpoints': '**REDACTED**',
|
||||
'mapping_varies_by_dest_ip': False,
|
||||
}),
|
||||
'client_version': '1.12.3-td91ea7286-ge1bbbd90c',
|
||||
'created': '2021-08-19T09:25:22+00:00',
|
||||
'device_id': '**REDACTED**',
|
||||
'enabled_routes': list([
|
||||
]),
|
||||
'expires': '2022-02-15T09:25:22+00:00',
|
||||
'hostname': '**REDACTED**',
|
||||
'is_external': False,
|
||||
'key_expiry_disabled': False,
|
||||
'last_seen': '2021-09-16T06:11:23+00:00',
|
||||
'machine_key': '**REDACTED**',
|
||||
'name': '**REDACTED**',
|
||||
'node_key': '**REDACTED**',
|
||||
'os': 'iOS',
|
||||
'tags': list([
|
||||
]),
|
||||
'update_available': True,
|
||||
'user': '**REDACTED**',
|
||||
}),
|
||||
dict({
|
||||
'addresses': '**REDACTED**',
|
||||
'advertised_routes': list([
|
||||
'0.0.0.0/0',
|
||||
'10.10.10.0/23',
|
||||
'::/0',
|
||||
]),
|
||||
'authorized': True,
|
||||
'blocks_incoming_connections': False,
|
||||
'client_connectivity': dict({
|
||||
'client_supports': dict({
|
||||
'hair_pinning': True,
|
||||
'ipv6': False,
|
||||
'pcp': False,
|
||||
'pmp': False,
|
||||
'udp': True,
|
||||
'upnp': False,
|
||||
}),
|
||||
'endpoints': '**REDACTED**',
|
||||
'mapping_varies_by_dest_ip': False,
|
||||
}),
|
||||
'client_version': '1.14.0-t5cff36945-g809e87bba',
|
||||
'created': '2021-08-29T09:49:06+00:00',
|
||||
'device_id': '**REDACTED**',
|
||||
'enabled_routes': list([
|
||||
'0.0.0.0/0',
|
||||
'10.10.10.0/23',
|
||||
'::/0',
|
||||
]),
|
||||
'expires': '2022-02-25T09:49:06+00:00',
|
||||
'hostname': '**REDACTED**',
|
||||
'is_external': False,
|
||||
'key_expiry_disabled': False,
|
||||
'last_seen': '2021-11-15T20:37:03+00:00',
|
||||
'machine_key': '**REDACTED**',
|
||||
'name': '**REDACTED**',
|
||||
'node_key': '**REDACTED**',
|
||||
'os': 'linux',
|
||||
'tags': list([
|
||||
]),
|
||||
'update_available': True,
|
||||
'user': '**REDACTED**',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
@ -1,6 +1,6 @@
|
||||
"""Tests for the diagnostics data provided by the Tailscale integration."""
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.diagnostics import REDACTED
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@ -12,93 +12,10 @@ async def test_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
init_integration: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test diagnostics."""
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, init_integration
|
||||
) == {
|
||||
"devices": [
|
||||
{
|
||||
"addresses": REDACTED,
|
||||
"device_id": REDACTED,
|
||||
"user": REDACTED,
|
||||
"name": REDACTED,
|
||||
"hostname": REDACTED,
|
||||
"client_version": "1.12.3-td91ea7286-ge1bbbd90c",
|
||||
"update_available": True,
|
||||
"os": "iOS",
|
||||
"created": "2021-08-19T09:25:22+00:00",
|
||||
"last_seen": "2021-09-16T06:11:23+00:00",
|
||||
"key_expiry_disabled": False,
|
||||
"expires": "2022-02-15T09:25:22+00:00",
|
||||
"authorized": True,
|
||||
"is_external": False,
|
||||
"machine_key": REDACTED,
|
||||
"node_key": REDACTED,
|
||||
"blocks_incoming_connections": False,
|
||||
"enabled_routes": [],
|
||||
"advertised_routes": [],
|
||||
"client_connectivity": {
|
||||
"endpoints": REDACTED,
|
||||
"derp": "",
|
||||
"mapping_varies_by_dest_ip": False,
|
||||
"latency": {},
|
||||
"client_supports": {
|
||||
"hair_pinning": False,
|
||||
"ipv6": False,
|
||||
"pcp": False,
|
||||
"pmp": False,
|
||||
"udp": True,
|
||||
"upnp": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"addresses": REDACTED,
|
||||
"device_id": REDACTED,
|
||||
"user": REDACTED,
|
||||
"name": REDACTED,
|
||||
"hostname": REDACTED,
|
||||
"client_version": "1.14.0-t5cff36945-g809e87bba",
|
||||
"update_available": True,
|
||||
"os": "linux",
|
||||
"created": "2021-08-29T09:49:06+00:00",
|
||||
"last_seen": "2021-11-15T20:37:03+00:00",
|
||||
"key_expiry_disabled": False,
|
||||
"expires": "2022-02-25T09:49:06+00:00",
|
||||
"authorized": True,
|
||||
"is_external": False,
|
||||
"machine_key": REDACTED,
|
||||
"node_key": REDACTED,
|
||||
"blocks_incoming_connections": False,
|
||||
"enabled_routes": ["0.0.0.0/0", "10.10.10.0/23", "::/0"],
|
||||
"advertised_routes": ["0.0.0.0/0", "10.10.10.0/23", "::/0"],
|
||||
"client_connectivity": {
|
||||
"endpoints": REDACTED,
|
||||
"derp": "",
|
||||
"mapping_varies_by_dest_ip": False,
|
||||
"latency": {
|
||||
"Bangalore": {"latencyMs": 143.42505599999998},
|
||||
"Chicago": {"latencyMs": 101.123646},
|
||||
"Dallas": {"latencyMs": 136.85886},
|
||||
"Frankfurt": {"latencyMs": 18.968314},
|
||||
"London": {"preferred": True, "latencyMs": 14.314574},
|
||||
"New York City": {"latencyMs": 83.078912},
|
||||
"San Francisco": {"latencyMs": 148.215522},
|
||||
"Seattle": {"latencyMs": 181.553595},
|
||||
"Singapore": {"latencyMs": 164.566539},
|
||||
"São Paulo": {"latencyMs": 207.250179},
|
||||
"Tokyo": {"latencyMs": 226.90714300000002},
|
||||
},
|
||||
"client_supports": {
|
||||
"hair_pinning": True,
|
||||
"ipv6": False,
|
||||
"pcp": False,
|
||||
"pmp": False,
|
||||
"udp": True,
|
||||
"upnp": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
assert (
|
||||
await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
|
||||
== snapshot
|
||||
)
|
||||
|
@ -202,7 +202,7 @@ def _patch_discovery(device=None, no_device=False):
|
||||
|
||||
|
||||
def _patch_single_discovery(device=None, no_device=False):
|
||||
async def _discover_single(*_):
|
||||
async def _discover_single(*args, **kwargs):
|
||||
if no_device:
|
||||
raise SmartDeviceException
|
||||
return device if device else _mocked_bulb()
|
||||
|
@ -1,43 +1,4 @@
|
||||
[
|
||||
{
|
||||
"id": 2081804182,
|
||||
"timezone": "Europe/Paris",
|
||||
"model": 32,
|
||||
"model_id": 63,
|
||||
"hash_deviceid": "201d0b9a0556d6b755166b2cf8d22d3bdf0487ee",
|
||||
"startdate": 1618691453,
|
||||
"enddate": 1618713173,
|
||||
"date": "2021-04-18",
|
||||
"data": {
|
||||
"wakeupduration": 3060,
|
||||
"wakeupcount": 1,
|
||||
"durationtosleep": 540,
|
||||
"remsleepduration": 2400,
|
||||
"durationtowakeup": 1140,
|
||||
"total_sleep_time": 18660,
|
||||
"sleep_efficiency": 0.86,
|
||||
"sleep_latency": 540,
|
||||
"wakeup_latency": 1140,
|
||||
"waso": 1380,
|
||||
"nb_rem_episodes": 1,
|
||||
"out_of_bed_count": 0,
|
||||
"lightsleepduration": 10440,
|
||||
"deepsleepduration": 5820,
|
||||
"hr_average": 103,
|
||||
"hr_min": 70,
|
||||
"hr_max": 120,
|
||||
"rr_average": 14,
|
||||
"rr_min": 10,
|
||||
"rr_max": 20,
|
||||
"breathing_disturbances_intensity": 9,
|
||||
"snoring": 1080,
|
||||
"snoringepisodecount": 18,
|
||||
"sleep_score": 37,
|
||||
"apnea_hypopnea_index": 9
|
||||
},
|
||||
"created": 1620237476,
|
||||
"modified": 1620237476
|
||||
},
|
||||
{
|
||||
"id": 2081804265,
|
||||
"timezone": "Europe/Paris",
|
||||
@ -77,6 +38,45 @@
|
||||
"created": 1620237480,
|
||||
"modified": 1620237479
|
||||
},
|
||||
{
|
||||
"id": 2081804182,
|
||||
"timezone": "Europe/Paris",
|
||||
"model": 32,
|
||||
"model_id": 63,
|
||||
"hash_deviceid": "201d0b9a0556d6b755166b2cf8d22d3bdf0487ee",
|
||||
"startdate": 1618691453,
|
||||
"enddate": 1618713173,
|
||||
"date": "2021-04-18",
|
||||
"data": {
|
||||
"wakeupduration": 3060,
|
||||
"wakeupcount": 1,
|
||||
"durationtosleep": 540,
|
||||
"remsleepduration": 2400,
|
||||
"durationtowakeup": 1140,
|
||||
"total_sleep_time": 18660,
|
||||
"sleep_efficiency": 0.86,
|
||||
"sleep_latency": 540,
|
||||
"wakeup_latency": 1140,
|
||||
"waso": 1380,
|
||||
"nb_rem_episodes": 1,
|
||||
"out_of_bed_count": 0,
|
||||
"lightsleepduration": 10440,
|
||||
"deepsleepduration": 5820,
|
||||
"hr_average": 103,
|
||||
"hr_min": 70,
|
||||
"hr_max": 120,
|
||||
"rr_average": 14,
|
||||
"rr_min": 10,
|
||||
"rr_max": 20,
|
||||
"breathing_disturbances_intensity": 9,
|
||||
"snoring": 1080,
|
||||
"snoringepisodecount": 18,
|
||||
"sleep_score": 37,
|
||||
"apnea_hypopnea_index": 9
|
||||
},
|
||||
"created": 1620237476,
|
||||
"modified": 1620237476
|
||||
},
|
||||
{
|
||||
"id": 2081804358,
|
||||
"timezone": "Europe/Paris",
|
||||
|
Loading…
x
Reference in New Issue
Block a user