diff --git a/README.md b/README.md index 143c49a..31f6aed 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,43 @@ cloudEnabled = False cam.set_info("NetWork.Nat", { "NatEnable" : cloudEnabled }) ``` +## Motion detection + +Xiongmai cameras typically do **not** expose ONVIF `AnalyticsService`, so +motion detection cannot be configured through ONVIF. On some firmwares it +is also off by default. Configure it directly over the native protocol via +the `Detect` config path: + +```python +# Inspect current config (per-channel arrays) +print(cam.get_detect_info()) +# {'HumanDetection': [{'Enable': False, ...}], +# 'MotionDetect': [{'Enable': True, +# 'EventHandler': {'RecordEnable': True, +# 'AlarmOutEnable': False, ...}, +# 'Level': 5}]} + +# Enable motion detection at sensitivity 5 with event-triggered recording. +# Sparse payloads are merged — fields you omit keep their current values. +cam.set_detect_info({ + "MotionDetect": [{"Enable": True, + "EventHandler": {"RecordEnable": True}, + "Level": 5}], + "HumanDetection": [{"Enable": False}], +}) +``` + +The equivalent low-level call is `cam.set_info("Detect", ...)` — both +shapes work; the wrapper just documents the path. + +Receiving the events is a separate problem. Some XM firmwares push +`AlarmInfo` packets over the existing TCP session, so registering +`setAlarm()` + `alarmStart()` is enough; on others the camera only emits +to a separately-configured alarm server (see `AlarmServer.py` and the +`EventHandler.AlarmInfo` / `MsgtoNetEnable` fields). If `setAlarm()` +silently produces no callbacks even while recordings show motion is +detected, the camera is likely in the latter group. + ## Add user and change password ```python diff --git a/asyncio_dvrip.py b/asyncio_dvrip.py index 4060404..edc3b0e 100644 --- a/asyncio_dvrip.py +++ b/asyncio_dvrip.py @@ -580,6 +580,14 @@ class DVRIPCam(object): code = 1042 return await self.get_command("Simplify.Encode", code) + async def get_detect_info(self): + """Read 'Detect' config: per-channel MotionDetect / HumanDetection / etc.""" + return await self.get_info("Detect") + + async def set_detect_info(self, data): + """Update 'Detect' config. Sparse payloads are merged with current state.""" + return await self.set_info("Detect", data) + async def recv_json(self, buf=bytearray()): p = compile(b".*({.*})") diff --git a/connect.py b/connect.py index e6e7b79..84367af 100644 --- a/connect.py +++ b/connect.py @@ -27,6 +27,13 @@ info["OSDInfo"][0]["OSDInfoWidget"]["PreviewBlend"] = True # info["OSDInfo"][0]["OSDInfoWidget"]["RelativePos"] = [6144,6144,8192,8192] cam.set_info("fVideo.OSDInfo", info) # enc_info = cam.get_info("Simplify.Encode") +# Motion detection: turn it on and route events into recording. +# cam.set_detect_info({ +# "MotionDetect": [{"Enable": True, +# "EventHandler": {"RecordEnable": True}, +# "Level": 5}], +# "HumanDetection": [{"Enable": False}], +# }) # Alarm example def alarm(content, ids): print(content) diff --git a/dvrip.py b/dvrip.py index a3c5114..e9007e2 100644 --- a/dvrip.py +++ b/dvrip.py @@ -703,6 +703,14 @@ class DVRIPCam(object): code = 1042 return self.get_command("Simplify.Encode", code) + def get_detect_info(self): + """Read 'Detect' config: per-channel MotionDetect / HumanDetection / etc.""" + return self.get_info("Detect") + + def set_detect_info(self, data): + """Update 'Detect' config. Sparse payloads are merged with current state.""" + return self.set_info("Detect", data) + def recv_json(self, buf=bytearray()): p = compile(b".*({.*})")