Add sensor platform to Qbus integration (#149389)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Thomas D 2025-08-02 20:01:58 +02:00 committed by GitHub
parent fa476d4e34
commit 755864f9f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1882 additions and 40 deletions

View File

@ -22,7 +22,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import QbusConfigEntry from .coordinator import QbusConfigEntry
from .entity import QbusEntity, add_new_outputs from .entity import QbusEntity, create_new_entities
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -42,13 +42,13 @@ async def async_setup_entry(
added_outputs: list[QbusMqttOutput] = [] added_outputs: list[QbusMqttOutput] = []
def _check_outputs() -> None: def _check_outputs() -> None:
add_new_outputs( entities = create_new_entities(
coordinator, coordinator,
added_outputs, added_outputs,
lambda output: output.type == "thermo", lambda output: output.type == "thermo",
QbusClimate, QbusClimate,
async_add_entities,
) )
async_add_entities(entities)
_check_outputs() _check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs)) entry.async_on_unload(coordinator.async_add_listener(_check_outputs))

View File

@ -10,6 +10,7 @@ PLATFORMS: list[Platform] = [
Platform.COVER, Platform.COVER,
Platform.LIGHT, Platform.LIGHT,
Platform.SCENE, Platform.SCENE,
Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
] ]

View File

@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import QbusConfigEntry from .coordinator import QbusConfigEntry
from .entity import QbusEntity, add_new_outputs from .entity import QbusEntity, create_new_entities
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -36,13 +36,13 @@ async def async_setup_entry(
added_outputs: list[QbusMqttOutput] = [] added_outputs: list[QbusMqttOutput] = []
def _check_outputs() -> None: def _check_outputs() -> None:
add_new_outputs( entities = create_new_entities(
coordinator, coordinator,
added_outputs, added_outputs,
lambda output: output.type == "shutter", lambda output: output.type == "shutter",
QbusCover, QbusCover,
async_add_entities,
) )
async_add_entities(entities)
_check_outputs() _check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs)) entry.async_on_unload(coordinator.async_add_listener(_check_outputs))

View File

