mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Merge branch 'dev' into whirlpool_sensor_door_remove
This commit is contained in:
commit
f07ee5d972
@ -29,6 +29,7 @@ PLATFORMS: list[Platform] = [
|
|||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
Platform.BUTTON,
|
Platform.BUTTON,
|
||||||
Platform.CLIMATE,
|
Platform.CLIMATE,
|
||||||
|
Platform.FAN,
|
||||||
Platform.LOCK,
|
Platform.LOCK,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
@ -51,6 +52,7 @@ class SwitchbotDevices:
|
|||||||
sensors: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
sensors: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||||
vacuums: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
vacuums: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||||
locks: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
locks: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||||
|
fans: list[tuple[Device, SwitchBotCoordinator]] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -96,7 +98,6 @@ async def make_switchbot_devices(
|
|||||||
for device in devices
|
for device in devices
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return devices_data
|
return devices_data
|
||||||
|
|
||||||
|
|
||||||
@ -177,6 +178,16 @@ async def make_device_data(
|
|||||||
else:
|
else:
|
||||||
devices_data.switches.append((device, coordinator))
|
devices_data.switches.append((device, coordinator))
|
||||||
|
|
||||||
|
if isinstance(device, Device) and device.device_type in [
|
||||||
|
"Battery Circulator Fan",
|
||||||
|
"Circulator Fan",
|
||||||
|
]:
|
||||||
|
coordinator = await coordinator_for_device(
|
||||||
|
hass, entry, api, device, coordinators_by_id
|
||||||
|
)
|
||||||
|
devices_data.fans.append((device, coordinator))
|
||||||
|
devices_data.sensors.append((device, coordinator))
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up SwitchBot via API from a config entry."""
|
"""Set up SwitchBot via API from a config entry."""
|
||||||
|
120
homeassistant/components/switchbot_cloud/fan.py
Normal file
120
homeassistant/components/switchbot_cloud/fan.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
"""Support for the Switchbot Battery Circulator fan."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from switchbot_api import (
|
||||||
|
BatteryCirculatorFanCommands,
|
||||||
|
BatteryCirculatorFanMode,
|
||||||
|
CommonCommands,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
|
from . import SwitchbotCloudData
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entity import SwitchBotCloudEntity
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up SwitchBot Cloud entry."""
|
||||||
|
data: SwitchbotCloudData = hass.data[DOMAIN][config.entry_id]
|
||||||
|
async_add_entities(
|
||||||
|
SwitchBotCloudFan(data.api, device, coordinator)
|
||||||
|
for device, coordinator in data.devices.fans
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchBotCloudFan(SwitchBotCloudEntity, FanEntity):
|
||||||
|
"""Representation of a SwitchBot Battery Circulator Fan."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
_attr_supported_features = (
|
||||||
|
FanEntityFeature.SET_SPEED
|
||||||
|
| FanEntityFeature.PRESET_MODE
|
||||||
|
| FanEntityFeature.TURN_OFF
|
||||||
|
| FanEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
_attr_preset_modes = list(BatteryCirculatorFanMode)
|
||||||
|
|
||||||
|
_attr_is_on: bool | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return true if the entity is on."""
|
||||||
|
return self._attr_is_on
|
||||||
|
|
||||||
|
def _set_attributes(self) -> None:
|
||||||
|
"""Set attributes from coordinator data."""
|
||||||
|
if self.coordinator.data is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
power: str = self.coordinator.data["power"]
|
||||||
|
mode: str = self.coordinator.data["mode"]
|
||||||
|
fan_speed: str = self.coordinator.data["fanSpeed"]
|
||||||
|
self._attr_is_on = power == "on"
|
||||||
|
self._attr_preset_mode = mode
|
||||||
|
self._attr_percentage = int(fan_speed)
|
||||||
|
self._attr_supported_features = (
|
||||||
|
FanEntityFeature.PRESET_MODE
|
||||||
|
| FanEntityFeature.TURN_OFF
|
||||||
|
| FanEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
if self.is_on and self.preset_mode == BatteryCirculatorFanMode.DIRECT.value:
|
||||||
|
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||||
|
|
||||||
|
async def async_turn_on(
|
||||||
|
self,
|
||||||
|
percentage: int | None = None,
|
||||||
|
preset_mode: str | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Turn on the fan."""
|
||||||
|
await self.send_api_command(CommonCommands.ON)
|
||||||
|
await self.send_api_command(
|
||||||
|
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||||
|
parameters=str(self.preset_mode),
|
||||||
|
)
|
||||||
|
if self.preset_mode == BatteryCirculatorFanMode.DIRECT.value:
|
||||||
|
await self.send_api_command(
|
||||||
|
command=BatteryCirculatorFanCommands.SET_WIND_SPEED,
|
||||||
|
parameters=str(self.percentage),
|
||||||
|
)
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn off the fan."""
|
||||||
|
await self.send_api_command(CommonCommands.OFF)
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
|
"""Set the speed of the fan, as a percentage."""
|
||||||
|
await self.send_api_command(
|
||||||
|
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||||
|
parameters=str(BatteryCirculatorFanMode.DIRECT.value),
|
||||||
|
)
|
||||||
|
await self.send_api_command(
|
||||||
|
command=BatteryCirculatorFanCommands.SET_WIND_SPEED,
|
||||||
|
parameters=str(percentage),
|
||||||
|
)
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set new preset mode."""
|
||||||
|
await self.send_api_command(
|
||||||
|
command=BatteryCirculatorFanCommands.SET_WIND_MODE,
|
||||||
|
parameters=preset_mode,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await self.coordinator.async_request_refresh()
|
@ -91,6 +91,7 @@ CO2_DESCRIPTION = SensorEntityDescription(
|
|||||||
|
|
||||||
SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES = {
|
SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES = {
|
||||||
"Bot": (BATTERY_DESCRIPTION,),
|
"Bot": (BATTERY_DESCRIPTION,),
|
||||||
|
"Battery Circulator Fan": (BATTERY_DESCRIPTION,),
|
||||||
"Meter": (
|
"Meter": (
|
||||||
TEMPERATURE_DESCRIPTION,
|
TEMPERATURE_DESCRIPTION,
|
||||||
HUMIDITY_DESCRIPTION,
|
HUMIDITY_DESCRIPTION,
|
||||||
|
@ -165,6 +165,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data[DOMAIN][entry.entry_id] = YoLinkHomeStore(
|
hass.data[DOMAIN][entry.entry_id] = YoLinkHomeStore(
|
||||||
yolink_home, device_coordinators
|
yolink_home, device_coordinators
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Clean up yolink devices which are not associated to the account anymore.
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device_entries = dr.async_entries_for_config_entry(device_registry, entry.entry_id)
|
||||||
|
for device_entry in device_entries:
|
||||||
|
for identifier in device_entry.identifiers:
|
||||||
|
if (
|
||||||
|
identifier[0] == DOMAIN
|
||||||
|
and device_coordinators.get(identifier[1]) is None
|
||||||
|
):
|
||||||
|
device_registry.async_update_device(
|
||||||
|
device_entry.id, remove_config_entry_id=entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async def async_yolink_unload(event) -> None:
|
async def async_yolink_unload(event) -> None:
|
||||||
|
187
tests/components/switchbot_cloud/test_fan.py
Normal file
187
tests/components/switchbot_cloud/test_fan.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
"""Test for the Switchbot Battery Circulator Fan."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from switchbot_api import Device, SwitchBotAPI
|
||||||
|
|
||||||
|
from homeassistant.components.fan import (
|
||||||
|
ATTR_PERCENTAGE,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
DOMAIN as FAN_DOMAIN,
|
||||||
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_ON,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import configure_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coordinator_data_is_none(
|
||||||
|
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator data is none."""
|
||||||
|
mock_list_devices.return_value = [
|
||||||
|
Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock_get_status.side_effect = [
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_id = "fan.battery_fan_1"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_turn_on(hass: HomeAssistant, mock_list_devices, mock_get_status) -> None:
|
||||||
|
"""Test turning on the fan."""
|
||||||
|
mock_list_devices.return_value = [
|
||||||
|
Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock_get_status.side_effect = [
|
||||||
|
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
]
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_id = "fan.battery_fan_1"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
mock_send_command.assert_called()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
|
async def test_turn_off(
|
||||||
|
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||||
|
) -> None:
|
||||||
|
"""Test turning off the fan."""
|
||||||
|
mock_list_devices.return_value = [
|
||||||
|
Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock_get_status.side_effect = [
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "off", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
]
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_id = "fan.battery_fan_1"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
mock_send_command.assert_called()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_percentage(
|
||||||
|
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||||
|
) -> None:
|
||||||
|
"""Test set percentage."""
|
||||||
|
mock_list_devices.return_value = [
|
||||||
|
Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock_get_status.side_effect = [
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "off", "mode": "direct", "fanSpeed": "5"},
|
||||||
|
]
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_id = "fan.battery_fan_1"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_PERCENTAGE,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_PERCENTAGE: 5},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_send_command.assert_called()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_set_preset_mode(
|
||||||
|
hass: HomeAssistant, mock_list_devices, mock_get_status
|
||||||
|
) -> None:
|
||||||
|
"""Test set preset mode."""
|
||||||
|
mock_list_devices.return_value = [
|
||||||
|
Device(
|
||||||
|
version="V1.0",
|
||||||
|
deviceId="battery-fan-id-1",
|
||||||
|
deviceName="battery-fan-1",
|
||||||
|
deviceType="Battery Circulator Fan",
|
||||||
|
hubDeviceId="test-hub-id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
mock_get_status.side_effect = [
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "on", "mode": "direct", "fanSpeed": "0"},
|
||||||
|
{"power": "on", "mode": "baby", "fanSpeed": "0"},
|
||||||
|
]
|
||||||
|
entry = await configure_integration(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
entity_id = "fan.battery_fan_1"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
with patch.object(SwitchBotAPI, "send_command") as mock_send_command:
|
||||||
|
await hass.services.async_call(
|
||||||
|
FAN_DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "baby"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_send_command.assert_called_once()
|
@ -75,6 +75,14 @@ DEVICE_MOCKS = {
|
|||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
],
|
],
|
||||||
|
"qxj_temp_humidity_external_probe": [
|
||||||
|
# https://github.com/home-assistant/core/issues/136472
|
||||||
|
Platform.SENSOR,
|
||||||
|
],
|
||||||
|
"qxj_weather_station": [
|
||||||
|
# https://github.com/orgs/home-assistant/discussions/318
|
||||||
|
Platform.SENSOR,
|
||||||
|
],
|
||||||
"rqbj_gas_sensor": [
|
"rqbj_gas_sensor": [
|
||||||
# https://github.com/orgs/home-assistant/discussions/100
|
# https://github.com/orgs/home-assistant/discussions/100
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"endpoint": "https://apigw.tuyaeu.com",
|
||||||
|
"terminal_id": "1708196692712PHOeqy",
|
||||||
|
"mqtt_connected": true,
|
||||||
|
"disabled_by": null,
|
||||||
|
"disabled_polling": false,
|
||||||
|
"id": "bff00f6abe0563b284t77p",
|
||||||
|
"name": "Frysen",
|
||||||
|
"category": "qxj",
|
||||||
|
"product_id": "is2indt9nlth6esa",
|
||||||
|
"product_name": "T & H Sensor with external probe",
|
||||||
|
"online": true,
|
||||||
|
"sub": false,
|
||||||
|
"time_zone": "+01:00",
|
||||||
|
"active_time": "2025-01-27T15:19:27+00:00",
|
||||||
|
"create_time": "2025-01-27T15:19:27+00:00",
|
||||||
|
"update_time": "2025-01-27T15:19:27+00:00",
|
||||||
|
"function": {},
|
||||||
|
"status_range": {
|
||||||
|
"temp_current": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -99,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_value": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_state": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "middle", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_current_external": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 1200,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"temp_current": 222,
|
||||||
|
"humidity_value": 38,
|
||||||
|
"battery_state": "high",
|
||||||
|
"temp_current_external": -130
|
||||||
|
},
|
||||||
|
"set_up": false,
|
||||||
|
"support_local": true
|
||||||
|
}
|
412
tests/components/tuya/fixtures/qxj_weather_station.json
Normal file
412
tests/components/tuya/fixtures/qxj_weather_station.json
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
{
|
||||||
|
"endpoint": "https://apigw.tuyaeu.com",
|
||||||
|
"terminal_id": "1751921699759JsVujI",
|
||||||
|
"mqtt_connected": true,
|
||||||
|
"disabled_by": null,
|
||||||
|
"disabled_polling": false,
|
||||||
|
"id": "bf84c743a84eb2c8abeurz",
|
||||||
|
"name": "BR 7-in-1 WLAN Wetterstation Anthrazit",
|
||||||
|
"category": "qxj",
|
||||||
|
"product_id": "fsea1lat3vuktbt6",
|
||||||
|
"product_name": "BR 7-in-1 WLAN Wetterstation Anthrazit",
|
||||||
|
"online": true,
|
||||||
|
"sub": false,
|
||||||
|
"time_zone": "+02:00",
|
||||||
|
"active_time": "2025-07-07T17:43:41+00:00",
|
||||||
|
"create_time": "2025-07-07T17:43:41+00:00",
|
||||||
|
"update_time": "2025-07-07T17:43:41+00:00",
|
||||||
|
"function": {
|
||||||
|
"temp_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["c", "f"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windspeed_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["mph"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pressure_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["hpa", "inhg", "mmhg"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rain_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["mm", "inch"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bright_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["lux", "fc", "wm2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status_range": {
|
||||||
|
"temp_current": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_value": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_state": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["c", "f"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windspeed_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["mph"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pressure_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["hpa", "inhg", "mmhg"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rain_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["mm", "inch"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bright_unit_convert": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["lux", "fc", "wm2"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fault_type": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": [
|
||||||
|
"normal",
|
||||||
|
"ch1_offline",
|
||||||
|
"ch2_offline",
|
||||||
|
"ch3_offline",
|
||||||
|
"offline"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_status": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_state_1": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_state_2": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"battery_state_3": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["low", "high"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_current_external": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_outdoor": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_current_external_1": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_outdoor_1": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_current_external_2": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_outdoor_2": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temp_current_external_3": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"humidity_outdoor_3": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "%",
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"atmospheric_pressture": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "hPa",
|
||||||
|
"min": 3000,
|
||||||
|
"max": 12000,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pressure_drop": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "hPa",
|
||||||
|
"min": 0,
|
||||||
|
"max": 15,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windspeed_avg": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "m/s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 700,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windspeed_gust": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "m/s",
|
||||||
|
"min": 0,
|
||||||
|
"max": 700,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wind_direct": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": [
|
||||||
|
"north",
|
||||||
|
"north_north_east",
|
||||||
|
"north_east",
|
||||||
|
"east_north_east",
|
||||||
|
"east",
|
||||||
|
"east_south_east",
|
||||||
|
"south_east",
|
||||||
|
"south_south_east",
|
||||||
|
"south",
|
||||||
|
"south_south_west",
|
||||||
|
"south_west",
|
||||||
|
"west_south_west",
|
||||||
|
"west",
|
||||||
|
"west_north_west",
|
||||||
|
"north_west",
|
||||||
|
"north_north_west"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rain_24h": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "mm",
|
||||||
|
"min": 0,
|
||||||
|
"max": 1000000,
|
||||||
|
"scale": 3,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rain_rate": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "mm",
|
||||||
|
"min": 0,
|
||||||
|
"max": 999999,
|
||||||
|
"scale": 3,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uv_index": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "",
|
||||||
|
"min": 0,
|
||||||
|
"max": 180,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bright_value": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "lux",
|
||||||
|
"min": 0,
|
||||||
|
"max": 238000,
|
||||||
|
"scale": 0,
|
||||||
|
"step": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dew_point_temp": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -400,
|
||||||
|
"max": 800,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feellike_temp": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -650,
|
||||||
|
"max": 500,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heat_index": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": 260,
|
||||||
|
"max": 500,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windchill_index": {
|
||||||
|
"type": "Integer",
|
||||||
|
"value": {
|
||||||
|
"unit": "\u2103",
|
||||||
|
"min": -650,
|
||||||
|
"max": 600,
|
||||||
|
"scale": 1,
|
||||||
|
"step": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"com_index": {
|
||||||
|
"type": "Enum",
|
||||||
|
"value": {
|
||||||
|
"range": ["moist", "dry", "comfortable"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"temp_current": 240,
|
||||||
|
"humidity_value": 52,
|
||||||
|
"battery_state": "high",
|
||||||
|
"temp_unit_convert": "c",
|
||||||
|
"windspeed_unit_convert": "m_s",
|
||||||
|
"pressure_unit_convert": "hpa",
|
||||||
|
"rain_unit_convert": "mm",
|
||||||
|
"bright_unit_convert": "lux",
|
||||||
|
"fault_type": "normal",
|
||||||
|
"battery_status": "low",
|
||||||
|
"battery_state_1": "high",
|
||||||
|
"battery_state_2": "high",
|
||||||
|
"battery_state_3": "low",
|
||||||
|
"temp_current_external": -400,
|
||||||
|
"humidity_outdoor": 0,
|
||||||
|
"temp_current_external_1": 193,
|
||||||
|
"humidity_outdoor_1": 99,
|
||||||
|
"temp_current_external_2": 252,
|
||||||
|
"humidity_outdoor_2": 0,
|
||||||
|
"temp_current_external_3": -400,
|
||||||
|
"humidity_outdoor_3": 0,
|
||||||
|
"atmospheric_pressture": 10040,
|
||||||
|
"pressure_drop": 0,
|
||||||
|
"windspeed_avg": 0,
|
||||||
|
"windspeed_gust": 0,
|
||||||
|
"wind_direct": "none",
|
||||||
|
"rain_24h": 0,
|
||||||
|
"rain_rate": 0,
|
||||||
|
"uv_index": 0,
|
||||||
|
"bright_value": 0,
|
||||||
|
"dew_point_temp": -400,
|
||||||
|
"feellike_temp": -650,
|
||||||
|
"heat_index": 260,
|
||||||
|
"windchill_index": -650,
|
||||||
|
"com_index": "none"
|
||||||
|
},
|
||||||
|
"set_up": false,
|
||||||
|
"support_local": true
|
||||||
|
}
|
@ -1265,6 +1265,485 @@
|
|||||||
'state': '100.0',
|
'state': '100.0',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_battery_state-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.frysen_battery_state',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Battery state',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'battery_state',
|
||||||
|
'unique_id': 'tuya.bff00f6abe0563b284t77pbattery_state',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_battery_state-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Frysen Battery state',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.frysen_battery_state',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'high',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_humidity-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.frysen_humidity',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Humidity',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'humidity',
|
||||||
|
'unique_id': 'tuya.bff00f6abe0563b284t77phumidity_value',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_humidity-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'humidity',
|
||||||
|
'friendly_name': 'Frysen Humidity',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.frysen_humidity',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '38.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_probe_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.frysen_probe_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Probe temperature',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'temperature_external',
|
||||||
|
'unique_id': 'tuya.bff00f6abe0563b284t77ptemp_current_external',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_probe_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Frysen Probe temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.frysen_probe_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '-13.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.frysen_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'temperature',
|
||||||
|
'unique_id': 'tuya.bff00f6abe0563b284t77ptemp_current',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_temp_humidity_external_probe][sensor.frysen_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Frysen Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.frysen_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '22.2',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_battery_state-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_battery_state',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Battery state',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'battery_state',
|
||||||
|
'unique_id': 'tuya.bf84c743a84eb2c8abeurzbattery_state',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_battery_state-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'BR 7-in-1 WLAN Wetterstation Anthrazit Battery state',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_battery_state',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'high',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_humidity-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_humidity',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Humidity',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'humidity',
|
||||||
|
'unique_id': 'tuya.bf84c743a84eb2c8abeurzhumidity_value',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_humidity-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'humidity',
|
||||||
|
'friendly_name': 'BR 7-in-1 WLAN Wetterstation Anthrazit Humidity',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_humidity',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '52.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_illuminance-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_illuminance',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ILLUMINANCE: 'illuminance'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Illuminance',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'illuminance',
|
||||||
|
'unique_id': 'tuya.bf84c743a84eb2c8abeurzbright_value',
|
||||||
|
'unit_of_measurement': 'lx',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_illuminance-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'illuminance',
|
||||||
|
'friendly_name': 'BR 7-in-1 WLAN Wetterstation Anthrazit Illuminance',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': 'lx',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_illuminance',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_probe_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_probe_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Probe temperature',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'temperature_external',
|
||||||
|
'unique_id': 'tuya.bf84c743a84eb2c8abeurztemp_current_external',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_probe_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'BR 7-in-1 WLAN Wetterstation Anthrazit Probe temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_probe_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '-40.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'temperature',
|
||||||
|
'unique_id': 'tuya.bf84c743a84eb2c8abeurztemp_current',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[qxj_weather_station][sensor.br_7_in_1_wlan_wetterstation_anthrazit_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'BR 7-in-1 WLAN Wetterstation Anthrazit Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.br_7_in_1_wlan_wetterstation_anthrazit_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '24.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_platform_setup_and_discovery[rqbj_gas_sensor][sensor.gas_sensor_gas-entry]
|
# name: test_platform_setup_and_discovery[rqbj_gas_sensor][sensor.gas_sensor_gas-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
77
tests/components/yolink/conftest.py
Normal file
77
tests/components/yolink/conftest.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""Provide common fixtures for the YoLink integration tests."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from yolink.home_manager import YoLinkHome
|
||||||
|
|
||||||
|
from homeassistant.components.application_credentials import (
|
||||||
|
ClientCredential,
|
||||||
|
async_import_client_credential,
|
||||||
|
)
|
||||||
|
from homeassistant.components.yolink.api import ConfigEntryAuth
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
CLIENT_ID = "12345"
|
||||||
|
CLIENT_SECRET = "6789"
|
||||||
|
DOMAIN = "yolink"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def setup_credentials(hass: HomeAssistant) -> None:
|
||||||
|
"""Fixture to setup credentials."""
|
||||||
|
assert await async_setup_component(hass, "application_credentials", {})
|
||||||
|
await async_import_client_credential(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_auth_manager")
|
||||||
|
def mock_auth_manager() -> Generator[MagicMock]:
|
||||||
|
"""Mock the authentication manager."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.yolink.api.ConfigEntryAuth", autospec=True
|
||||||
|
) as mock_auth:
|
||||||
|
mock_auth.return_value = MagicMock(spec=ConfigEntryAuth)
|
||||||
|
yield mock_auth
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_yolink_home")
|
||||||
|
def mock_yolink_home() -> Generator[AsyncMock]:
|
||||||
|
"""Mock YoLink home instance."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.yolink.YoLinkHome", autospec=True
|
||||||
|
) as mock_home:
|
||||||
|
mock_home.return_value = AsyncMock(spec=YoLinkHome)
|
||||||
|
yield mock_home
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
|
||||||
|
"""Mock a config entry for YoLink."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
unique_id=DOMAIN,
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="yolink",
|
||||||
|
data={
|
||||||
|
"auth_implementation": DOMAIN,
|
||||||
|
"token": {
|
||||||
|
"refresh_token": "mock-refresh-token",
|
||||||
|
"access_token": "mock-access-token",
|
||||||
|
"type": "Bearer",
|
||||||
|
"expires_in": 60,
|
||||||
|
"scope": "create",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options={},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
return config_entry
|
38
tests/components/yolink/test_init.py
Normal file
38
tests/components/yolink/test_init.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""Tests for the yolink integration."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.yolink import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_credentials", "mock_auth_manager", "mock_yolink_home")
|
||||||
|
async def test_device_remove_devices(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test we can only remove a device that no longer exists."""
|
||||||
|
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=mock_config_entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, "stale_device_id")},
|
||||||
|
)
|
||||||
|
device_entries = dr.async_entries_for_config_entry(
|
||||||
|
device_registry, mock_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(device_entries) == 1
|
||||||
|
device_entry = device_entries[0]
|
||||||
|
assert device_entry.identifiers == {(DOMAIN, "stale_device_id")}
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
device_entries = dr.async_entries_for_config_entry(
|
||||||
|
device_registry, mock_config_entry.entry_id
|
||||||
|
)
|
||||||
|
assert len(device_entries) == 0
|
Loading…
x
Reference in New Issue
Block a user