Can you access AXIS PTZ controls via Ignition?

This is a broad question and I'm not certain if its even possible but I'm genuinely curios if there is a way to make some form of call from a perspective session to control an AXIS Pan-Tilt-Zoom camera.

This is a very loaded question, but if anyone has any ideas of how this may be possible I'm all ears.

Thank you,

Cam

You have a whole library of APIs you can call with web requests, check out the PTZ section.

This is exceptional! Thank you!

However, I am now facing a different issue that you may also have ran into if you know about these.

This is my very simple function call on a button click event:

system.net.httpGet("https://10.10.0.3/axis-cgi/com/ptz.cgi?zoom=1000", username = "***", password = "***")

The username and password fields are correct on my end.

This is giving me this error:

com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 2, in runAction IOError: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.python.core.Py.fromIOException(Py.java:216)
at org.python.core.Py.IOError(Py.java:183)
at com.inductiveautomation.ignition.common.script.builtin.AbstractNetUtilities.httpGet(AbstractNetUtilities.java:274)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:190)
at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:552)
at org.python.core.PyObject.__call__(PyObject.java:400)
at org.python.pycode._pyx17.runAction$1(:2)
at org.python.pycode._pyx17.call_function()
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
at org.python.core.PyFunction.function___call__(PyFunction.java:474)
at org.python.core.PyFunction.__call__(PyFunction.java:469)
at org.python.core.PyFunction.__call__(PyFunction.java:464)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:846)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:828)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:832)
at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1009)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:897)
at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:158)
at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:97)
at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:74)
at com.inductiveautomation.perspective.gateway.action.ActionDecorator.runAction(ActionDecorator.java:18)
at com.inductiveautomation.perspective.gateway.action.SecuredAction.runAction(SecuredAction.java:44)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)
at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)
at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException: IOError: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
... 38 common frames omitted

Its very long and gross but it confuses me because the get request has the identification credentials and it seems to be a security error.

not sure how to proceed but any insights would be helpful while I try and fix this issue.

Thank you,

Cam

Try the call over HTTP instead of HTTPS. The camera is probably using a self signed certificate.

Also, you should use system.net.httpClient instead of system.net.httpGet, my understanding is that .httpGet is just leftover for legacy reasons.

2 Likes

With a little looking it seems that the self signed certificate is not trusted. So I added a bypass for testing purposes to see if I could actually make it work before I went about adding the certificate and what not. (from here: Unable to POST on a REST API using https - #8 by Ben_Hunt)

This is currently what I have in a simple button event:

client = system.net.httpClient(bypass_cert_validation = True)
url = "https://10.10.0.3/axis-cgi/com/ptz.cgi?zoom=1000"
client.post(url)
<Response@1172685220 'https://10.10.0.3/axis-cgi/com/ptz.cgi?zoom=1000' [401]>

However, this still does not seem to accept the zoom function to the camera regardless of HTTP or HTTPS. The password and username variables also don't seem to make a difference. 401 is still the return.

The weird thing about this is I did a basic test and just threw the URL into a browser and ran it and it will zoom the camera in but not via the button.

If you have any further suggestions they would be greatly appreciated.

Thank you,

Cam

You will definitely need to authenticate before you can make the API call.
IIRC Axis cameras use Digest Auth.

Have you been able to work with the AXIS cameras through ignition? Because I did some more digging and found this:

which informed me via the .net function provided by ignition you cannot authenticate with these cameras to perform actions?

I have an idea for a potential work around that involves the 'requests' header in python but cannot seem to add it to the scripting library properly to call it. Would you happen to know how to do that? Or do you have a better way?

Thank you,

Cam

I have, but it’s been a while and I don’t have a code reference or a camera to test with (our office cameras are Axis, but they’re on a VLAN I can’t reach without much effort).

Try looking up resources for accomplishing digest auth with Java’s HTTP client. The examples will need to be adapted for Jython, but the same general approach will apply.

Relevant SO thread.

Hello,

I am using this script to connect to HIKVision camera with digest authentification

import urllib2 
p = urllib2.HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, "http://{0}/ISAPI/ContentMgmt/search".format(self.getSibling("TextField_CameraIP").props.text), "{0}".format(self.getSibling("TextField_CameraUser").props.text), "{0}".format(self.getSibling("PasswordField_Camera").props.text))
handler = urllib2.HTTPDigestAuthHandler(p)
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)
request = urllib2.Request("http://{0}/ISAPI/ContentMgmt/search".format(self.getSibling("TextField_CameraIP").props.text), data='<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>8EEB2D93-199C-422E-AC4B-2FEAFD8411A5</searchID><trackList><trackID>101</trackID></trackList><timeSpanList><timeSpan><startTime>{0}</startTime><endTime>{1}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults></CMSearchDescription>'.format(start, end))
f = opener.open(request)
tracks = f.read()```

Thank you both!

Cam

Hi Andrii,

I know this thread is technically closed but I was just wondering if you know how to make calls that involve JSON? Because I can get the simple ones to work that move the camera but I cannot get the ones with JSON to work for user management and things like that.

To be more specific something like this:

Thank you,

Cam

Hi Cam,

I guess you should send POST request with json body.
Here is an example how to do it with urllib2 library