Skip to content

Commit 2d0b4dd

Browse files
New Foscam switch (home-assistant#152732)
1 parent eab1205 commit 2d0b4dd

File tree

7 files changed

+257
-27
lines changed

7 files changed

+257
-27
lines changed

homeassistant/components/foscam/coordinator.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@ class FoscamDeviceInfo:
3535
is_turn_off_volume: bool
3636
is_turn_off_light: bool
3737
supports_speak_volume_adjustment: bool
38+
supports_pet_adjustment: bool
39+
supports_car_adjustment: bool
40+
supports_wdr_adjustment: bool
41+
supports_hdr_adjustment: bool
3842

3943
is_open_wdr: bool | None = None
4044
is_open_hdr: bool | None = None
45+
is_pet_detection_on: bool | None = None
46+
is_car_detection_on: bool | None = None
47+
is_human_detection_on: bool | None = None
4148

4249

4350
class FoscamCoordinator(DataUpdateCoordinator[FoscamDeviceInfo]):
@@ -107,14 +114,15 @@ def gather_all_configs(self) -> FoscamDeviceInfo:
107114

108115
is_open_wdr = None
109116
is_open_hdr = None
110-
reserve3 = product_info.get("reserve3")
117+
reserve3 = product_info.get("reserve4")
111118
reserve3_int = int(reserve3) if reserve3 is not None else 0
112-
113-
if (reserve3_int & (1 << 8)) != 0:
119+
supports_wdr_adjustment_val = bool(int(reserve3_int & 256))
120+
supports_hdr_adjustment_val = bool(int(reserve3_int & 128))
121+
if supports_wdr_adjustment_val:
114122
ret_wdr, is_open_wdr_data = self.session.getWdrMode()
115123
mode = is_open_wdr_data["mode"] if ret_wdr == 0 and is_open_wdr_data else 0
116124
is_open_wdr = bool(int(mode))
117-
else:
125+
elif supports_hdr_adjustment_val:
118126
ret_hdr, is_open_hdr_data = self.session.getHdrMode()
119127
mode = is_open_hdr_data["mode"] if ret_hdr == 0 and is_open_hdr_data else 0
120128
is_open_hdr = bool(int(mode))
@@ -126,6 +134,34 @@ def gather_all_configs(self) -> FoscamDeviceInfo:
126134
if ret_sw == 0
127135
else False
128136
)
137+
pet_adjustment_val = (
138+
bool(int(software_capabilities.get("swCapabilities2")) & 512)
139+
if ret_sw == 0
140+
else False
141+
)
142+
car_adjustment_val = (
143+
bool(int(software_capabilities.get("swCapabilities2")) & 256)
144+
if ret_sw == 0
145+
else False
146+
)
147+
ret_md, mothion_config_val = self.session.get_motion_detect_config()
148+
if pet_adjustment_val:
149+
is_pet_detection_on_val = (
150+
mothion_config_val["petEnable"] == "1" if ret_md == 0 else False
151+
)
152+
else:
153+
is_pet_detection_on_val = False
154+
155+
if car_adjustment_val:
156+
is_car_detection_on_val = (
157+
mothion_config_val["carEnable"] == "1" if ret_md == 0 else False
158+
)
159+
else:
160+
is_car_detection_on_val = False
161+
162+
is_human_detection_on_val = (
163+
mothion_config_val["humanEnable"] == "1" if ret_md == 0 else False
164+
)
129165

130166
return FoscamDeviceInfo(
131167
dev_info=dev_info,
@@ -141,8 +177,15 @@ def gather_all_configs(self) -> FoscamDeviceInfo:
141177
is_turn_off_volume=is_turn_off_volume_val,
142178
is_turn_off_light=is_turn_off_light_val,
143179
supports_speak_volume_adjustment=supports_speak_volume_adjustment_val,
180+
supports_pet_adjustment=pet_adjustment_val,
181+
supports_car_adjustment=car_adjustment_val,
182+
supports_hdr_adjustment=supports_hdr_adjustment_val,
183+
supports_wdr_adjustment=supports_wdr_adjustment_val,
144184
is_open_wdr=is_open_wdr,
145185
is_open_hdr=is_open_hdr,
186+
is_pet_detection_on=is_pet_detection_on_val,
187+
is_car_detection_on=is_car_detection_on_val,
188+
is_human_detection_on=is_human_detection_on_val,
146189
)
147190

148191
async def _async_update_data(self) -> FoscamDeviceInfo:

