Stutter while sending PTZ command

MWLC

n3wb
Joined
Jun 20, 2024
Messages
2
Reaction score
0
Location
The Netherlands
Hi all,

I'm working on a script to move my camera using /ISAPI endpoints. I have set a ptz location for a preset with ID 1 so I can go back to it when I want. I can see the live feed of my camera to keep track of what is happening. When I send the preset command, the camera moves a little to that direction and not fully. I checked if the problem is in my connection, but sometimes it moves on one command, and sometimes not.

These are the details of my camera
"""
Model: DS-2DE2A204IW-DE3
Firmware Version:V5.6.16 build 200925
Encoding Version: V7.3 build 200907
Web Version:V4.0.1 build 191111
Plugin Version:3.0.7.25
"""


Here is the code I'm using
Python:
import logging
import xml.etree.ElementTree as ET

import requests
from requests.auth import HTTPDigestAuth

logger = logging.getLogger(__name__)


class CameraControl:
    def __init__(self, base_url, user, password):
        self.base_url = base_url
        self.user = user
        self.password = password

    def adjust_ptz(
        self,
        pan=None,
        tilt=None,
        panSpeed=100,
        tiltSpeed=100,
        duration=500,
        method="Continuous",
    ):
        try:
            # pan = tilt [-100:100]
            if method == "Momentary":
                url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/Momentary"
                payload = f"<PTZData><pan>{pan}</pan><tilt>{tilt}</tilt><panSpeed>{panSpeed}</panSpeed><tiltSpeed>{tiltSpeed}</tiltSpeed><zoom>0</zoom><Momentary><duration>{duration}</duration></Momentary></PTZData>"
            elif method == "Continuous":
                url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/Continuous"
                payload = f"<PTZData><pan>{pan}</pan><tilt>{tilt}</tilt><panSpeed>{panSpeed}</panSpeed><tiltSpeed>{tiltSpeed}</tiltSpeed><zoom>0</zoom><Continuous><duration>{duration}</duration></Continuous></PTZData>"
            elif method == "Absolute":
                # pan [0:3550], tilt [0-900]
                url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/Absolute"
                payload = (
                    "<PTZData>"
                    f"<AbsoluteHigh><azimuth>{pan}</azimuth><elevation>{tilt}</elevation><panSpeed>{panSpeed}</panSpeed><tiltSpeed>{tiltSpeed}</tiltSpeed><absoluteZoom>10</absoluteZoom></AbsoluteHigh>"
                    "</PTZData>"
                )

            response = requests.put(
                url,
                auth=HTTPDigestAuth(self.user, self.password),
                data=payload,
                headers={"Content-Type": "application/xml"},
            )
            if response.status_code == 200:
                return "Success: PTZ adjusted."
            else:
                logger.info(
                    f"Error: Unexpected response {response.status_code} - {response.text}"
                )
                return f"Error: Unexpected response {response.status_code} - {response.text}"
        except requests.exceptions.RequestException as err:
            return f"Error: {err}"

    def get_ptz_status(self):
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/status"
        try:
            response = requests.get(url, auth=HTTPDigestAuth(self.user, self.password))
            response.raise_for_status()
            xml_root = ET.fromstring(response.content)
            ns = {"hik": "http://www.hikvision.com/ver20/XMLSchema"}
            azimuth = xml_root.find("./hik:azimuth", ns).text
            elevation = xml_root.find("./hik:elevation", ns).text
            return (azimuth, elevation)
        except requests.exceptions.RequestException as e:
            return f"Failed to get PTZ status: {e}"
        except ET.ParseError as e:
            return f"XML parsing error: {e}"

    def set_home_position(self):
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/homeposition/set"
        response = requests.put(url, auth=HTTPDigestAuth(self.user, self.password))
        try:
            response.raise_for_status()
            return "Success: Home position set."
        except requests.exceptions.HTTPError as err:
            return f"Error: {err}"

    def goto_home_position(self):
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/homeposition/goto"
        response = requests.put(url, auth=HTTPDigestAuth(self.user, self.password))
        try:
            response.raise_for_status()
            if response.status_code == 200:
                return "Success: Camera moved to Home position."
            else:
                logger.info(
                    f"Error: Unexpected response {response.status_code} - {response.text}"
                )
                return f"Error: Unexpected response {response.status_code} - {response.text}"
        except requests.exceptions.HTTPError as err:
            return f"Error: {err}"

    def set_preset_position(self, preset_id):
        """Sets the current position of the PTZ camera as a preset position."""
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/presets/{preset_id}"
        payload = f"<PTZPreset><id>{preset_id}</id><presetName>Preset{preset_id}</presetName></PTZPreset>"
        response = requests.put(
            url,
            auth=HTTPDigestAuth(self.user, self.password),
            data=payload,
            headers={"Content-Type": "application/xml"},
        )
        try:
            response.raise_for_status()
            if response.status_code == 200:
                return f"Success: Preset position {preset_id} set."
            else:
                logger.info(
                    f"Error: Unexpected response {response.status_code} - {response.text}"
                )
                return f"Error: Unexpected response {response.status_code} - {response.text}"
        except requests.exceptions.HTTPError as err:
            return f"Error: {err}"

    def goto_preset_position(self, preset_id):
        """Moves the PTZ camera to a specific preset position."""
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/presets/{preset_id}/goto"
        response = requests.put(
            url,
            auth=HTTPDigestAuth(self.user, self.password),
            headers={"Content-Type": "application/xml"},
        )
        try:
            response.raise_for_status()
            if response.status_code == 200:
                logger.info(f"went to preset {preset_id}")
                return f"Success: Camera moved to preset position {preset_id}."
            else:
                logger.info(
                    f"Error: Unexpected response {response.status_code} - {response.text}"
                )
                return f"Error: Unexpected response {response.status_code} - {response.text}"
        except requests.exceptions.HTTPError as err:
            return f"Error: {err}"

    def get_preset_position(self, preset_id):
        """Gets the coordinates of a preset position."""
        url = f"{self.base_url}/ISAPI/PTZCtrl/channels/1/presets/{preset_id}"
        try:
            response = requests.get(url, auth=HTTPDigestAuth(self.user, self.password))
            response.raise_for_status()
            xml_root = ET.fromstring(response.content)
            ns = {"hik": "http://www.hikvision.com/ver20/XMLSchema"}
            azimuth = xml_root.find("./hik:azimuth", ns).text
            elevation = xml_root.find("./hik:elevation", ns).text
            return (azimuth, elevation)
        except requests.exceptions.RequestException as e:
            return f"Failed to get preset position: {e}"
        except ET.ParseError as e:
            return f"XML parsing error: {e}"


