1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 12:59:34 +00:00

New Foscam switch (#152732)

This commit is contained in:
Foscam-wangzhengyu
2025-10-13 23:23:27 +08:00
committed by GitHub
parent eab1205823
commit 2d0b4dd7e9
7 changed files with 257 additions and 27 deletions

View File

@@ -35,9 +35,16 @@ class FoscamDeviceInfo:
is_turn_off_volume: bool
is_turn_off_light: bool
supports_speak_volume_adjustment: bool
supports_pet_adjustment: bool
supports_car_adjustment: bool
supports_wdr_adjustment: bool
supports_hdr_adjustment: bool
is_open_wdr: bool | None = None
is_open_hdr: bool | None = None
is_pet_detection_on: bool | None = None
is_car_detection_on: bool | None = None
is_human_detection_on: bool | None = None
class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
@@ -107,14 +114,15 @@ class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
is_open_wdr = None
is_open_hdr = None
reserve3 = product_info.get("reserve3")
reserve3 = product_info.get("reserve4")
reserve3_int = int(reserve3) if reserve3 is not None else 0
if (reserve3_int & (1 << 8)) != 0:
supports_wdr_adjustment_val = bool(int(reserve3_int & 256))
supports_hdr_adjustment_val = bool(int(reserve3_int & 128))
if supports_wdr_adjustment_val:
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:
elif supports_hdr_adjustment_val:
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))
@@ -126,6 +134,34 @@ class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
if ret_sw == 0
else False
)
pet_adjustment_val = (
bool(int(software_capabilities.get("swCapabilities2")) & 512)
if ret_sw == 0
else False
)
car_adjustment_val = (
bool(int(software_capabilities.get("swCapabilities2")) & 256)
if ret_sw == 0
else False
)
ret_md, mothion_config_val = self.session.get_motion_detect_config()
if pet_adjustment_val:
is_pet_detection_on_val = (
mothion_config_val["petEnable"] == "1" if ret_md == 0 else False
)
else:
is_pet_detection_on_val = False
if car_adjustment_val:
is_car_detection_on_val = (
mothion_config_val["carEnable"] == "1" if ret_md == 0 else False
)
else:
is_car_detection_on_val = False
is_human_detection_on_val = (
mothion_config_val["humanEnable"] == "1" if ret_md == 0 else False
)
return FoscamDeviceInfo(
dev_info=dev_info,
@@ -141,8 +177,15 @@ class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
is_turn_off_volume=is_turn_off_volume_val,
is_turn_off_light=is_turn_off_light_val,
supports_speak_volume_adjustment=supports_speak_volume_adjustment_val,
supports_pet_adjustment=pet_adjustment_val,
supports_car_adjustment=car_adjustment_val,
supports_hdr_adjustment=supports_hdr_adjustment_val,
supports_wdr_adjustment=supports_wdr_adjustment_val,
is_open_wdr=is_open_wdr,
is_open_hdr=is_open_hdr,
is_pet_detection_on=is_pet_detection_on_val,
is_car_detection_on=is_car_detection_on_val,
is_human_detection_on=is_human_detection_on_val,
)
async def _async_update_data(self) -> FoscamDeviceInfo:

View File