homeassistant/components/foscam/icons.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@
3838
},
3939
"wdr_switch": {
4040
"default": "mdi:alpha-w-box"
41+
},
42+
"pet_detection": {
43+
"default": "mdi:paw"
44+
},
45+
"car_detection": {
46+
"default": "mdi:car-hatchback"
47+
},
48+
"human_detection": {
49+
"default": "mdi:human"
4150
}
4251
},
4352
"number": {

homeassistant/components/foscam/number.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class FoscamNumberEntityDescription(NumberEntityDescription):
2222

2323
native_value_fn: Callable[[FoscamCoordinator], int]
2424
set_value_fn: Callable[[FoscamCamera, float], Any]
25-
exists_fn: Callable[[FoscamCoordinator], bool]
25+
exists_fn: Callable[[FoscamCoordinator], bool] = lambda _: True
2626

2727

2828
NUMBER_DESCRIPTIONS: list[FoscamNumberEntityDescription] = [
@@ -34,7 +34,6 @@ class FoscamNumberEntityDescription(NumberEntityDescription):
3434
native_step=1,
3535
native_value_fn=lambda coordinator: coordinator.data.device_volume,
3636
set_value_fn=lambda session, value: session.setAudioVolume(value),
37-
exists_fn=lambda _: True,
3837
),
3938
FoscamNumberEntityDescription(
4039
key="speak_volume",

homeassistant/components/foscam/strings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@
6161
},
6262
"wdr_switch": {
6363
"name": "WDR"
64+
},
65+
"pet_detection": {
66+
"name": "Pet detection"
67+
},
68+
"car_detection": {
69+
"name": "Car detection"
70+
},
71+
"human_detection": {
72+
"name": "Human detection"
6473
}
6574
},
6675
"number": {

homeassistant/components/foscam/switch.py

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,22 @@ def handle_ir_turn_off(session: FoscamCamera) -> None:
3030
session.close_infra_led()
3131

3232

33+
def set_motion_detection(session: FoscamCamera, field: str, enabled: bool) -> None:
34+
"""Turns on pet detection."""
35+
ret, config = session.get_motion_detect_config()
36+
if not ret:
37+
config[field] = int(enabled)
38+
session.set_motion_detect_config(config)
39+
40+
3341
@dataclass(frozen=True, kw_only=True)
3442
class FoscamSwitchEntityDescription(SwitchEntityDescription):
3543
"""A custom entity description that supports a turn_off function."""
3644

3745
native_value_fn: Callable[..., bool]
3846
turn_off_fn: Callable[[FoscamCamera], None]
3947
turn_on_fn: Callable[[FoscamCamera], None]
48+
exists_fn: Callable[[FoscamCoordinator], bool] = lambda _: True
4049

4150

4251
SWITCH_DESCRIPTIONS: list[FoscamSwitchEntityDescription] = [
@@ -102,13 +111,38 @@ class FoscamSwitchEntityDescription(SwitchEntityDescription):
102111
native_value_fn=lambda data: data.is_open_hdr,
103112
turn_off_fn=lambda session: session.setHdrMode(0),
104113
turn_on_fn=lambda session: session.setHdrMode(1),
114+
exists_fn=lambda coordinator: coordinator.data.supports_hdr_adjustment,
105115
),
106116
FoscamSwitchEntityDescription(
107117
key="is_open_wdr",
108118
translation_key="wdr_switch",
109119
native_value_fn=lambda data: data.is_open_wdr,
110120
turn_off_fn=lambda session: session.setWdrMode(0),
111121
turn_on_fn=lambda session: session.setWdrMode(1),
122+
exists_fn=lambda coordinator: coordinator.data.supports_wdr_adjustment,
123+
),
124+
FoscamSwitchEntityDescription(
125+
key="pet_detection",
126+
translation_key="pet_detection",
127+
native_value_fn=lambda data: data.is_pet_detection_on,
128+
turn_off_fn=lambda session: set_motion_detection(session, "petEnable", False),
129+
turn_on_fn=lambda session: set_motion_detection(session, "petEnable", True),
130+
exists_fn=lambda coordinator: coordinator.data.supports_pet_adjustment,
131+
),
132+
FoscamSwitchEntityDescription(
133+
key="car_detection",
134+
translation_key="car_detection",
135+
native_value_fn=lambda data: data.is_car_detection_on,
136+
turn_off_fn=lambda session: set_motion_detection(session, "carEnable", False),
137+
turn_on_fn=lambda session: set_motion_detection(session, "carEnable", True),
138+
exists_fn=lambda coordinator: coordinator.data.supports_car_adjustment,
139+
),
140+
FoscamSwitchEntityDescription(
141+
key="human_detection",
142+
translation_key="human_detection",
143+
native_value_fn=lambda data: data.is_human_detection_on,
144+
turn_off_fn=lambda session: set_motion_detection(session, "humanEnable", False),
145+
turn_on_fn=lambda session: set_motion_detection(session, "humanEnable", True),
112146
),
113147
]
114148

@@ -122,24 +156,11 @@ async def async_setup_entry(
122156

123157
coordinator = config_entry.runtime_data
124158

125-
entities = []
126-
127-
product_info = coordinator.data.product_info
128-
reserve3 = product_info.get("reserve3", "0")
129-
130-
for description in SWITCH_DESCRIPTIONS:
131-
if description.key == "is_asleep":
132-
if not coordinator.data.is_asleep["supported"]:
133-
continue
134-
elif description.key == "is_open_hdr":
135-
if ((1 << 8) & int(reserve3)) != 0 or ((1 << 7) & int(reserve3)) == 0:
136-
continue
137-
elif description.key == "is_open_wdr":
138-
if ((1 << 8) & int(reserve3)) == 0:
139-
continue
140-
141-
entities.append(FoscamGenericSwitch(coordinator, description))
142-
async_add_entities(entities)
159+
async_add_entities(
160+
FoscamGenericSwitch(coordinator, description)
161+
for description in SWITCH_DESCRIPTIONS
162+
if description.exists_fn(coordinator)
163+
)
143164

144165

145166
class FoscamGenericSwitch(FoscamEntity, SwitchEntity):

tests/components/foscam/conftest.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,16 @@ def configure_mock_on_init(host, port, user, passwd, verbose=False):
7979
0,
8080
{
8181
"swCapabilities1": "100",
82-
"swCapbilities2": "100",
83-
"swCapbilities3": "100",
84-
"swCapbilities4": "100",
82+
"swCapabilities2": "768",
83+
"swCapabilities3": "100",
84+
"swCapabilities4": "100",
8585
},
8686
)
87+
mock_foscam_camera.get_motion_detect_config.return_value = (
88+
0,
89+
{"petEnable": "1", "carEnable": "1", "humanEnable": "1"},
90+
)
91+
8792
return mock_foscam_camera
8893

8994
mock_foscam_camera.side_effect = configure_mock_on_init

0 commit comments

Comments
 (0)