@ -14,7 +14,6 @@ from qbusmqttapi.state import QbusMqttState
from homeassistant.components.mqtt import ReceiveMessage, client as mqtt from homeassistant.components.mqtt import ReceiveMessage, client as mqtt
from homeassistant.helpers.device_registry import DeviceInfo, format_mac from homeassistant.helpers.device_registry import DeviceInfo, format_mac
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, MANUFACTURER from .const import DOMAIN, MANUFACTURER
from .coordinator import QbusControllerCoordinator from .coordinator import QbusControllerCoordinator
@ -24,14 +23,24 @@ _REFID_REGEX = re.compile(r"^\d+\/(\d+(?:\/\d+)?)$")
StateT = TypeVar("StateT", bound=QbusMqttState) StateT = TypeVar("StateT", bound=QbusMqttState)
def add_new_outputs( def create_new_entities(
coordinator: QbusControllerCoordinator, coordinator: QbusControllerCoordinator,
added_outputs: list[QbusMqttOutput], added_outputs: list[QbusMqttOutput],
filter_fn: Callable[[QbusMqttOutput], bool], filter_fn: Callable[[QbusMqttOutput], bool],
entity_type: type[QbusEntity], entity_type: type[QbusEntity],
async_add_entities: AddConfigEntryEntitiesCallback, ) -> list[QbusEntity]:
) -> None: """Create entities for new outputs."""
"""Call async_add_entities for new outputs."""
new_outputs = determine_new_outputs(coordinator, added_outputs, filter_fn)
return [entity_type(output) for output in new_outputs]
def determine_new_outputs(
coordinator: QbusControllerCoordinator,
added_outputs: list[QbusMqttOutput],
filter_fn: Callable[[QbusMqttOutput], bool],
) -> list[QbusMqttOutput]:
"""Determine new outputs."""
added_ref_ids = {k.ref_id for k in added_outputs} added_ref_ids = {k.ref_id for k in added_outputs}
@ -43,7 +52,8 @@ def add_new_outputs(
if new_outputs: if new_outputs:
added_outputs.extend(new_outputs) added_outputs.extend(new_outputs)
async_add_entities([entity_type(output) for output in new_outputs])
return new_outputs
def format_ref_id(ref_id: str) -> str | None: def format_ref_id(ref_id: str) -> str | None:
@ -67,7 +77,13 @@ class QbusEntity(Entity, Generic[StateT], ABC):
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_should_poll = False _attr_should_poll = False
def __init__(self, mqtt_output: QbusMqttOutput) -> None: def __init__(
self,
mqtt_output: QbusMqttOutput,
*,
id_suffix: str = "",
link_to_main_device: bool = False,
) -> None:
"""Initialize the Qbus entity.""" """Initialize the Qbus entity."""
self._mqtt_output = mqtt_output self._mqtt_output = mqtt_output
@ -79,17 +95,25 @@ class QbusEntity(Entity, Generic[StateT], ABC):
) )
ref_id = format_ref_id(mqtt_output.ref_id) ref_id = format_ref_id(mqtt_output.ref_id)
unique_id = f"ctd_{mqtt_output.device.serial_number}_{ref_id}"
self._attr_unique_id = f"ctd_{mqtt_output.device.serial_number}_{ref_id}" if id_suffix:
unique_id += f"_{id_suffix}"
# Create linked device self._attr_unique_id = unique_id
self._attr_device_info = DeviceInfo(
name=mqtt_output.name.title(), if link_to_main_device:
manufacturer=MANUFACTURER, self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{mqtt_output.device.serial_number}_{ref_id}")}, identifiers={create_main_device_identifier(mqtt_output)}
suggested_area=mqtt_output.location.title(), )
via_device=create_main_device_identifier(mqtt_output), else:
) self._attr_device_info = DeviceInfo(
name=mqtt_output.name.title(),
manufacturer=MANUFACTURER,
identifiers={(DOMAIN, f"{mqtt_output.device.serial_number}_{ref_id}")},
suggested_area=mqtt_output.location.title(),
via_device=create_main_device_identifier(mqtt_output),
)
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass.""" """Run when entity about to be added to hass."""

View File

@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.color import brightness_to_value, value_to_brightness from homeassistant.util.color import brightness_to_value, value_to_brightness
from .coordinator import QbusConfigEntry from .coordinator import QbusConfigEntry
from .entity import QbusEntity, add_new_outputs from .entity import QbusEntity, create_new_entities
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -27,13 +27,13 @@ async def async_setup_entry(
added_outputs: list[QbusMqttOutput] = [] added_outputs: list[QbusMqttOutput] = []
def _check_outputs() -> None: def _check_outputs() -> None:
add_new_outputs( entities = create_new_entities(
coordinator, coordinator,
added_outputs, added_outputs,
lambda output: output.type == "analog", lambda output: output.type == "analog",
QbusLight, QbusLight,
async_add_entities,
) )
async_add_entities(entities)
_check_outputs() _check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs)) entry.async_on_unload(coordinator.async_add_listener(_check_outputs))

View File

@ -7,6 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/qbus", "documentation": "https://www.home-assistant.io/integrations/qbus",
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["qbusmqttapi"],
"mqtt": [ "mqtt": [
"cloudapp/QBUSMQTTGW/state", "cloudapp/QBUSMQTTGW/state",
"cloudapp/QBUSMQTTGW/config", "cloudapp/QBUSMQTTGW/config",

View File

@ -7,11 +7,10 @@ from qbusmqttapi.state import QbusMqttState, StateAction, StateType
from homeassistant.components.scene import Scene from homeassistant.components.scene import Scene
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import QbusConfigEntry from .coordinator import QbusConfigEntry
from .entity import QbusEntity, add_new_outputs, create_main_device_identifier from .entity import QbusEntity, create_new_entities
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -27,13 +26,13 @@ async def async_setup_entry(
added_outputs: list[QbusMqttOutput] = [] added_outputs: list[QbusMqttOutput] = []
def _check_outputs() -> None: def _check_outputs() -> None:
add_new_outputs( entities = create_new_entities(
coordinator, coordinator,
added_outputs, added_outputs,
lambda output: output.type == "scene", lambda output: output.type == "scene",
QbusScene, QbusScene,
async_add_entities,
) )
async_add_entities(entities)
_check_outputs() _check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs)) entry.async_on_unload(coordinator.async_add_listener(_check_outputs))
@ -45,12 +44,8 @@ class QbusScene(QbusEntity, Scene):
def __init__(self, mqtt_output: QbusMqttOutput) -> None: def __init__(self, mqtt_output: QbusMqttOutput) -> None:
"""Initialize scene entity.""" """Initialize scene entity."""
super().__init__(mqtt_output) super().__init__(mqtt_output, link_to_main_device=True)
# Add to main controller device
self._attr_device_info = DeviceInfo(
identifiers={create_main_device_identifier(mqtt_output)}
)
self._attr_name = mqtt_output.name.title() self._attr_name = mqtt_output.name.title()
async def async_activate(self, **kwargs: Any) -> None: async def async_activate(self, **kwargs: Any) -> None:

View File

@ -0,0 +1,378 @@
"""Support for Qbus sensor."""
from dataclasses import dataclass
from qbusmqttapi.discovery import QbusMqttOutput
from qbusmqttapi.state import (
GaugeStateProperty,
QbusMqttGaugeState,
QbusMqttHumidityState,
QbusMqttThermoState,
QbusMqttVentilationState,
QbusMqttWeatherState,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfLength,
UnitOfPower,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import QbusConfigEntry
from .entity import QbusEntity, create_new_entities, determine_new_outputs
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class QbusWeatherDescription(SensorEntityDescription):
"""Description for Qbus weather entities."""
property: str
_WEATHER_DESCRIPTIONS = (
QbusWeatherDescription(
key="daylight",
property="dayLight",
translation_key="daylight",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=LIGHT_LUX,
),
QbusWeatherDescription(
key="light",
property="light",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=LIGHT_LUX,
),
QbusWeatherDescription(
key="light_east",
property="lightEast",
translation_key="light_east",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=LIGHT_LUX,
),
QbusWeatherDescription(
key="light_south",
property="lightSouth",
translation_key="light_south",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=LIGHT_LUX,
),
QbusWeatherDescription(
key="light_west",
property="lightWest",
translation_key="light_west",
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=LIGHT_LUX,
),
QbusWeatherDescription(
key="temperature",
property="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
QbusWeatherDescription(
key="wind",
property="wind",
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
),
)
_GAUGE_VARIANT_DESCRIPTIONS = {
"AIRPRESSURE": SensorEntityDescription(
key="airpressure",
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
),
"AIRQUALITY": SensorEntityDescription(
key="airquality",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"CURRENT": SensorEntityDescription(
key="current",
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
),
"ENERGY": SensorEntityDescription(
key="energy",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
state_class=SensorStateClass.TOTAL,
),
"GAS": SensorEntityDescription(
key="gas",
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
"GASFLOW": SensorEntityDescription(
key="gasflow",
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
"HUMIDITY": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
"LIGHT": SensorEntityDescription(
key="light",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
),
"LOUDNESS": SensorEntityDescription(
key="loudness",
device_class=SensorDeviceClass.SOUND_PRESSURE,
native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
state_class=SensorStateClass.MEASUREMENT,
),
"POWER": SensorEntityDescription(
key="power",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT,
state_class=SensorStateClass.MEASUREMENT,
),
"PRESSURE": SensorEntityDescription(
key="pressure",
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.KPA,
state_class=SensorStateClass.MEASUREMENT,
),
"TEMPERATURE": SensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
"VOLTAGE": SensorEntityDescription(
key="voltage",
device_class=SensorDeviceClass.VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
),
"VOLUME": SensorEntityDescription(
key="volume",
device_class=SensorDeviceClass.VOLUME_STORAGE,
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.MEASUREMENT,
),
"WATER": SensorEntityDescription(
key="water",
device_class=SensorDeviceClass.WATER,
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.TOTAL,
),
"WATERFLOW": SensorEntityDescription(
key="waterflow",
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
native_unit_of_measurement=UnitOfVolumeFlowRate.LITERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
"WATERLEVEL": SensorEntityDescription(
key="waterlevel",
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.METERS,
state_class=SensorStateClass.MEASUREMENT,
),
"WATERPRESSURE": SensorEntityDescription(
key="waterpressure",
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
),
"WIND": SensorEntityDescription(
key="wind",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
}
def _is_gauge_with_variant(output: QbusMqttOutput) -> bool:
return (
output.type == "gauge"
and isinstance(output.variant, str)
and _GAUGE_VARIANT_DESCRIPTIONS.get(output.variant.upper()) is not None
)
def _is_ventilation_with_co2(output: QbusMqttOutput) -> bool:
return output.type == "ventilation" and output.properties.get("co2") is not None
async def async_setup_entry(
hass: HomeAssistant,
entry: QbusConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up sensor entities."""
coordinator = entry.runtime_data
added_outputs: list[QbusMqttOutput] = []
def _create_weather_entities() -> list[QbusEntity]:
new_outputs = determine_new_outputs(
coordinator, added_outputs, lambda output: output.type == "weatherstation"
)
return [
QbusWeatherSensor(output, description)
for output in new_outputs
for description in _WEATHER_DESCRIPTIONS
]
def _check_outputs() -> None:
entities: list[QbusEntity] = [
*create_new_entities(
coordinator,
added_outputs,
_is_gauge_with_variant,
QbusGaugeVariantSensor,
),
*create_new_entities(
coordinator,
added_outputs,
lambda output: output.type == "humidity",
QbusHumiditySensor,
),
*create_new_entities(
coordinator,
added_outputs,
lambda output: output.type == "thermo",
QbusThermoSensor,
),
*create_new_entities(
coordinator,
added_outputs,
_is_ventilation_with_co2,
QbusVentilationSensor,
),
*_create_weather_entities(),
]
async_add_entities(entities)
_check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs))
class QbusGaugeVariantSensor(QbusEntity, SensorEntity):
"""Representation of a Qbus sensor entity for gauges with variant."""
_state_cls = QbusMqttGaugeState
_attr_name = None
_attr_suggested_display_precision = 2
def __init__(self, mqtt_output: QbusMqttOutput) -> None:
"""Initialize sensor entity."""
super().__init__(mqtt_output)
variant = str(mqtt_output.variant)
self.entity_description = _GAUGE_VARIANT_DESCRIPTIONS[variant.upper()]
async def _handle_state_received(self, state: QbusMqttGaugeState) -> None:
self._attr_native_value = state.read_value(GaugeStateProperty.CURRENT_VALUE)
class QbusHumiditySensor(QbusEntity, SensorEntity):
"""Representation of a Qbus sensor entity for humidity modules."""
_state_cls = QbusMqttHumidityState
_attr_device_class = SensorDeviceClass.HUMIDITY
_attr_name = None
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
async def _handle_state_received(self, state: QbusMqttHumidityState) -> None:
self._attr_native_value = state.read_value()
class QbusThermoSensor(QbusEntity, SensorEntity):
"""Representation of a Qbus sensor entity for thermostats."""
_state_cls = QbusMqttThermoState
_attr_device_class = SensorDeviceClass.TEMPERATURE
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
_attr_state_class = SensorStateClass.MEASUREMENT
async def _handle_state_received(self, state: QbusMqttThermoState) -> None:
self._attr_native_value = state.read_current_temperature()
class QbusVentilationSensor(QbusEntity, SensorEntity):
"""Representation of a Qbus sensor entity for ventilations."""
_state_cls = QbusMqttVentilationState
_attr_device_class = SensorDeviceClass.CO2
_attr_name = None
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_suggested_display_precision = 0
async def _handle_state_received(self, state: QbusMqttVentilationState) -> None:
self._attr_native_value = state.read_co2()
class QbusWeatherSensor(QbusEntity, SensorEntity):
"""Representation of a Qbus weather sensor."""
_state_cls = QbusMqttWeatherState
entity_description: QbusWeatherDescription
def __init__(
self, mqtt_output: QbusMqttOutput, description: QbusWeatherDescription
) -> None:
"""Initialize sensor entity."""
super().__init__(mqtt_output, id_suffix=description.key)
self.entity_description = description
if description.key == "temperature":
self._attr_name = None
async def _handle_state_received(self, state: QbusMqttWeatherState) -> None:
if value := state.read_property(self.entity_description.property, None):
self.native_value = value

