mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
2023.11.2 (#103737)
This commit is contained in:
commit
a3319262ac
@ -9,5 +9,5 @@
|
|||||||
},
|
},
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["jaraco.abode", "lomond"],
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioairzone_cloud"],
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["blinkpy"],
|
"loggers": ["blinkpy"],
|
||||||
"requirements": ["blinkpy==0.22.2"]
|
"requirements": ["blinkpy==0.22.3"]
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"bleak-retry-connector==3.3.0",
|
"bleak-retry-connector==3.3.0",
|
||||||
"bluetooth-adapters==0.16.1",
|
"bluetooth-adapters==0.16.1",
|
||||||
"bluetooth-auto-recovery==1.2.3",
|
"bluetooth-auto-recovery==1.2.3",
|
||||||
"bluetooth-data-tools==1.13.0",
|
"bluetooth-data-tools==1.14.0",
|
||||||
"dbus-fast==2.12.0"
|
"dbus-fast==2.12.0"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ class DSMRConnection:
|
|||||||
self._protocol = protocol
|
self._protocol = protocol
|
||||||
self._telegram: dict[str, DSMRObject] = {}
|
self._telegram: dict[str, DSMRObject] = {}
|
||||||
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
|
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
|
||||||
|
if dsmr_version == "5B":
|
||||||
|
self._equipment_identifier = obis_ref.BELGIUM_EQUIPMENT_IDENTIFIER
|
||||||
if dsmr_version == "5L":
|
if dsmr_version == "5L":
|
||||||
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
|
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
|
||||||
if dsmr_version == "Q3D":
|
if dsmr_version == "Q3D":
|
||||||
|
@ -34,6 +34,3 @@ DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}
|
|||||||
|
|
||||||
DSMR_PROTOCOL = "dsmr_protocol"
|
DSMR_PROTOCOL = "dsmr_protocol"
|
||||||
RFXTRX_DSMR_PROTOCOL = "rfxtrx_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",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["dsmr_parser"],
|
"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 homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BELGIUM_5MIN_GAS_METER_READING,
|
|
||||||
CONF_DSMR_VERSION,
|
CONF_DSMR_VERSION,
|
||||||
CONF_PRECISION,
|
CONF_PRECISION,
|
||||||
CONF_PROTOCOL,
|
CONF_PROTOCOL,
|
||||||
@ -382,16 +381,6 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
|
|||||||
device_class=SensorDeviceClass.GAS,
|
device_class=SensorDeviceClass.GAS,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
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(
|
DSMRSensorEntityDescription(
|
||||||
key="gas_meter_reading",
|
key="gas_meter_reading",
|
||||||
translation_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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -438,6 +452,10 @@ async def async_setup_entry(
|
|||||||
return (entity_description.device_class, UNIT_CONVERSION[uom])
|
return (entity_description.device_class, UNIT_CONVERSION[uom])
|
||||||
return (entity_description.device_class, uom)
|
return (entity_description.device_class, uom)
|
||||||
|
|
||||||
|
all_sensors = SENSORS
|
||||||
|
if dsmr_version == "5B":
|
||||||
|
all_sensors += (add_gas_sensor_5B(telegram),)
|
||||||
|
|
||||||
entities.extend(
|
entities.extend(
|
||||||
[
|
[
|
||||||
DSMREntity(
|
DSMREntity(
|
||||||
@ -448,7 +466,7 @@ async def async_setup_entry(
|
|||||||
telegram, description
|
telegram, description
|
||||||
), # type: ignore[arg-type]
|
), # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
for description in SENSORS
|
for description in all_sensors
|
||||||
if (
|
if (
|
||||||
description.dsmr_versions is None
|
description.dsmr_versions is None
|
||||||
or dsmr_version in description.dsmr_versions
|
or dsmr_version in description.dsmr_versions
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyenphase"],
|
"loggers": ["pyenphase"],
|
||||||
"requirements": ["pyenphase==1.14.1"],
|
"requirements": ["pyenphase==1.14.2"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_enphase-envoy._tcp.local."
|
"type": "_enphase-envoy._tcp.local."
|
||||||
|
@ -596,6 +596,10 @@ def _async_setup_device_registry(
|
|||||||
model = project_name[1]
|
model = project_name[1]
|
||||||
hw_version = device_info.project_version
|
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_registry = dr.async_get(hass)
|
||||||
device_entry = device_registry.async_get_or_create(
|
device_entry = device_registry.async_get_or_create(
|
||||||
config_entry_id=entry.entry_id,
|
config_entry_id=entry.entry_id,
|
||||||
@ -606,6 +610,7 @@ def _async_setup_device_registry(
|
|||||||
model=model,
|
model=model,
|
||||||
sw_version=sw_version,
|
sw_version=sw_version,
|
||||||
hw_version=hw_version,
|
hw_version=hw_version,
|
||||||
|
suggested_area=suggested_area,
|
||||||
)
|
)
|
||||||
return device_entry.id
|
return device_entry.id
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
"loggers": ["aioesphomeapi", "noiseprotocol"],
|
"loggers": ["aioesphomeapi", "noiseprotocol"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"async-interrupt==1.1.1",
|
"async-interrupt==1.1.1",
|
||||||
"aioesphomeapi==18.1.0",
|
"aioesphomeapi==18.2.4",
|
||||||
"bluetooth-data-tools==1.13.0",
|
"bluetooth-data-tools==1.14.0",
|
||||||
"esphome-dashboard-api==1.2.3"
|
"esphome-dashboard-api==1.2.3"
|
||||||
],
|
],
|
||||||
"zeroconf": ["_esphomelib._tcp.local."]
|
"zeroconf": ["_esphomelib._tcp.local."]
|
||||||
|
@ -139,9 +139,9 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
return self._device_information["fwVersion"]
|
return self._device_information["fwVersion"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serial_number(self) -> str:
|
def serial_number(self) -> str | None:
|
||||||
"""Return the serial number for the device."""
|
"""Return the serial number for the device."""
|
||||||
return self._device_information["serialNumber"]
|
return self._device_information.get("serialNumber")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pending_info_alerts_count(self) -> int:
|
def pending_info_alerts_count(self) -> int:
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"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."""
|
"""Extract value from state."""
|
||||||
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||||
value = self.expose_default
|
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:
|
else:
|
||||||
value = (
|
value = state.state
|
||||||
state.state
|
|
||||||
if self.expose_attribute is None
|
|
||||||
else state.attributes.get(self.expose_attribute, self.expose_default)
|
|
||||||
)
|
|
||||||
if self.expose_type == "binary":
|
if self.expose_type == "binary":
|
||||||
if value in (1, STATE_ON, "True"):
|
if value in (1, STATE_ON, "True"):
|
||||||
return True
|
return True
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
|
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_push",
|
"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"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||||
"iot_class": "local_polling",
|
"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]
|
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))
|
devices.append(LupusecSwitch(data, device))
|
||||||
|
|
||||||
add_entities(devices)
|
add_entities(devices)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "calculated",
|
"iot_class": "calculated",
|
||||||
"loggers": ["yt_dlp"],
|
"loggers": ["yt_dlp"],
|
||||||
"quality_scale": "internal",
|
"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_unique_id = f"{self._attr_unique_id}_{idx}"
|
||||||
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
||||||
|
self._attr_device_class = entry.get(CONF_DEVICE_CLASS)
|
||||||
self._attr_available = False
|
self._attr_available = False
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ PARM_IS_LEGAL = namedtuple(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
# PARM_IS_LEGAL defines if the keywords:
|
# PARM_IS_LEGAL defines if the keywords:
|
||||||
# count: ..
|
# count:
|
||||||
# structure: ..
|
# structure:
|
||||||
# swap: byte
|
# swap: byte
|
||||||
# swap: word
|
# swap: word
|
||||||
# swap: word_byte (identical to 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.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.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.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)),
|
DataType.CUSTOM: ENTRY("?", 0, PARM_IS_LEGAL(True, True, False, False, False)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ from .mixins import (
|
|||||||
MqttAvailability,
|
MqttAvailability,
|
||||||
MqttEntity,
|
MqttEntity,
|
||||||
async_setup_entity_entry_helper,
|
async_setup_entity_entry_helper,
|
||||||
validate_sensor_entity_category,
|
|
||||||
write_state_on_attr_change,
|
write_state_on_attr_change,
|
||||||
)
|
)
|
||||||
from .models import MqttValueTemplate, ReceiveMessage
|
from .models import MqttValueTemplate, ReceiveMessage
|
||||||
@ -56,7 +55,7 @@ DEFAULT_PAYLOAD_ON = "ON"
|
|||||||
DEFAULT_FORCE_UPDATE = False
|
DEFAULT_FORCE_UPDATE = False
|
||||||
CONF_EXPIRE_AFTER = "expire_after"
|
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_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
|
||||||
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
|
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)
|
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
|
||||||
|
|
||||||
DISCOVERY_SCHEMA = vol.All(
|
DISCOVERY_SCHEMA = PLATFORM_SCHEMA_MODERN.extend({}, extra=vol.REMOVE_EXTRA)
|
||||||
validate_sensor_entity_category,
|
|
||||||
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
|
|
||||||
)
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA_MODERN = vol.All(validate_sensor_entity_category, _PLATFORM_SCHEMA_BASE)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -12,5 +12,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyatmo"],
|
"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 aiohttp import BasicAuth
|
||||||
from python_opensky import OpenSky
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
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),
|
contributing_user=entry.options.get(CONF_CONTRIBUTING_USER, False),
|
||||||
)
|
)
|
||||||
except OpenSkyUnauthenticatedError as exc:
|
except OpenSkyError as exc:
|
||||||
raise ConfigEntryNotReady from exc
|
raise ConfigEntryNotReady from exc
|
||||||
|
|
||||||
coordinator = OpenSkyDataUpdateCoordinator(hass, client)
|
coordinator = OpenSkyDataUpdateCoordinator(hass, client)
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/private_ble_device",
|
"documentation": "https://www.home-assistant.io/integrations/private_ble_device",
|
||||||
"iot_class": "local_push",
|
"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")
|
raise AbortFlow("already_configured")
|
||||||
|
|
||||||
# check if the camera is reachable at the new IP
|
# 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:
|
try:
|
||||||
await host.api.get_state("GetLocalLink")
|
await host.api.get_state("GetLocalLink")
|
||||||
await host.api.logout()
|
await host.api.logout()
|
||||||
|
@ -211,7 +211,9 @@ async def _async_create_bridge_with_updated_data(
|
|||||||
partial(getmac.get_mac_address, ip=host)
|
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)
|
LOGGER.info("Updated mac to %s for %s", mac, host)
|
||||||
updated_data[CONF_MAC] = mac
|
updated_data[CONF_MAC] = mac
|
||||||
else:
|
else:
|
||||||
|
@ -219,6 +219,9 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._title = f"{self._name} ({self._model})"
|
self._title = f"{self._name} ({self._model})"
|
||||||
self._udn = _strip_uuid(dev_info.get("udn", info["id"]))
|
self._udn = _strip_uuid(dev_info.get("udn", info["id"]))
|
||||||
if mac := mac_from_device_info(info):
|
if mac := mac_from_device_info(info):
|
||||||
|
# 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
|
self._mac = mac
|
||||||
elif mac := await self.hass.async_add_executor_job(
|
elif mac := await self.hass.async_add_executor_job(
|
||||||
partial(getmac.get_mac_address, ip=self._host)
|
partial(getmac.get_mac_address, ip=self._host)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["smarttub"],
|
"loggers": ["smarttub"],
|
||||||
"quality_scale": "platinum",
|
"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."""
|
"""Generic class for SmartTub status sensors."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str:
|
def native_value(self) -> str | None:
|
||||||
"""Return the current state of the sensor."""
|
"""Return the current state of the sensor."""
|
||||||
|
if self._state is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if isinstance(self._state, Enum):
|
if isinstance(self._state, Enum):
|
||||||
return self._state.name.lower()
|
return self._state.name.lower()
|
||||||
|
|
||||||
return self._state.lower()
|
return self._state.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,5 +32,5 @@ async def async_get_config_entry_diagnostics(
|
|||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
coordinator: TailscaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
coordinator: TailscaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
# Round-trip via JSON to trigger serialization
|
# 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)
|
return async_redact_data({"devices": devices}, TO_REDACT)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "platinum",
|
"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."""
|
"""Set up TPLink from a config entry."""
|
||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
try:
|
try:
|
||||||
device: SmartDevice = await Discover.discover_single(host)
|
device: SmartDevice = await Discover.discover_single(host, timeout=10)
|
||||||
except SmartDeviceException as ex:
|
except SmartDeviceException as ex:
|
||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ class TractiveClient:
|
|||||||
self._config_entry.data[CONF_EMAIL],
|
self._config_entry.data[CONF_EMAIL],
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except KeyError as error:
|
except (KeyError, TypeError) as error:
|
||||||
_LOGGER.error("Error while listening for events: %s", error)
|
_LOGGER.error("Error while listening for events: %s", error)
|
||||||
continue
|
continue
|
||||||
except aiotractive.exceptions.TractiveError:
|
except aiotractive.exceptions.TractiveError:
|
||||||
@ -284,11 +284,16 @@ class TractiveClient:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _send_wellness_update(self, event: dict[str, Any]) -> None:
|
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 = {
|
payload = {
|
||||||
ATTR_ACTIVITY_LABEL: event["wellness"].get("activity_label"),
|
ATTR_ACTIVITY_LABEL: event["wellness"].get("activity_label"),
|
||||||
ATTR_CALORIES: event["activity"]["calories"],
|
ATTR_CALORIES: event["activity"]["calories"],
|
||||||
ATTR_MINUTES_DAY_SLEEP: event["sleep"]["minutes_day_sleep"],
|
ATTR_MINUTES_DAY_SLEEP: sleep_day,
|
||||||
ATTR_MINUTES_NIGHT_SLEEP: event["sleep"]["minutes_night_sleep"],
|
ATTR_MINUTES_NIGHT_SLEEP: sleep_night,
|
||||||
ATTR_MINUTES_REST: event["activity"]["minutes_rest"],
|
ATTR_MINUTES_REST: event["activity"]["minutes_rest"],
|
||||||
ATTR_SLEEP_LABEL: event["wellness"].get("sleep_label"),
|
ATTR_SLEEP_LABEL: event["wellness"].get("sleep_label"),
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,8 @@ class WeatherFlowSensorEntityDescription(
|
|||||||
|
|
||||||
def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
|
def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
|
||||||
"""Return the parsed sensor value."""
|
"""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)
|
return self.raw_data_conv_fn(raw_sensor_data)
|
||||||
|
|
||||||
|
|
||||||
@ -371,14 +372,17 @@ class WeatherFlowSensorEntity(SensorEntity):
|
|||||||
return self.device.last_report
|
return self.device.last_report
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
def _async_update_state(self) -> None:
|
||||||
def native_value(self) -> datetime | StateType:
|
"""Update entity state."""
|
||||||
"""Return the state of the sensor."""
|
value = self.entity_description.get_native_value(self.device)
|
||||||
return 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:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Subscribe to events."""
|
"""Subscribe to events."""
|
||||||
|
self._async_update_state()
|
||||||
for event in self.entity_description.event_subscriptions:
|
for event in self.entity_description.event_subscriptions:
|
||||||
self.async_on_remove(
|
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
|
_default_update_interval: timedelta | None = UPDATE_INTERVAL
|
||||||
_last_valid_update: datetime | None = None
|
_last_valid_update: datetime | None = None
|
||||||
webhooks_connected: bool = False
|
webhooks_connected: bool = False
|
||||||
|
coordinator_name: str = ""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
"""Initialize the Withings data coordinator."""
|
"""Initialize the Withings data coordinator."""
|
||||||
super().__init__(
|
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._client = client
|
||||||
self.notification_categories: set[NotificationCategory] = set()
|
self.notification_categories: set[NotificationCategory] = set()
|
||||||
@ -77,6 +81,8 @@ class WithingsMeasurementDataUpdateCoordinator(
|
|||||||
):
|
):
|
||||||
"""Withings measurement coordinator."""
|
"""Withings measurement coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "measurements"
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
"""Initialize the Withings data coordinator."""
|
"""Initialize the Withings data coordinator."""
|
||||||
super().__init__(hass, client)
|
super().__init__(hass, client)
|
||||||
@ -109,6 +115,8 @@ class WithingsSleepDataUpdateCoordinator(
|
|||||||
):
|
):
|
||||||
"""Withings sleep coordinator."""
|
"""Withings sleep coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "sleep"
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
"""Initialize the Withings data coordinator."""
|
"""Initialize the Withings data coordinator."""
|
||||||
super().__init__(hass, client)
|
super().__init__(hass, client)
|
||||||
@ -147,12 +155,16 @@ class WithingsSleepDataUpdateCoordinator(
|
|||||||
)
|
)
|
||||||
if not response:
|
if not response:
|
||||||
return None
|
return None
|
||||||
return response[0]
|
|
||||||
|
return sorted(
|
||||||
|
response, key=lambda sleep_summary: sleep_summary.end_date, reverse=True
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
|
||||||
class WithingsBedPresenceDataUpdateCoordinator(WithingsDataUpdateCoordinator[None]):
|
class WithingsBedPresenceDataUpdateCoordinator(WithingsDataUpdateCoordinator[None]):
|
||||||
"""Withings bed presence coordinator."""
|
"""Withings bed presence coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "bed presence"
|
||||||
in_bed: bool | None = None
|
in_bed: bool | None = None
|
||||||
_default_update_interval = None
|
_default_update_interval = None
|
||||||
|
|
||||||
@ -178,6 +190,7 @@ class WithingsBedPresenceDataUpdateCoordinator(WithingsDataUpdateCoordinator[Non
|
|||||||
class WithingsGoalsDataUpdateCoordinator(WithingsDataUpdateCoordinator[Goals]):
|
class WithingsGoalsDataUpdateCoordinator(WithingsDataUpdateCoordinator[Goals]):
|
||||||
"""Withings goals coordinator."""
|
"""Withings goals coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "goals"
|
||||||
_default_update_interval = timedelta(hours=1)
|
_default_update_interval = timedelta(hours=1)
|
||||||
|
|
||||||
def webhook_subscription_listener(self, connected: bool) -> None:
|
def webhook_subscription_listener(self, connected: bool) -> None:
|
||||||
@ -194,6 +207,7 @@ class WithingsActivityDataUpdateCoordinator(
|
|||||||
):
|
):
|
||||||
"""Withings activity coordinator."""
|
"""Withings activity coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "activity"
|
||||||
_previous_data: Activity | None = None
|
_previous_data: Activity | None = None
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
@ -232,6 +246,7 @@ class WithingsWorkoutDataUpdateCoordinator(
|
|||||||
):
|
):
|
||||||
"""Withings workout coordinator."""
|
"""Withings workout coordinator."""
|
||||||
|
|
||||||
|
coordinator_name: str = "workout"
|
||||||
_previous_data: Workout | None = None
|
_previous_data: Workout | None = None
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
def __init__(self, hass: HomeAssistant, client: WithingsClient) -> None:
|
||||||
|
@ -7,7 +7,7 @@ from typing import Final
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 11
|
MINOR_VERSION: Final = 11
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__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, 11, 0)
|
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
|
bleak==0.21.1
|
||||||
bluetooth-adapters==0.16.1
|
bluetooth-adapters==0.16.1
|
||||||
bluetooth-auto-recovery==1.2.3
|
bluetooth-auto-recovery==1.2.3
|
||||||
bluetooth-data-tools==1.13.0
|
bluetooth-data-tools==1.14.0
|
||||||
certifi>=2021.5.30
|
certifi>=2021.5.30
|
||||||
ciso8601==2.3.0
|
ciso8601==2.3.0
|
||||||
cryptography==41.0.4
|
cryptography==41.0.4
|
||||||
@ -22,7 +22,7 @@ ha-av==10.1.1
|
|||||||
hass-nabucasa==0.74.0
|
hass-nabucasa==0.74.0
|
||||||
hassil==1.2.5
|
hassil==1.2.5
|
||||||
home-assistant-bluetooth==1.10.4
|
home-assistant-bluetooth==1.10.4
|
||||||
home-assistant-frontend==20231030.1
|
home-assistant-frontend==20231030.2
|
||||||
home-assistant-intents==2023.10.16
|
home-assistant-intents==2023.10.16
|
||||||
httpx==0.25.0
|
httpx==0.25.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.11.1"
|
version = "2023.11.2"
|
||||||
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"
|
||||||
|
@ -192,7 +192,7 @@ aio-georss-gdacs==0.8
|
|||||||
aioairq==0.2.4
|
aioairq==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.airzone_cloud
|
# homeassistant.components.airzone_cloud
|
||||||
aioairzone-cloud==0.3.1
|
aioairzone-cloud==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.6.9
|
aioairzone==0.6.9
|
||||||
@ -237,7 +237,7 @@ aioecowitt==2023.5.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==18.1.0
|
aioesphomeapi==18.2.4
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -539,7 +539,7 @@ bleak==0.21.1
|
|||||||
blebox-uniapi==2.2.0
|
blebox-uniapi==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.22.2
|
blinkpy==0.22.3
|
||||||
|
|
||||||
# homeassistant.components.bitcoin
|
# homeassistant.components.bitcoin
|
||||||
blockchain==1.4.4
|
blockchain==1.4.4
|
||||||
@ -562,7 +562,7 @@ bluetooth-auto-recovery==1.2.3
|
|||||||
# homeassistant.components.ld2410_ble
|
# homeassistant.components.ld2410_ble
|
||||||
# homeassistant.components.led_ble
|
# homeassistant.components.led_ble
|
||||||
# homeassistant.components.private_ble_device
|
# homeassistant.components.private_ble_device
|
||||||
bluetooth-data-tools==1.13.0
|
bluetooth-data-tools==1.14.0
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-async==0.2.1
|
bond-async==0.2.1
|
||||||
@ -701,7 +701,7 @@ dovado==0.4.1
|
|||||||
dremel3dpy==2.1.1
|
dremel3dpy==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.dsmr
|
# homeassistant.components.dsmr
|
||||||
dsmr-parser==1.3.0
|
dsmr-parser==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.dwd_weather_warnings
|
# homeassistant.components.dwd_weather_warnings
|
||||||
dwdwfsapi==1.0.6
|
dwdwfsapi==1.0.6
|
||||||
@ -1007,7 +1007,7 @@ hole==0.8.0
|
|||||||
holidays==0.35
|
holidays==0.35
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231030.1
|
home-assistant-frontend==20231030.2
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.10.16
|
home-assistant-intents==2023.10.16
|
||||||
@ -1094,6 +1094,9 @@ janus==1.0.0
|
|||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
jaraco.abode==3.3.0
|
jaraco.abode==3.3.0
|
||||||
|
|
||||||
|
# homeassistant.components.abode
|
||||||
|
jaraco.functools==3.9.0
|
||||||
|
|
||||||
# homeassistant.components.jellyfin
|
# homeassistant.components.jellyfin
|
||||||
jellyfin-apiclient-python==1.9.2
|
jellyfin-apiclient-python==1.9.2
|
||||||
|
|
||||||
@ -1597,7 +1600,7 @@ pyairvisual==2023.08.1
|
|||||||
pyatag==0.3.5.3
|
pyatag==0.3.5.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==7.5.0
|
pyatmo==7.6.0
|
||||||
|
|
||||||
# homeassistant.components.apple_tv
|
# homeassistant.components.apple_tv
|
||||||
pyatv==0.14.3
|
pyatv==0.14.3
|
||||||
@ -1693,7 +1696,7 @@ pyedimax==0.2.1
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.14.1
|
pyenphase==1.14.2
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
pyenvisalink==4.6
|
pyenvisalink==4.6
|
||||||
@ -2184,7 +2187,7 @@ python-ripple-api==0.0.3
|
|||||||
python-roborock==0.35.0
|
python-roborock==0.35.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.35
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.15.2
|
python-songpal==0.15.2
|
||||||
@ -2533,7 +2536,7 @@ synology-srm==0.2.0
|
|||||||
systembridgeconnector==3.8.4
|
systembridgeconnector==3.8.4
|
||||||
|
|
||||||
# homeassistant.components.tailscale
|
# homeassistant.components.tailscale
|
||||||
tailscale==0.2.0
|
tailscale==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.tank_utility
|
# homeassistant.components.tank_utility
|
||||||
tank-utility==1.5.0
|
tank-utility==1.5.0
|
||||||
@ -2779,7 +2782,7 @@ youless-api==1.0.1
|
|||||||
youtubeaio==1.1.5
|
youtubeaio==1.1.5
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp==2023.9.24
|
yt-dlp==2023.10.13
|
||||||
|
|
||||||
# homeassistant.components.zamg
|
# homeassistant.components.zamg
|
||||||
zamg==0.3.0
|
zamg==0.3.0
|
||||||
|
@ -173,7 +173,7 @@ aio-georss-gdacs==0.8
|
|||||||
aioairq==0.2.4
|
aioairq==0.2.4
|
||||||
|
|
||||||
# homeassistant.components.airzone_cloud
|
# homeassistant.components.airzone_cloud
|
||||||
aioairzone-cloud==0.3.1
|
aioairzone-cloud==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.6.9
|
aioairzone==0.6.9
|
||||||
@ -218,7 +218,7 @@ aioecowitt==2023.5.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==18.1.0
|
aioesphomeapi==18.2.4
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -460,7 +460,7 @@ bleak==0.21.1
|
|||||||
blebox-uniapi==2.2.0
|
blebox-uniapi==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.22.2
|
blinkpy==0.22.3
|
||||||
|
|
||||||
# homeassistant.components.bluemaestro
|
# homeassistant.components.bluemaestro
|
||||||
bluemaestro-ble==0.2.3
|
bluemaestro-ble==0.2.3
|
||||||
@ -476,7 +476,7 @@ bluetooth-auto-recovery==1.2.3
|
|||||||
# homeassistant.components.ld2410_ble
|
# homeassistant.components.ld2410_ble
|
||||||
# homeassistant.components.led_ble
|
# homeassistant.components.led_ble
|
||||||
# homeassistant.components.private_ble_device
|
# homeassistant.components.private_ble_device
|
||||||
bluetooth-data-tools==1.13.0
|
bluetooth-data-tools==1.14.0
|
||||||
|
|
||||||
# homeassistant.components.bond
|
# homeassistant.components.bond
|
||||||
bond-async==0.2.1
|
bond-async==0.2.1
|
||||||
@ -572,7 +572,7 @@ discovery30303==0.2.1
|
|||||||
dremel3dpy==2.1.1
|
dremel3dpy==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.dsmr
|
# homeassistant.components.dsmr
|
||||||
dsmr-parser==1.3.0
|
dsmr-parser==1.3.1
|
||||||
|
|
||||||
# homeassistant.components.dwd_weather_warnings
|
# homeassistant.components.dwd_weather_warnings
|
||||||
dwdwfsapi==1.0.6
|
dwdwfsapi==1.0.6
|
||||||
@ -796,7 +796,7 @@ hole==0.8.0
|
|||||||
holidays==0.35
|
holidays==0.35
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20231030.1
|
home-assistant-frontend==20231030.2
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.10.16
|
home-assistant-intents==2023.10.16
|
||||||
@ -862,6 +862,9 @@ janus==1.0.0
|
|||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
jaraco.abode==3.3.0
|
jaraco.abode==3.3.0
|
||||||
|
|
||||||
|
# homeassistant.components.abode
|
||||||
|
jaraco.functools==3.9.0
|
||||||
|
|
||||||
# homeassistant.components.jellyfin
|
# homeassistant.components.jellyfin
|
||||||
jellyfin-apiclient-python==1.9.2
|
jellyfin-apiclient-python==1.9.2
|
||||||
|
|
||||||
@ -1215,7 +1218,7 @@ pyairvisual==2023.08.1
|
|||||||
pyatag==0.3.5.3
|
pyatag==0.3.5.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==7.5.0
|
pyatmo==7.6.0
|
||||||
|
|
||||||
# homeassistant.components.apple_tv
|
# homeassistant.components.apple_tv
|
||||||
pyatv==0.14.3
|
pyatv==0.14.3
|
||||||
@ -1275,7 +1278,7 @@ pyeconet==0.1.22
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.14.1
|
pyenphase==1.14.2
|
||||||
|
|
||||||
# homeassistant.components.everlights
|
# homeassistant.components.everlights
|
||||||
pyeverlights==0.1.0
|
pyeverlights==0.1.0
|
||||||
@ -1628,7 +1631,7 @@ python-qbittorrent==0.4.3
|
|||||||
python-roborock==0.35.0
|
python-roborock==0.35.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.35
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.15.2
|
python-songpal==0.15.2
|
||||||
@ -1887,7 +1890,7 @@ switchbot-api==1.2.1
|
|||||||
systembridgeconnector==3.8.4
|
systembridgeconnector==3.8.4
|
||||||
|
|
||||||
# homeassistant.components.tailscale
|
# homeassistant.components.tailscale
|
||||||
tailscale==0.2.0
|
tailscale==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.tellduslive
|
# homeassistant.components.tellduslive
|
||||||
tellduslive==0.10.11
|
tellduslive==0.10.11
|
||||||
@ -2076,7 +2079,7 @@ youless-api==1.0.1
|
|||||||
youtubeaio==1.1.5
|
youtubeaio==1.1.5
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp==2023.9.24
|
yt-dlp==2023.10.13
|
||||||
|
|
||||||
# homeassistant.components.zamg
|
# homeassistant.components.zamg
|
||||||
zamg==0.3.0
|
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.protocol import DSMRProtocol
|
||||||
from dsmr_parser.clients.rfxtrx_protocol import RFXtrxDSMRProtocol
|
from dsmr_parser.clients.rfxtrx_protocol import RFXtrxDSMRProtocol
|
||||||
from dsmr_parser.obis_references import (
|
from dsmr_parser.obis_references import (
|
||||||
|
BELGIUM_EQUIPMENT_IDENTIFIER,
|
||||||
EQUIPMENT_IDENTIFIER,
|
EQUIPMENT_IDENTIFIER,
|
||||||
EQUIPMENT_IDENTIFIER_GAS,
|
EQUIPMENT_IDENTIFIER_GAS,
|
||||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER,
|
LUXEMBOURG_EQUIPMENT_IDENTIFIER,
|
||||||
@ -81,6 +82,15 @@ async def dsmr_connection_send_validate_fixture(hass):
|
|||||||
|
|
||||||
async def connection_factory(*args, **kwargs):
|
async def connection_factory(*args, **kwargs):
|
||||||
"""Return mocked out Asyncio classes."""
|
"""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":
|
if args[1] == "5L":
|
||||||
protocol.telegram = {
|
protocol.telegram = {
|
||||||
LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemObject(
|
LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemObject(
|
||||||
|
@ -215,6 +215,50 @@ async def test_setup_serial_rfxtrx(
|
|||||||
assert result["data"] == {**entry_data, **SERIAL_DATA}
|
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()])
|
@patch("serial.tools.list_ports.comports", return_value=[com_port()])
|
||||||
async def test_setup_5L(
|
async def test_setup_5L(
|
||||||
com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture
|
com_mock, hass: HomeAssistant, dsmr_connection_send_validate_fixture
|
||||||
|
@ -8,10 +8,22 @@ import asyncio
|
|||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from itertools import chain, repeat
|
from itertools import chain, repeat
|
||||||
|
from typing import Literal
|
||||||
from unittest.mock import DEFAULT, MagicMock
|
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 import config_entries
|
||||||
from homeassistant.components.dsmr.const import BELGIUM_5MIN_GAS_METER_READING
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
ATTR_OPTIONS,
|
ATTR_OPTIONS,
|
||||||
ATTR_STATE_CLASS,
|
ATTR_STATE_CLASS,
|
||||||
@ -483,6 +495,10 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
|
|||||||
from dsmr_parser.obis_references import (
|
from dsmr_parser.obis_references import (
|
||||||
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
||||||
BELGIUM_MAXIMUM_DEMAND_MONTH,
|
BELGIUM_MAXIMUM_DEMAND_MONTH,
|
||||||
|
BELGIUM_MBUS1_METER_READING2,
|
||||||
|
BELGIUM_MBUS2_METER_READING2,
|
||||||
|
BELGIUM_MBUS3_METER_READING2,
|
||||||
|
BELGIUM_MBUS4_METER_READING2,
|
||||||
ELECTRICITY_ACTIVE_TARIFF,
|
ELECTRICITY_ACTIVE_TARIFF,
|
||||||
)
|
)
|
||||||
from dsmr_parser.objects import CosemObject, MBusObject
|
from dsmr_parser.objects import CosemObject, MBusObject
|
||||||
@ -500,13 +516,34 @@ async def test_belgian_meter(hass: HomeAssistant, dsmr_connection_fixture) -> No
|
|||||||
}
|
}
|
||||||
|
|
||||||
telegram = {
|
telegram = {
|
||||||
BELGIUM_5MIN_GAS_METER_READING: MBusObject(
|
BELGIUM_MBUS1_METER_READING2: MBusObject(
|
||||||
BELGIUM_5MIN_GAS_METER_READING,
|
BELGIUM_MBUS1_METER_READING2,
|
||||||
[
|
[
|
||||||
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||||
{"value": Decimal(745.695), "unit": "m3"},
|
{"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: CosemObject(
|
||||||
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
BELGIUM_CURRENT_AVERAGE_DEMAND,
|
||||||
[{"value": Decimal(1.75), "unit": "kW"}],
|
[{"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:
|
async def test_belgian_meter_low(hass: HomeAssistant, dsmr_connection_fixture) -> None:
|
||||||
"""Test if Belgian meter is correctly parsed."""
|
"""Test if Belgian meter is correctly parsed."""
|
||||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||||
|
@ -77,6 +77,17 @@ def mock_config_entry(hass) -> MockConfigEntry:
|
|||||||
return config_entry
|
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
|
@pytest.fixture
|
||||||
def mock_device_info() -> DeviceInfo:
|
def mock_device_info() -> DeviceInfo:
|
||||||
"""Return the default mocked device info."""
|
"""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.address = "127.0.0.1"
|
||||||
mock_client.api_version = APIVersion(99, 99)
|
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
|
"homeassistant.components.esphome.config_flow.APIClient", mock_client
|
||||||
):
|
):
|
||||||
yield mock_client
|
yield mock_client
|
||||||
@ -234,7 +248,7 @@ async def _mock_generic_device_entry(
|
|||||||
|
|
||||||
try_connect_done = Event()
|
try_connect_done = Event()
|
||||||
|
|
||||||
class MockReconnectLogic(ReconnectLogic):
|
class MockReconnectLogic(BaseMockReconnectLogic):
|
||||||
"""Mock ReconnectLogic."""
|
"""Mock ReconnectLogic."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -250,6 +264,13 @@ async def _mock_generic_device_entry(
|
|||||||
try_connect_done.set()
|
try_connect_done.set()
|
||||||
return result
|
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(
|
with patch(
|
||||||
"homeassistant.components.esphome.manager.ReconnectLogic", MockReconnectLogic
|
"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", {})
|
hass.states.async_set(entity_id, "off", {})
|
||||||
await knx.assert_telegram_count(0)
|
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(
|
async def test_expose_attribute_with_default(
|
||||||
hass: HomeAssistant, knx: KNXTestKit
|
hass: HomeAssistant, knx: KNXTestKit
|
||||||
@ -132,6 +140,14 @@ async def test_expose_attribute_with_default(
|
|||||||
hass.states.async_set(entity_id, "off", {})
|
hass.states.async_set(entity_id, "off", {})
|
||||||
await knx.assert_write("1/1/8", (0,))
|
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:
|
async def test_expose_string(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||||
"""Test an expose to send string values of up to 14 bytes only."""
|
"""Test an expose to send string values of up to 14 bytes only."""
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Test the Litter-Robot time entity."""
|
"""Test the Litter-Robot time entity."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import time
|
from datetime import datetime, time
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pylitterbot import LitterRobot3
|
from pylitterbot import LitterRobot3
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.time import DOMAIN as PLATFORM_DOMAIN
|
from homeassistant.components.time import DOMAIN as PLATFORM_DOMAIN
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
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"
|
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(
|
async def test_sleep_mode_start_time(
|
||||||
hass: HomeAssistant, mock_account: MagicMock
|
hass: HomeAssistant, mock_account: MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -513,6 +513,20 @@ async def test_config_wrong_struct_sensor(
|
|||||||
False,
|
False,
|
||||||
"07-05-2020 14:35",
|
"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,
|
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(
|
@patch(
|
||||||
|
@ -572,6 +572,7 @@
|
|||||||
'read_smokedetector',
|
'read_smokedetector',
|
||||||
'read_station',
|
'read_station',
|
||||||
'read_thermostat',
|
'read_thermostat',
|
||||||
|
'read_mhs1',
|
||||||
'write_bubendorff',
|
'write_bubendorff',
|
||||||
'write_camera',
|
'write_camera',
|
||||||
'write_magellan',
|
'write_magellan',
|
||||||
@ -579,6 +580,7 @@
|
|||||||
'write_presence',
|
'write_presence',
|
||||||
'write_smarther',
|
'write_smarther',
|
||||||
'write_thermostat',
|
'write_thermostat',
|
||||||
|
'write_mhs1',
|
||||||
]),
|
]),
|
||||||
'type': 'Bearer',
|
'type': 'Bearer',
|
||||||
}),
|
}),
|
||||||
|
@ -34,8 +34,10 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def reolink_connect(mock_get_source_ip: None) -> Generator[MagicMock, None, None]:
|
def reolink_connect_class(
|
||||||
"""Mock reolink connection."""
|
mock_get_source_ip: None,
|
||||||
|
) -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock reolink connection and return both the host_mock and host_mock_class."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.reolink.host.webhook.async_register",
|
"homeassistant.components.reolink.host.webhook.async_register",
|
||||||
return_value=True,
|
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.session_active = True
|
||||||
host_mock.timeout = 60
|
host_mock.timeout = 60
|
||||||
host_mock.renewtimer.return_value = 600
|
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
|
@pytest.fixture
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock, call
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError
|
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 import DEVICE_UPDATE_INTERVAL, const
|
||||||
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
||||||
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
from homeassistant.components.reolink.exceptions import ReolinkWebhookException
|
||||||
|
from homeassistant.components.reolink.host import DEFAULT_TIMEOUT
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -380,41 +381,47 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: MagicMock) -> No
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("last_update_success", "attr", "value", "expected"),
|
("last_update_success", "attr", "value", "expected", "host_call_list"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
TEST_HOST2,
|
TEST_HOST2,
|
||||||
|
[TEST_HOST, TEST_HOST2],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
True,
|
True,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
TEST_HOST,
|
TEST_HOST,
|
||||||
|
[TEST_HOST],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
False,
|
False,
|
||||||
"get_state",
|
"get_state",
|
||||||
AsyncMock(side_effect=ReolinkError("Test error")),
|
AsyncMock(side_effect=ReolinkError("Test error")),
|
||||||
TEST_HOST,
|
TEST_HOST,
|
||||||
|
[TEST_HOST, TEST_HOST2],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
False,
|
False,
|
||||||
"mac_address",
|
"mac_address",
|
||||||
"aa:aa:aa:aa:aa:aa",
|
"aa:aa:aa:aa:aa:aa",
|
||||||
TEST_HOST,
|
TEST_HOST,
|
||||||
|
[TEST_HOST, TEST_HOST2],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_dhcp_ip_update(
|
async def test_dhcp_ip_update(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
reolink_connect_class: MagicMock,
|
||||||
reolink_connect: MagicMock,
|
reolink_connect: MagicMock,
|
||||||
last_update_success: bool,
|
last_update_success: bool,
|
||||||
attr: str,
|
attr: str,
|
||||||
value: Any,
|
value: Any,
|
||||||
expected: str,
|
expected: str,
|
||||||
|
host_call_list: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test dhcp discovery aborts if already configured where the IP is updated if appropriate."""
|
"""Test dhcp discovery aborts if already configured where the IP is updated if appropriate."""
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
@ -459,6 +466,22 @@ async def test_dhcp_ip_update(
|
|||||||
const.DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_data
|
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["type"] is data_entry_flow.FlowResultType.ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Tests for Samsung TV config flow."""
|
"""Tests for Samsung TV config flow."""
|
||||||
|
from copy import deepcopy
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
from unittest.mock import ANY, AsyncMock, Mock, call, patch
|
from unittest.mock import ANY, AsyncMock, Mock, call, patch
|
||||||
|
|
||||||
@ -165,14 +166,6 @@ MOCK_DEVICE_INFO = {
|
|||||||
},
|
},
|
||||||
"id": "123",
|
"id": "123",
|
||||||
}
|
}
|
||||||
MOCK_DEVICE_INFO_2 = {
|
|
||||||
"device": {
|
|
||||||
"type": "Samsung SmartTV",
|
|
||||||
"name": "fake2_name",
|
|
||||||
"modelName": "fake2_model",
|
|
||||||
},
|
|
||||||
"id": "345",
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTODETECT_LEGACY = {
|
AUTODETECT_LEGACY = {
|
||||||
"name": "HomeAssistant",
|
"name": "HomeAssistant",
|
||||||
@ -1968,3 +1961,56 @@ async def test_no_update_incorrect_udn_not_matching_mac_from_dhcp(
|
|||||||
assert result["step_id"] == "confirm"
|
assert result["step_id"] == "confirm"
|
||||||
assert entry.data[CONF_MAC] == "aa:bb:ss:ss:dd:pp"
|
assert entry.data[CONF_MAC] == "aa:bb:ss:ss:dd:pp"
|
||||||
assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
|
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 pytest
|
||||||
import smarttub
|
import smarttub
|
||||||
|
|
||||||
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,27 @@ async def test_sensor(
|
|||||||
assert state.state == expected_state
|
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(
|
async def test_primary_filtration(
|
||||||
spa, spa_state, setup_entry, hass: HomeAssistant
|
spa, spa_state, setup_entry, hass: HomeAssistant
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -41,7 +41,7 @@ def mock_tailscale_config_flow() -> Generator[None, MagicMock, None]:
|
|||||||
"homeassistant.components.tailscale.config_flow.Tailscale", autospec=True
|
"homeassistant.components.tailscale.config_flow.Tailscale", autospec=True
|
||||||
) as tailscale_mock:
|
) as tailscale_mock:
|
||||||
tailscale = tailscale_mock.return_value
|
tailscale = tailscale_mock.return_value
|
||||||
tailscale.devices.return_value = Devices.parse_raw(
|
tailscale.devices.return_value = Devices.from_json(
|
||||||
load_fixture("tailscale/devices.json")
|
load_fixture("tailscale/devices.json")
|
||||||
).devices
|
).devices
|
||||||
yield tailscale
|
yield tailscale
|
||||||
@ -54,7 +54,7 @@ def mock_tailscale(request: pytest.FixtureRequest) -> Generator[None, MagicMock,
|
|||||||
if hasattr(request, "param") and request.param:
|
if hasattr(request, "param") and request.param:
|
||||||
fixture = request.param
|
fixture = request.param
|
||||||
|
|
||||||
devices = Devices.parse_raw(load_fixture(fixture)).devices
|
devices = Devices.from_json(load_fixture(fixture)).devices
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.tailscale.coordinator.Tailscale", autospec=True
|
"homeassistant.components.tailscale.coordinator.Tailscale", autospec=True
|
||||||
) as tailscale_mock:
|
) 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."""
|
"""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 homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -12,93 +12,10 @@ async def test_diagnostics(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
init_integration: MockConfigEntry,
|
init_integration: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test diagnostics."""
|
"""Test diagnostics."""
|
||||||
assert await get_diagnostics_for_config_entry(
|
assert (
|
||||||
hass, hass_client, init_integration
|
await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
|
||||||
) == {
|
== snapshot
|
||||||
"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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -202,7 +202,7 @@ def _patch_discovery(device=None, no_device=False):
|
|||||||
|
|
||||||
|
|
||||||
def _patch_single_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:
|
if no_device:
|
||||||
raise SmartDeviceException
|
raise SmartDeviceException
|
||||||
return device if device else _mocked_bulb()
|
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,
|
"id": 2081804265,
|
||||||
"timezone": "Europe/Paris",
|
"timezone": "Europe/Paris",
|
||||||
@ -77,6 +38,45 @@
|
|||||||
"created": 1620237480,
|
"created": 1620237480,
|
||||||
"modified": 1620237479
|
"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,
|
"id": 2081804358,
|
||||||
"timezone": "Europe/Paris",
|
"timezone": "Europe/Paris",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user