@@ -38,6 +38,15 @@
},
"wdr_switch": {
"default": "mdi:alpha-w-box"
},
"pet_detection": {
"default": "mdi:paw"
},
"car_detection": {
"default": "mdi:car-hatchback"
},
"human_detection": {
"default": "mdi:human"
}
},
"number": {

View File

@@ -22,7 +22,7 @@ class FoscamNumberEntityDescription(NumberEntityDescription):
native_value_fn: Callable[[FoscamCoordinator], int]
set_value_fn: Callable[[FoscamCamera, float], Any]
exists_fn: Callable[[FoscamCoordinator], bool]
exists_fn: Callable[[FoscamCoordinator], bool] = lambda _: True
NUMBER_DESCRIPTIONS: list[FoscamNumberEntityDescription] = [
@@ -34,7 +34,6 @@ NUMBER_DESCRIPTIONS: list[FoscamNumberEntityDescription] = [
native_step=1,
native_value_fn=lambda coordinator: coordinator.data.device_volume,
set_value_fn=lambda session, value: session.setAudioVolume(value),
exists_fn=lambda _: True,
),
FoscamNumberEntityDescription(
key="speak_volume",

View File

@@ -61,6 +61,15 @@
},
"wdr_switch": {
"name": "WDR"
},
"pet_detection": {
"name": "Pet detection"
},
"car_detection": {
"name": "Car detection"
},
"human_detection": {
"name": "Human detection"
}
},
"number": {

View File

@@ -30,6 +30,14 @@ def handle_ir_turn_off(session: FoscamCamera) -> None:
session.close_infra_led()
def set_motion_detection(session: FoscamCamera, field: str, enabled: bool) -> None:
"""Turns on pet detection."""
ret, config = session.get_motion_detect_config()
if not ret:
config[field] = int(enabled)
session.set_motion_detect_config(config)
@dataclass(frozen=True, kw_only=True)
class FoscamSwitchEntityDescription(SwitchEntityDescription):
"""A custom entity description that supports a turn_off function."""
@@ -37,6 +45,7 @@ class FoscamSwitchEntityDescription(SwitchEntityDescription):
native_value_fn: Callable[..., bool]
turn_off_fn: Callable[[FoscamCamera], None]
turn_on_fn: Callable[[FoscamCamera], None]
exists_fn: Callable[[FoscamCoordinator], bool] = lambda _: True
SWITCH_DESCRIPTIONS: list[FoscamSwitchEntityDescription] = [
@@ -102,6 +111,7 @@ SWITCH_DESCRIPTIONS: list[FoscamSwitchEntityDescription] = [
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),
exists_fn=lambda coordinator: coordinator.data.supports_hdr_adjustment,
),
FoscamSwitchEntityDescription(
key="is_open_wdr",
@@ -109,6 +119,30 @@ SWITCH_DESCRIPTIONS: list[FoscamSwitchEntityDescription] = [
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),
exists_fn=lambda coordinator: coordinator.data.supports_wdr_adjustment,
),
FoscamSwitchEntityDescription(
key="pet_detection",
translation_key="pet_detection",
native_value_fn=lambda data: data.is_pet_detection_on,
turn_off_fn=lambda session: set_motion_detection(session, "petEnable", False),
turn_on_fn=lambda session: set_motion_detection(session, "petEnable", True),
exists_fn=lambda coordinator: coordinator.data.supports_pet_adjustment,
),
FoscamSwitchEntityDescription(
key="car_detection",
translation_key="car_detection",
native_value_fn=lambda data: data.is_car_detection_on,
turn_off_fn=lambda session: set_motion_detection(session, "carEnable", False),
turn_on_fn=lambda session: set_motion_detection(session, "carEnable", True),
exists_fn=lambda coordinator: coordinator.data.supports_car_adjustment,
),
FoscamSwitchEntityDescription(
key="human_detection",
translation_key="human_detection",
native_value_fn=lambda data: data.is_human_detection_on,
turn_off_fn=lambda session: set_motion_detection(session, "humanEnable", False),
turn_on_fn=lambda session: set_motion_detection(session, "humanEnable", True),
),
]
@@ -122,24 +156,11 @@ async def async_setup_entry(
coordinator = config_entry.runtime_data
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)
async_add_entities(
FoscamGenericSwitch(coordinator, description)
for description in SWITCH_DESCRIPTIONS
if description.exists_fn(coordinator)
)
class FoscamGenericSwitch(FoscamEntity, SwitchEntity):

View File

@@ -79,11 +79,16 @@ def setup_mock_foscam_camera(mock_foscam_camera):
0,
{
"swCapabilities1": "100",
"swCapbilities2": "100",
"swCapbilities3": "100",
"swCapbilities4": "100",
"swCapabilities2": "768",
"swCapabilities3": "100",
"swCapabilities4": "100",
},
)
mock_foscam_camera.get_motion_detect_config.return_value = (
0,
{"petEnable": "1", "carEnable": "1", "humanEnable": "1"},
)
return mock_foscam_camera
mock_foscam_camera.side_effect = configure_mock_on_init

View File

@@ -1,4 +1,52 @@
# serializer version: 1
# name: test_entities[switch.mock_title_car_detection-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_car_detection',
'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': 'Car detection',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'car_detection',
'unique_id': '123ABC_car_detection',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_car_detection-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Car detection',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_car_detection',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.mock_title_flip-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -47,6 +95,54 @@
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_human_detection-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_human_detection',
'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': 'Human detection',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'human_detection',
'unique_id': '123ABC_human_detection',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_human_detection-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Human detection',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_human_detection',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.mock_title_infrared_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -191,6 +287,54 @@
'state': 'off',
})
# ---
# name: test_entities[switch.mock_title_pet_detection-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_pet_detection',
'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': 'Pet detection',
'platform': 'foscam',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'pet_detection',
'unique_id': '123ABC_pet_detection',
'unit_of_measurement': None,
})
# ---
# name: test_entities[switch.mock_title_pet_detection-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Mock Title Pet detection',
}),
'context': <ANY>,
'entity_id': 'switch.mock_title_pet_detection',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_entities[switch.mock_title_siren_alarm-entry]
EntityRegistryEntrySnapshot({
'aliases': set({