Add more Foscam switches (#147409)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Foscam-wangzhengyu
2025-08-12 04:14:32 +08:00
committed by GitHub
parent 9e398ffc10
commit e394435d7c
11 changed files with 763 additions and 78 deletions

View File

@@ -30,7 +30,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: FoscamConfigEntry) -> bo
verbose=False,
)
coordinator = FoscamCoordinator(hass, entry, session)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
@@ -89,7 +88,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: FoscamConfigEntry) ->
async def async_migrate_entities(hass: HomeAssistant, entry: FoscamConfigEntry) -> None:
"""Migrate old entry."""
"""Migrate old entries to support config_entry_id-based unique IDs."""
@callback
def _update_unique_id(

View File

@@ -11,3 +11,16 @@ CONF_STREAM = "stream"
SERVICE_PTZ = "ptz"
SERVICE_PTZ_PRESET = "ptz_preset"
SUPPORTED_SWITCHES = [
"flip_switch",
"mirror_switch",
"ir_switch",
"sleep_switch",
"white_light_switch",
"siren_alarm_switch",
"turn_off_volume_switch",
"light_status_switch",
"hdr_switch",
"wdr_switch",
]

View File

@@ -1,8 +1,8 @@
"""The foscam coordinator object."""
import asyncio
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
from libpyfoscamcgi import FoscamCamera
@@ -15,9 +15,35 @@ from .const import DOMAIN, LOGGER
type FoscamConfigEntry = ConfigEntry[FoscamCoordinator]
class FoscamCoordinator(DataUpdateCoordinator[dict[str, Any]]):
@dataclass
class FoscamDeviceInfo:
"""A data class representing the current state and configuration of a Foscam camera device."""
dev_info: dict
product_info: dict
is_open_ir: bool
is_flip: bool
is_mirror: bool
is_asleep: dict
is_open_white_light: bool
is_siren_alarm: bool
volume: int
speak_volume: int
is_turn_off_volume: bool
is_turn_off_light: bool
is_open_wdr: bool | None = None
is_open_hdr: bool | None = None
class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
"""Foscam coordinator."""
config_entry: FoscamConfigEntry
def __init__(
self,
hass: HomeAssistant,
@@ -34,24 +60,82 @@ class FoscamCoordinator(DataUpdateCoordinator[dict[str, Any]]):
)
self.session = session
async def _async_update_data(self) -> dict[str, Any]:
def gather_all_configs(self) -> FoscamDeviceInfo:
"""Get all Foscam configurations."""
ret_dev_info, dev_info = self.session.get_dev_info()
dev_info = dev_info if ret_dev_info == 0 else {}
ret_product_info, product_info = self.session.get_product_all_info()
product_info = product_info if ret_product_info == 0 else {}
ret_ir, infra_led_config = self.session.get_infra_led_config()
is_open_ir = infra_led_config["mode"] == "1" if ret_ir == 0 else False
ret_mf, mirror_flip_setting = self.session.get_mirror_and_flip_setting()
is_flip = mirror_flip_setting["isFlip"] == "1" if ret_mf == 0 else False
is_mirror = mirror_flip_setting["isMirror"] == "1" if ret_mf == 0 else False
ret_sleep, sleep_setting = self.session.is_asleep()
is_asleep = {"supported": ret_sleep == 0, "status": bool(int(sleep_setting))}
ret_wl, is_open_white_light = self.session.getWhiteLightBrightness()
is_open_white_light_val = (
is_open_white_light["enable"] == "1" if ret_wl == 0 else False
)
ret_sc, is_siren_alarm = self.session.getSirenConfig()
is_siren_alarm_val = (
is_siren_alarm["sirenEnable"] == "1" if ret_sc == 0 else False
)
ret_vol, volume = self.session.getAudioVolume()
volume_val = int(volume["volume"]) if ret_vol == 0 else 0
ret_sv, speak_volume = self.session.getSpeakVolume()
speak_volume_val = int(speak_volume["SpeakVolume"]) if ret_sv == 0 else 0
ret_ves, is_turn_off_volume = self.session.getVoiceEnableState()
is_turn_off_volume_val = not (
ret_ves == 0 and is_turn_off_volume["isEnable"] == "1"
)
ret_les, is_turn_off_light = self.session.getLedEnableState()
is_turn_off_light_val = not (
ret_les == 0 and is_turn_off_light["isEnable"] == "0"
)
is_open_wdr = None
is_open_hdr = None
reserve3 = product_info.get("reserve3")
reserve3_int = int(reserve3) if reserve3 is not None else 0
if (reserve3_int & (1 << 8)) != 0:
ret_wdr, is_open_wdr_data = self.session.getWdrMode()
mode = is_open_wdr_data["mode"] if ret_wdr == 0 and is_open_wdr_data else 0
is_open_wdr = bool(int(mode))
else:
ret_hdr, is_open_hdr_data = self.session.getHdrMode()
mode = is_open_hdr_data["mode"] if ret_hdr == 0 and is_open_hdr_data else 0
is_open_hdr = bool(int(mode))
return FoscamDeviceInfo(
dev_info=dev_info,
product_info=product_info,
is_open_ir=is_open_ir,
is_flip=is_flip,
is_mirror=is_mirror,
is_asleep=is_asleep,
is_open_white_light=is_open_white_light_val,
is_siren_alarm=is_siren_alarm_val,
volume=volume_val,
speak_volume=speak_volume_val,
is_turn_off_volume=is_turn_off_volume_val,
is_turn_off_light=is_turn_off_light_val,
is_open_wdr=is_open_wdr,
is_open_hdr=is_open_hdr,
)
async def _async_update_data(self) -> FoscamDeviceInfo:
"""Fetch data from API endpoint."""
async with asyncio.timeout(30):
data = {}
ret, dev_info = await self.hass.async_add_executor_job(
self.session.get_dev_info
)
if ret == 0:
data["dev_info"] = dev_info
all_info = await self.hass.async_add_executor_job(
self.session.get_product_all_info
)
data["product_info"] = all_info[1]
ret, is_asleep = await self.hass.async_add_executor_job(
self.session.is_asleep
)
data["is_asleep"] = {"supported": ret == 0, "status": is_asleep}
return data
async with asyncio.timeout(10):
return await self.hass.async_add_executor_job(self.gather_all_configs)

View File

@@ -13,19 +13,15 @@ from .coordinator import FoscamCoordinator
class FoscamEntity(CoordinatorEntity[FoscamCoordinator]):
"""Base entity for Foscam camera."""
def __init__(
self,
coordinator: FoscamCoordinator,
entry_id: str,
) -> None:
def __init__(self, coordinator: FoscamCoordinator, config_entry_id: str) -> None:
"""Initialize the base Foscam entity."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry_id)},
identifiers={(DOMAIN, config_entry_id)},
manufacturer="Foscam",
)
if dev_info := coordinator.data.get("dev_info"):
if dev_info := coordinator.data.dev_info:
self._attr_device_info[ATTR_MODEL] = dev_info["productName"]
self._attr_device_info[ATTR_SW_VERSION] = dev_info["firmwareVer"]
self._attr_device_info[ATTR_HW_VERSION] = dev_info["hardwareVer"]

View File

@@ -6,5 +6,39 @@
"ptz_preset": {
"service": "mdi:target-variant"
}
},
"entity": {
"switch": {
"flip_switch": {
"default": "mdi:flip-vertical"
},
"mirror_switch": {
"default": "mdi:mirror"
},
"ir_switch": {
"default": "mdi:theme-light-dark"
},
"sleep_switch": {
"default": "mdi:sleep"
},
"white_light_switch": {
"default": "mdi:light-flood-down"
},
"siren_alarm_switch": {
"default": "mdi:alarm-note"
},
"turn_off_volume_switch": {
"default": "mdi:volume-off"
},
"turn_off_light_switch": {
"default": "mdi:lightbulb-fluorescent-tube"
},
"hdr_switch": {
"default": "mdi:hdr"
},
"wdr_switch": {
"default": "mdi:alpha-w-box"
}
}
}
}

View File

@@ -27,8 +27,35 @@
},
"entity": {
"switch": {
"flip_switch": {
"name": "Flip"
},
"mirror_switch": {
"name": "Mirror"
},
"ir_switch": {
"name": "Infrared mode"
},
"sleep_switch": {
"name": "Sleep"
"name": "Sleep mode"
},
"white_light_switch": {
"name": "White light"
},
"siren_alarm_switch": {
"name": "Siren alarm"
},
"turn_off_volume_switch": {
"name": "Volume muted"
},
"turn_off_light_switch": {
"name": "Light"
},
"hdr_switch": {
"name": "HDR"
},
"wdr_switch": {
"name": "WDR"
}
}
},

View File

@@ -2,18 +2,117 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from libpyfoscamcgi import FoscamCamera
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import LOGGER
from .coordinator import FoscamConfigEntry, FoscamCoordinator
from .entity import FoscamEntity
def handle_ir_turn_on(session: FoscamCamera) -> None:
"""Turn on IR LED: sets IR mode to auto (if supported), then turns off the IR LED."""
session.set_infra_led_config(1)
session.open_infra_led()
def handle_ir_turn_off(session: FoscamCamera) -> None:
"""Turn off IR LED: sets IR mode to manual (if supported), then turns open the IR LED."""
session.set_infra_led_config(0)
session.close_infra_led()
@dataclass(frozen=True, kw_only=True)
class FoscamSwitchEntityDescription(SwitchEntityDescription):
"""A custom entity description that supports a turn_off function."""
native_value_fn: Callable[..., bool]
turn_off_fn: Callable[[FoscamCamera], None]
turn_on_fn: Callable[[FoscamCamera], None]
SWITCH_DESCRIPTIONS: list[FoscamSwitchEntityDescription] = [
FoscamSwitchEntityDescription(
key="is_flip",
translation_key="flip_switch",
native_value_fn=lambda data: data.is_flip,
turn_off_fn=lambda session: session.flip_video(0),
turn_on_fn=lambda session: session.flip_video(1),
),
FoscamSwitchEntityDescription(
key="is_mirror",
translation_key="mirror_switch",
native_value_fn=lambda data: data.is_mirror,
turn_off_fn=lambda session: session.mirror_video(0),
turn_on_fn=lambda session: session.mirror_video(1),
),
FoscamSwitchEntityDescription(
key="is_open_ir",
translation_key="ir_switch",
native_value_fn=lambda data: data.is_open_ir,
turn_off_fn=handle_ir_turn_off,
turn_on_fn=handle_ir_turn_on,
),
FoscamSwitchEntityDescription(
key="sleep_switch",
translation_key="sleep_switch",
native_value_fn=lambda data: data.is_asleep["status"],
turn_off_fn=lambda session: session.wake_up(),
turn_on_fn=lambda session: session.sleep(),
),
FoscamSwitchEntityDescription(
key="is_open_white_light",
translation_key="white_light_switch",
native_value_fn=lambda data: data.is_open_white_light,
turn_off_fn=lambda session: session.closeWhiteLight(),
turn_on_fn=lambda session: session.openWhiteLight(),
),
FoscamSwitchEntityDescription(
key="is_siren_alarm",
translation_key="siren_alarm_switch",
native_value_fn=lambda data: data.is_siren_alarm,
turn_off_fn=lambda session: session.setSirenConfig(0, 100, 0),
turn_on_fn=lambda session: session.setSirenConfig(1, 100, 0),
),
FoscamSwitchEntityDescription(
key="is_turn_off_volume",
translation_key="turn_off_volume_switch",
native_value_fn=lambda data: data.is_turn_off_volume,
turn_off_fn=lambda session: session.setVoiceEnableState(1),
turn_on_fn=lambda session: session.setVoiceEnableState(0),
),
FoscamSwitchEntityDescription(
key="is_turn_off_light",
translation_key="turn_off_light_switch",
native_value_fn=lambda data: data.is_turn_off_light,
turn_off_fn=lambda session: session.setLedEnableState(0),
turn_on_fn=lambda session: session.setLedEnableState(1),
),
FoscamSwitchEntityDescription(
key="is_open_hdr",
translation_key="hdr_switch",
native_value_fn=lambda data: data.is_open_hdr,
turn_off_fn=lambda session: session.setHdrMode(0),
turn_on_fn=lambda session: session.setHdrMode(1),
),
FoscamSwitchEntityDescription(
key="is_open_wdr",
translation_key="wdr_switch",
native_value_fn=lambda data: data.is_open_wdr,
turn_off_fn=lambda session: session.setWdrMode(0),
turn_on_fn=lambda session: session.setWdrMode(1),
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: FoscamConfigEntry,
@@ -22,63 +121,61 @@ async def async_setup_entry(
"""Set up foscam switch from a config entry."""
coordinator = config_entry.runtime_data
await coordinator.async_config_entry_first_refresh()
if coordinator.data["is_asleep"]["supported"]:
async_add_entities([FoscamSleepSwitch(coordinator, config_entry)])
entities = []
product_info = coordinator.data.product_info
reserve3 = product_info.get("reserve3", "0")
for description in SWITCH_DESCRIPTIONS:
if description.key == "is_asleep":
if not coordinator.data.is_asleep["supported"]:
continue
elif description.key == "is_open_hdr":
if ((1 << 8) & int(reserve3)) != 0 or ((1 << 7) & int(reserve3)) == 0:
continue
elif description.key == "is_open_wdr":
if ((1 << 8) & int(reserve3)) == 0:
continue
entities.append(FoscamGenericSwitch(coordinator, description))
async_add_entities(entities)
class FoscamSleepSwitch(FoscamEntity, SwitchEntity):
"""An implementation for Sleep Switch."""
class FoscamGenericSwitch(FoscamEntity, SwitchEntity):
"""A generic switch class for Foscam entities."""
_attr_has_entity_name = True
entity_description: FoscamSwitchEntityDescription
def __init__(
self,
coordinator: FoscamCoordinator,
config_entry: FoscamConfigEntry,
description: FoscamSwitchEntityDescription,
) -> None:
"""Initialize a Foscam Sleep Switch."""
super().__init__(coordinator, config_entry.entry_id)
"""Initialize the generic switch."""
entry_id = coordinator.config_entry.entry_id
super().__init__(coordinator, entry_id)
self._attr_unique_id = f"{config_entry.entry_id}_sleep_switch"
self._attr_translation_key = "sleep_switch"
self._attr_has_entity_name = True
self.is_asleep = self.coordinator.data["is_asleep"]["status"]
self.entity_description = description
self._attr_unique_id = f"{entry_id}_{description.key}"
@property
def is_on(self):
"""Return true if camera is asleep."""
return self.is_asleep
def is_on(self) -> bool:
"""Return the state of the switch."""
return self.entity_description.native_value_fn(self.coordinator.data)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Wake camera."""
LOGGER.debug("Wake camera")
ret, _ = await self.hass.async_add_executor_job(
self.coordinator.session.wake_up
"""Turn off the entity."""
self.hass.async_add_executor_job(
self.entity_description.turn_off_fn, self.coordinator.session
)
if ret != 0:
raise HomeAssistantError(f"Error waking up: {ret}")
await self.coordinator.async_request_refresh()
async def async_turn_on(self, **kwargs: Any) -> None:
"""But camera is sleep."""
LOGGER.debug("Sleep camera")
ret, _ = await self.hass.async_add_executor_job(self.coordinator.session.sleep)
if ret != 0:
raise HomeAssistantError(f"Error sleeping: {ret}")
"""Turn on the entity."""
self.hass.async_add_executor_job(
self.entity_description.turn_on_fn, self.coordinator.session
)
await self.coordinator.async_request_refresh()
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.is_asleep = self.coordinator.data["is_asleep"]["status"]
self.async_write_ha_state()

View File

@@ -60,6 +60,21 @@ def setup_mock_foscam_camera(mock_foscam_camera):
mock_foscam_camera.get_dev_info.return_value = (dev_info_rc, dev_info_data)
mock_foscam_camera.get_port_info.return_value = (dev_info_rc, {})
mock_foscam_camera.is_asleep.return_value = (0, True)
mock_foscam_camera.get_infra_led_config.return_value = (0, {"mode": "1"})
mock_foscam_camera.get_mirror_and_flip_setting.return_value = (
0,
{"isFlip": "0", "isMirror": "0"},
)
mock_foscam_camera.is_asleep.return_value = (0, "0")
mock_foscam_camera.getWhiteLightBrightness.return_value = (0, {"enable": "1"})
mock_foscam_camera.getSirenConfig.return_value = (0, {"sirenEnable": "1"})
mock_foscam_camera.getAudioVolume.return_value = (0, {"volume": "100"})
mock_foscam_camera.getSpeakVolume.return_value = (0, {"SpeakVolume": "100"})
mock_foscam_camera.getVoiceEnableState.return_value = (0, {"isEnable": "1"})
mock_foscam_camera.getLedEnableState.return_value = (0, {"isEnable": "0"})
mock_foscam_camera.getWdrMode.return_value = (0, {"mode": "0"})
mock_foscam_camera.getHdrMode.return_value = (0, {"mode": "0"})
mock_foscam_camera.get_motion_detect_config.return_value = (0, 1)
return mock_foscam_camera

View File

@@ -0,0 +1,385 @@
# serializer version: 1
# name: test_entities[switch.mock_title_flip-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_flip',
'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': 'Flip',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'flip_switch',
'unique_id': '123ABC_is_flip',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_flip-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Flip',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_flip',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_infrared_mode-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_infrared_mode',
'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': 'Infrared mode',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'ir_switch',
'unique_id': '123ABC_is_open_ir',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_infrared_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Infrared mode',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_infrared_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.mock_title_light-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_light',
'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': 'Light',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_off_light_switch',
'unique_id': '123ABC_is_turn_off_light',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Light',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_mirror-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_mirror',
'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': 'Mirror',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'mirror_switch',
'unique_id': '123ABC_is_mirror',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_mirror-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Mirror',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_mirror',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_siren_alarm-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_siren_alarm',
'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': 'Siren alarm',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'siren_alarm_switch',
'unique_id': '123ABC_is_siren_alarm',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_siren_alarm-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Siren alarm',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_siren_alarm',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.mock_title_sleep_mode-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_sleep_mode',
'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': 'Sleep mode',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'sleep_switch',
'unique_id': '123ABC_sleep_switch',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_sleep_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Sleep mode',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_sleep_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_volume_muted-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_volume_muted',
'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': 'Volume muted',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'turn_off_volume_switch',
'unique_id': '123ABC_is_turn_off_volume',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_volume_muted-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Volume muted',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_volume_muted',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_white_light-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': 'switch',
'entity_category': None,
'entity_id': 'switch.mock_title_white_light',
'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': 'White light',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'white_light_switch',
'unique_id': '123ABC_is_open_white_light',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_white_light-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title White light',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_white_light',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@@ -96,7 +96,7 @@ async def test_unique_id_migration_not_needed(
assert entity_before.unique_id == f"{ENTRY_ID}_sleep_switch"
with (
# Mock a valid camera instance"
# Mock a valid camera instance
patch("homeassistant.components.foscam.FoscamCamera") as mock_foscam_camera,
patch(
"homeassistant.components.foscam.async_migrate_entry",

View File

@@ -0,0 +1,35 @@
"""Test for the switch platform entity of the foscam component."""
from unittest.mock import patch
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.foscam.const import DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import setup_mock_foscam_camera
from .const import ENTRY_ID, VALID_CONFIG
from tests.common import MockConfigEntry, snapshot_platform
async def test_entities(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test that coordinator returns the data we expect after the first refresh."""
entry = MockConfigEntry(domain=DOMAIN, data=VALID_CONFIG, entry_id=ENTRY_ID)
entry.add_to_hass(hass)
with (
# Mock a valid camera instance"
patch("homeassistant.components.foscam.FoscamCamera") as mock_foscam_camera,
patch("homeassistant.components.foscam.PLATFORMS", [Platform.SWITCH]),
):
setup_mock_foscam_camera(mock_foscam_camera)
assert await hass.config_entries.async_setup(entry.entry_id)
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)