camera_control = CameraControl(f"http://192.168.1.64:80", "user1", "HaikuPlot876")

i = 1
while i < 6000:
    camera_control.adjust_ptz(pan="1800", tilt="10", duration=300, method="Absolute")
    i += 1
print(camera_control.get_ptz_status())
I made the for loop to test my theory of sending the command over time to make sure it will move, and it did move. How can i make sure that the camera will move only with one command?
 

blitzl3n

n3wb
Joined
Jun 26, 2024
Messages
1
Reaction score
0
Location
Marietta, GA
Hi! Also new here. I don't know how much I'll be able to help, as my networking development experience is minimal but I'm very much here to learn and am interested in what you've got going. I've been working on a more DIY approach to an ALPR and want it to be more flexible than just staring down a road lane: I want it to be able to scan the surroundings periodically, and didn't want to shell out too much as I'll want multiple cameras.

I made the for loop to test my theory of sending the command over time to make sure it will move
Interesting, so if I'm not mistaken, sometimes the camera will properly return when given the preset command? If you've tested it already, does this issue occur with an arbitrary target position and what I assume is the "absolute" method being passed to adjust_ptz(), except without this method call being looped? I presume the goto_preset_position() method is what is intended to be called, and does the same issue occur with the goto_home_position() method as well?
 

MWLC

n3wb
Joined
Jun 20, 2024
Messages
2
Reaction score
0
Location
The Netherlands
Hi @blitzl3n, It's good to know that I'm not the only one new here.

Sometimes the camera will properly return when given the preset command?
yes, sometimes it would go to the preset location. I noticed that if i send it one over another, it would go after the third try, if i try again (without restarting it) it would go after the fifth try. These numbers aren't fixed, but it doesn't go to the preset on the second time after less tries from the first time. I would need to restart the whole unit in order to make it go to the preset from the first try.

does this issue occur with an arbitrary target?
I'm not using any of the camera functionalities to follow a person.

I presume the goto_preset_position() method is what is intended to be called?
Yes it is

Does the same issue occur with the goto_home_position() method as well?
I didn't try with it, but I assume it wouldn't move to the home position and just move a little with every command I send.

Here is a more detailed version of my case scenario:

I have a Jetson nano (ip:192.168.1.100) connected to a router (IP:192.168.1.1) and a Hikvision camera (IP:192.168.1.64) is connected to that router. My jetson nano is running a docker image that moves the camera to follow a person. This script is written in python.

The problem I have is with the camera movement. When I start my script and it sends the movement command for the camera, it is working fine. When I stop the script and run it again, the camera moves only a little bit. I thought it was a problem in the python script, but the camera doesn't take any commands at all, even from other sources. I did try to move it with the Device Network SDK from Hikvision, but it also moves for a split second then stops. However, the camera shows the black and white text on bottom left to show the current pan and tilt, which means that it received the command successfully, but didn't fully execute the command.

Steps I took to solve the problem:
  • Send a restart command for the device using /ISAPI => It didn't manage to make the camera respond back to the pan and tilt commands.
  • Unplug the power for the camera, and plug it back => it would go through the whole self-check process (pan and tilt check) but it won't respond also after.
  • Remove the internet cable and connect it back => still the same problem.
  • The only way for the camera to respond back, was to turn off power from the whole circuit (router, nano and the camera) then turn them on again.

My hunch is telling me it might be a problem with the IP of the camera? It might be blocking the IP of Nano after it sends a specific number of commands.

On a side note, I noticed that the camera restarts by itself after a while.

I'm willing to change my whole code to use different approach that /ISAPI as long as it would give me the functionality of moving the camera to a position on one command.
 
Top