View File

@ -16,6 +16,22 @@
"no_controller": "No controllers were found" "no_controller": "No controllers were found"
} }
}, },
"entity": {
"sensor": {
"daylight": {
"name": "Daylight"
},
"light_east": {
"name": "Illuminance east"
},
"light_south": {
"name": "Illuminance south"
},
"light_west": {
"name": "Illuminance west"
}
}
},
"exceptions": { "exceptions": {
"invalid_preset": { "invalid_preset": {
"message": "Preset mode \"{preset}\" is not valid. Valid preset modes are: {options}." "message": "Preset mode \"{preset}\" is not valid. Valid preset modes are: {options}."

View File

@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import QbusConfigEntry from .coordinator import QbusConfigEntry
from .entity import QbusEntity, add_new_outputs from .entity import QbusEntity, create_new_entities
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@ -26,13 +26,13 @@ async def async_setup_entry(
added_outputs: list[QbusMqttOutput] = [] added_outputs: list[QbusMqttOutput] = []
def _check_outputs() -> None: def _check_outputs() -> None:
add_new_outputs( entities = create_new_entities(
coordinator, coordinator,
added_outputs, added_outputs,
lambda output: output.type == "onoff", lambda output: output.type == "onoff",
QbusSwitch, QbusSwitch,
async_add_entities,
) )
async_add_entities(entities)
_check_outputs() _check_outputs()
entry.async_on_unload(coordinator.async_add_listener(_check_outputs)) entry.async_on_unload(coordinator.async_add_listener(_check_outputs))

View File

@ -1,6 +1,6 @@
"""Test fixtures for qbus.""" """Test fixtures for qbus."""
from collections.abc import Generator from collections.abc import Awaitable, Callable, Generator
import json import json
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
@ -64,3 +64,22 @@ async def setup_integration(
async_fire_mqtt_message(hass, TOPIC_CONFIG, json.dumps(payload_config)) async_fire_mqtt_message(hass, TOPIC_CONFIG, json.dumps(payload_config))
await hass.async_block_till_done() await hass.async_block_till_done()
@pytest.fixture
async def setup_integration_deferred(
hass: HomeAssistant,
mqtt_mock: MqttMockHAClient,
mock_config_entry: MockConfigEntry,
payload_config: JsonObjectType,
) -> Callable[[], Awaitable]:
"""Set up the integration."""
async def run() -> None:
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
async_fire_mqtt_message(hass, TOPIC_CONFIG, json.dumps(payload_config))
await hass.async_block_till_done()
return run

View File

@ -116,7 +116,7 @@
{ {
"id": "UL30", "id": "UL30",
"location": "Guest bedroom", "location": "Guest bedroom",
"locationId": 0, "locationId": 3,
"name": "CURTAINS", "name": "CURTAINS",
"originalName": "CURTAINS", "originalName": "CURTAINS",
"refId": "000001/108", "refId": "000001/108",
@ -144,7 +144,7 @@
}, },
"id": "UL31", "id": "UL31",
"location": "Living", "location": "Living",
"locationId": 8, "locationId": 0,
"name": "SLATS", "name": "SLATS",
"originalName": "SLATS", "originalName": "SLATS",
"properties": { "properties": {
@ -183,6 +183,340 @@
}, },
"refId": "000001/4", "refId": "000001/4",
"type": "shutter" "type": "shutter"
},
{
"id": "UL40",
"location": "Tuin",
"locationId": 12,
"name": "Luchtdruk",
"originalName": "Luchtdruk",
"refId": "000001/81",
"type": "gauge",
"variant": "AirPressure",
"actions": {},
"properties": {
"currentValue": {
"max": 1500,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "mbar",
"write": false
}
}
},
{
"id": "UL41",
"location": "Tuin",
"locationId": 12,
"name": "Luchtkwaliteit",
"originalName": "Luchtkwaliteit",
"refId": "000001/82",
"type": "gauge",
"variant": "AirQuality",
"actions": {},
"properties": {
"currentValue": {
"max": 1500,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "ppm",
"write": false
}
}
},
{
"id": "UL42",
"location": "Garage",
"locationId": 27,
"name": "Stroom",
"originalName": "Stroom",
"refId": "000001/83",
"type": "gauge",
"variant": "Current",
"actions": {},
"properties": {
"currentValue": {
"max": 100,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "kWh",
"write": false
}
}
},
{
"id": "UL43",
"location": "Garage",
"locationId": 27,
"name": "Energie",
"originalName": "Energie",
"refId": "000001/84",
"type": "gauge",
"variant": "Energy",
"actions": {},
"properties": {
"currentValue": {
"read": true,
"step": 0.1,
"type": "number",
"unit": "A",
"write": false
}
}
},
{
"id": "UL44",
"location": "Garage",
"locationId": 27,
"name": "Gas",
"originalName": "Gas",
"refId": "000001/85",
"type": "gauge",
"variant": "Gas",
"actions": {},
"properties": {
"currentValue": {
"max": 5,
"min": 0,
"read": true,
"step": 0.001,
"type": "number",
"unit": "m³/h",
"write": false
}
}
},
{
"id": "UL45",
"location": "Garage",
"locationId": 27,
"name": "Gas flow",
"originalName": "Gas flow",
"refId": "000001/86",
"type": "gauge",
"variant": "GasFlow",
"actions": {},
"properties": {
"currentValue": {
"max": 10,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "m³/h",
"write": false
}
}
},
{
"id": "UL46",
"location": "Living",
"locationId": 0,
"name": "Vochtigheid living",
"originalName": "Vochtigheid living",
"refId": "000001/87",
"type": "gauge",
"variant": "Humidity",
"actions": {},
"properties": {
"currentValue": {
"max": 100,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "%",
"write": false
}
}
},
{
"id": "UL47",
"location": "Tuin",
"locationId": 12,
"name": "Lichtsterkte tuin",
"originalName": "Lichtsterkte tuin",
"refId": "000001/88",
"type": "gauge",
"variant": "Light",
"actions": {},
"properties": {
"currentValue": {
"max": 100000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "lx",
"write": false
}
}
},
{
"id": "UL40",
"location": "Tuin",
"locationId": 12,
"name": "Regenput",
"originalName": "Regenput",
"refId": "000001/40",
"type": "gauge",
"variant": "WaterLevel",
"actions": {},
"properties": {
"currentValue": {
"max": 100,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"unit": "m",
"write": false
}
}
},
{
"id": "UL60",
"location": "Tuin",
"locationId": 12,
"name": "Weersensor",
"originalName": "Weersensor",
"refId": "000001/21007",
"type": "weatherstation",
"variant": [null],
"actions": {},
"properties": {
"dayLight": {
"max": 1000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"light": {
"max": 100000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"lightEast": {
"max": 100000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"lightSouth": {
"max": 100000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"lightWest": {
"max": 100000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"raining": {
"read": true,
"type": "boolean",
"write": false
},
"temperature": {
"max": 100,
"min": -100,
"read": true,
"step": 0.1,
"type": "number",
"write": false
},
"twilight": {
"read": true,
"type": "boolean",
"write": false
},
"wind": {
"max": 1000,
"min": 0,
"read": true,
"step": 0.1,
"type": "number",
"write": false
}
}
},
{
"id": "UL70",
"location": "",
"locationId": 0,
"name": "Luchtsensor",
"originalName": "Luchtsensor",
"refId": "000001/224",
"type": "ventilation",
"variant": [null],
"actions": {},
"properties": {
"co2": {
"max": 5000,
"min": 0,
"read": true,
"step": 16,
"type": "number",
"unit": "ppm",
"write": false
},
"currRegime": {
"enumValues": ["Manueel", "Nacht", "Boost", "Uit", "Auto"],
"read": true,
"type": "enumString",
"write": true
},
"refresh": {
"max": 100,
"min": 0,
"read": true,
"step": 1,
"type": "number",
"write": true
}
}
},
{
"id": "UL80",
"location": "Kitchen",
"locationId": 8,
"name": "Vochtigheid keuken",
"originalName": "Vochtigheid keuken",
"properties": {
"currRegime": {
"enumValues": ["Manual", "Cook", "Boost", "Off", "Auto"],
"read": true,
"type": "enumString",
"write": true
},
"value": {
"read": true,
"step": 1,
"type": "percent",
"write": false
}
},
"refId": "000001/94/1",
"type": "humidity"
} }
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
"""Test Qbus sensors."""
from collections.abc import Awaitable, Callable
from unittest.mock import patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
async def test_sensor(
hass: HomeAssistant,
setup_integration_deferred: Callable[[], Awaitable],
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test sensor."""
with patch("homeassistant.components.qbus.PLATFORMS", [Platform.SENSOR]):
await setup_integration_deferred()
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)