This commit is contained in:
Franck Nijhof 2023-11-10 12:41:51 +01:00 committed by GitHub
commit a3319262ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 665 additions and 255 deletions

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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"
]
}

View File

@ -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":

View File

@ -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"

View File

@ -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"]
}

View File

@ -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

View File

@ -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."

View File

@ -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

View File

@ -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."]

View File

@ -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:

View File

@ -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"]
}

View File

@ -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

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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)

View File

@ -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"]
}

View File

@ -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)

View File

@ -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)),
}

View File

@ -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(

View File

@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pyatmo"],
"requirements": ["pyatmo==7.5.0"]
"requirements": ["pyatmo==7.6.0"]
}

View File

@ -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)

View File

@ -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"]
}

View File

@ -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()

View File

@ -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:

View File

@ -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)
):

View File

@ -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"]
}

View File

@ -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()

View File

@ -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)

View File

@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["tailscale==0.2.0"]
"requirements": ["tailscale==0.6.0"]
}

View File

@ -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

View File

@ -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"),
}

View File

@ -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())
)

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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
):

View File

@ -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."""

View File

@ -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:

View File

@ -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,

View File

@ -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(

View File

@ -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',
}),

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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:

View File

@ -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:

View 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**',
}),
]),
})
# ---

View File

@ -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
)

View File

@ -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()

View File

@ -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",