diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DOMUtils.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/DOMUtils.java similarity index 66% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DOMUtils.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/DOMUtils.java index 72a681d6e..dfa3fd9ac 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DOMUtils.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/DOMUtils.java @@ -12,14 +12,20 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import java.io.*; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import org.w3c.dom.Node; +import org.w3c.dom.Document; + +import org.xml.sax.InputSource; /** * Class for XML document utilities @@ -46,4 +52,22 @@ public static String getString(Node n) { } return null; } + + /** Gets Document from string */ + public static Document getDocument(String s) { + Document doc = null; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(s)); + doc = db.parse(is); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return doc; + } } diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DeviceService.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/DeviceService.java similarity index 68% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DeviceService.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/DeviceService.java index a17370ce9..072b49650 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/DeviceService.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/DeviceService.java @@ -12,9 +12,10 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.NodeList; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -29,7 +30,6 @@ public DeviceService(String deviceServiceAddress, String u, String p) { namespace = "http://www.onvif.org/ver10/device/wsdl"; username = u; password = p; - authenticate = false; } public static DeviceService getDeviceService(String deviceServiceAddress, String u, String p) { @@ -57,6 +57,39 @@ public String getServices() { return sendRequestDocument(doc); } + /** Gets a service address by its namespace */ + public String getServiceAddr(String namespace) { + String servicesRes = getServices(); + Document servicesDoc = DOMUtils.getDocument(servicesRes); + + NodeList services = servicesDoc.getElementsByTagNameNS("*", "Service"); + for (int i = 0; i < services.getLength(); i++) { + Element service = (Element) services.item(i); + Element ns = (Element) service.getElementsByTagNameNS("*", "Namespace").item(0); + + if (ns.getTextContent().equalsIgnoreCase(namespace)) { + Element addr = (Element) service.getElementsByTagNameNS("*", "XAddr").item(0); + return addr.getTextContent(); + } + } + return null; + } + + /** Get the PTZ binding address */ + public String getPTZBinding() { + return getServiceAddr("http://www.onvif.org/ver20/ptz/wsdl"); + } + + /** Get the media binding address */ + public String getMediaBinding() { + return getServiceAddr("http://www.onvif.org/ver10/media/wsdl"); + } + + /** Get the imaging binding address */ + public String getImagingBinding() { + return getServiceAddr("http://www.onvif.org/ver20/imaging/wsdl"); + } + /** Document builder function for GetScopes */ public Document getScopesDocument() { Document doc = getBaseDocument(); @@ -70,7 +103,6 @@ public Document getScopesDocument() { /** Get the scope parameters of the device */ public String getScopes() { - authenticate = true; Document doc = getScopesDocument(); return sendRequestDocument(doc); } @@ -106,4 +138,20 @@ public Document getAuxiliaryCommandDocument(String command, String state) { return doc; } + + public Document getSystemRebootDocument() { + Document doc = getBaseDocument(); + Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); + + Element systemRebootElem = doc.createElement("wsdl:SystemReboot"); + body.appendChild(systemRebootElem); + + return doc; + } + + /** Reboots the device */ + public String systemReboot() { + Document doc = getSystemRebootDocument(); + return sendRequestDocument(doc); + } } diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/ImagingService.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/ImagingService.java similarity index 88% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/ImagingService.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/ImagingService.java index 077454595..8412ed8ca 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/ImagingService.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/ImagingService.java @@ -12,7 +12,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; @@ -29,7 +29,6 @@ public ImagingService(String imagingServiceAddress, String u, String p) { namespace = "http://www.onvif.org/ver20/imaging/wsdl"; username = u; password = p; - authenticate = true; } public static ImagingService getImagingService(String imagingServiceAddress, String u, String p) { @@ -176,6 +175,33 @@ public String setIris(String vToken, String value) { return sendRequestDocument(doc); } + /** + * Gets the iris attenuation + * + * @param vToken reference token to the relevant video source + * + * @return float value of iris + */ + public float getIris(String vToken) { + String docString = getImagingSettings(vToken); + Document doc = DOMUtils.getDocument(docString); + if (doc == null) return 0; + + Element iris = (Element) doc.getElementsByTagName("tt:Iris").item(0); + return Float.parseFloat(iris.getTextContent()); + } + + /** + * Increments the iris attenuation by retrieving and setting it (absolute) + * + * @param vToken reference token to the relevant video source + * @param value the requested iris attenuation; "auto", "manual", or a float + */ + public String incrementIris(String vToken, String value) { + float newIris = getIris(vToken) + Float.parseFloat(value); + return setIris(vToken, String.valueOf(newIris)); + } + /** Document builder function for GetMoveOptions */ public Document getMoveOptionsDocument(String vToken) { Document doc = getBaseDocument(); @@ -201,11 +227,6 @@ public String getMoveOptions(String vToken) { return sendRequestDocument(doc); } - /** Document builder function for Move request; uses default mode (relative). */ - public Document getMoveDocument(String vToken, float distance) { - return getMoveDocument(vToken, distance, ""); - } - /** Document builder function for Move request; takes move mode as a parameter. */ public Document getMoveDocument(String vToken, float distance, String mode) { Document doc = getBaseDocument(); @@ -257,12 +278,23 @@ public Document getMoveDocument(String vToken, float distance, String mode) { * * @param vToken reference token to the relevant video source * @param distance the requested move distance + * @param mode mode to send to device ("continuous", "absolute", "relative") */ - public String moveFocus(String vToken, float distance) { - Document doc = getMoveDocument(vToken, distance); + public String moveFocus(String vToken, float distance, String mode) { + Document doc = getMoveDocument(vToken, distance, mode); return sendRequestDocument(doc); } + /** + * Moves the focus lens + * + * @param vToken reference token to the relevant video source + * @param distance the requested move distance + */ + public String moveFocus(String vToken, float distance) { + return moveFocus(vToken, distance, "continuous"); + } + /** Document builder function for GetStatus */ public Document getStatusDocument(String vToken) { Document doc = getBaseDocument(); diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/MediaService.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/MediaService.java similarity index 96% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/MediaService.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/MediaService.java index 2b99a423f..64bd1ac16 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/MediaService.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/MediaService.java @@ -12,7 +12,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; @@ -29,7 +29,6 @@ public MediaService(String mediaServiceAddress, String u, String p) { namespace = "http://www.onvif.org/ver10/media/wsdl"; username = u; password = p; - authenticate = true; } public static MediaService getMediaService(String mediaServiceAddress, String u, String p) { diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifPTZPoller.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifPTZPoller.java index 79f952a32..32266e38c 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifPTZPoller.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifPTZPoller.java @@ -1,7 +1,7 @@ /* * IRIS -- Intelligent Roadway Information System * Copyright (C) 2014 AHMCT, University of California - * Copyright (C) 2016-2020 Minnesota Department of Transportation + * Copyright (C) 2016-2023 Minnesota Department of Transportation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,55 +46,82 @@ public OnvifPTZPoller(CommLink link) { super(link, HTTP, ONVIF_LOG); } - // TODO: uncomment as implemented - static private OnvifProp createDeviceReqProp(DeviceRequest r) { - PTZCommandProp prop = new PTZCommandProp("imaging"); + static private PTZCommandProp createDeviceReqProp(PTZCommandProp prop, DeviceRequest r) { + if (prop == null) return null; + switch (r) { - //case CAMERA_FOCUS_NEAR: - // prop.addFocus(-1); - // return prop; - //case CAMERA_FOCUS_FAR: - // prop.addFocus(1); - // return prop; - //case CAMERA_FOCUS_STOP: - // prop.addFocus(0); - // return prop; - //case CAMERA_IRIS_CLOSE: - // prop.addIris(-1); - // return prop; - //case CAMERA_IRIS_OPEN: - // prop.addIris(1); - // return prop; - //case CAMERA_IRIS_STOP: - // prop.addIris(0); - // return prop; - //case CAMERA_FOCUS_MANUAL: - // prop.addAutoFocus(false); - // return prop; - //case CAMERA_FOCUS_AUTO: - // prop.addAutoFocus(true); - // return prop; - //case CAMERA_IRIS_MANUAL: - // prop.addAutoIris(false); - // return prop; - //case CAMERA_IRIS_AUTO: - // prop.addAutoIris(true); - // return prop; - case RESET_DEVICE: + case CAMERA_FOCUS_NEAR: + prop.addFocus(-1); + return prop; + case CAMERA_FOCUS_FAR: + prop.addFocus(1); + return prop; + case CAMERA_FOCUS_STOP: + prop.addFocus(0); + return prop; + // 0dB is completely open, >0 is more closed + case CAMERA_IRIS_CLOSE: + prop.addIris(1); + return prop; + case CAMERA_IRIS_OPEN: + prop.addIris(-1); + return prop; + case CAMERA_IRIS_STOP: + prop.addIris(0); + return prop; + case CAMERA_FOCUS_MANUAL: + prop.addAutoFocus(false); + return prop; + case CAMERA_FOCUS_AUTO: + prop.addAutoFocus(true); + return prop; + case CAMERA_IRIS_MANUAL: + prop.addAutoIris(false); + return prop; + case CAMERA_IRIS_AUTO: + prop.addAutoIris(true); + return prop; case CAMERA_WIPER_ONESHOT: - // FIXME: create SerialWriteProp - return null; + prop.addWiperOneshot(); + return prop; + case RESET_DEVICE: + prop.addReboot(); + return prop; default: return null; } } + /** Gets a base prop from camera's password field and requested service type */ + private PTZCommandProp getBaseProp(CameraImpl c, String type) { + String userpass = c.getController().getPassword(); + PTZCommandProp prop; + + if (userpass == null) + // don't set null user or password; use empty string instead + prop = new PTZCommandProp(type, "", ""); + else if (userpass.split(":").length < 2) + // if only one value, assume password to match label in client UI + prop = new PTZCommandProp(type, "", userpass); + else { + // otherwise, not null and has two values -> use them + String[] loginArr = userpass.split(":"); + prop = new PTZCommandProp(type, loginArr[0], loginArr[1]); + } + + // set url; preface with http:// if missing + String url = c.getController().getCommLink().getUri(); + if (!url.contains("http://")) + url = "http://" + url; + prop.setUrl(url); + + return prop; + } + /** Send a PTZ camera move command */ @Override public void sendPTZ(CameraImpl c, float p, float t, float z) { - PTZCommandProp prop = new PTZCommandProp("ptz"); - String url = c.getController().getCommLink().getUri(); - prop.setUrl(url); + PTZCommandProp prop = getBaseProp(c, "ptz"); prop.addPanTiltZoom(p, t, z); addOp(new OpOnvifPTZ(c, prop)); } @@ -102,9 +129,7 @@ public void sendPTZ(CameraImpl c, float p, float t, float z) { /** Send a "store camera preset" command */ @Override public void sendStorePreset(CameraImpl c, int preset) { - PTZCommandProp prop = new PTZCommandProp("ptz"); - String url = c.getController().getCommLink().getUri(); - prop.setUrl(url); + PTZCommandProp prop = getBaseProp(c, "ptz"); prop.addStorePreset(preset); addOp(new OpOnvifPTZ(c, prop)); } @@ -112,9 +137,7 @@ public void sendStorePreset(CameraImpl c, int preset) { /** Send a "recall camera preset" command */ @Override public void sendRecallPreset(CameraImpl c, int preset) { - PTZCommandProp prop = new PTZCommandProp("ptz"); - String url = c.getController().getCommLink().getUri(); - prop.setUrl(url); + PTZCommandProp prop = getBaseProp(c, "ptz"); prop.addRecallPreset(preset); addOp(new OpOnvifPTZ(c, prop)); } @@ -124,7 +147,11 @@ public void sendRecallPreset(CameraImpl c, int preset) { * @param dr Device request to send. */ @Override public void sendRequest(CameraImpl c, DeviceRequest dr) { - OnvifProp prop = createDeviceReqProp(dr); + PTZCommandProp prop = getBaseProp(c, "imaging"); + if (dr == DeviceRequest.CAMERA_WIPER_ONESHOT) + prop = getBaseProp(c, "ptz"); + + prop = createDeviceReqProp(prop, dr); if (prop != null) addOp(new OpOnvifPTZ(c, prop)); } diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifProp.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifProp.java index f5e03d178..9474dec6e 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifProp.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/OnvifProp.java @@ -1,6 +1,6 @@ /* * IRIS -- Intelligent Roadway Information System - * Copyright (C) 2016-2022 Minnesota Department of Transportation + * Copyright (C) 2016-2023 Minnesota Department of Transportation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,11 +18,15 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + import us.mn.state.dot.tms.server.ControllerImpl; import us.mn.state.dot.tms.server.comm.ControllerProperty; -import us.mn.state.dot.tms.server.comm.onvifptz.lib.*; import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Onvif Property. @@ -32,49 +36,163 @@ */ abstract public class OnvifProp extends ControllerProperty { - /** Onvif Service Path */ - String service_path; - /** Device URL */ String url; - /** ONVIF Service */ - Service service; + /** Device controller's username and password */ + String user, pass; - /** SOAP Message */ - Document message; + /** + * List of commands for multi-step operation + * + * String[] is { "OpName", "param1", ... } + */ + List cmds; /** Create a new Onvif property */ protected OnvifProp() { - service_path = ""; + cmds = new ArrayList(); } - /** Get as a string */ - @Override - public String toString() { - if (message != null) - return DOMUtils.getString(message); - return ""; - } - - /** Get the path + query for a property */ - @Override - public String getPathQuery() { - return service_path; + /** Logger method */ + protected void log(String s) { + OnvifPTZPoller.slog("PTZCommandProp:" + s); } public void setUrl(String u) { url = u; } - /** Send the SOAP message */ + /** Build and send the SOAP messages */ public String sendSoap() { - if (service != null && message != null) - return service.sendRequestDocument(message); - else if (service == null) - return "Error sending SOAP message - null service"; - else - return "Error sending SOAP message - null message"; + // if no cmd items, nothing to do + if (cmds.size() < 1) { + return "No cmd specified"; + } + + // create each service (device service binding specified by ONVIF standard) + DeviceService dev = DeviceService.getDeviceService(url + "/onvif/device_service", user, pass); + PTZService ptz = PTZService.getPTZService(dev.getPTZBinding(), user, pass); + MediaService media = MediaService.getMediaService(dev.getMediaBinding(), user, pass); + ImagingService img = ImagingService.getImagingService(dev.getImagingBinding(), user, pass); + + String mediaProfile = null, videoSource = null; + int mediaWidth = 0, videoWidth = 0; // to find maximum values + + // Should contain all necessary tokens + Document getProfilesRes = DOMUtils.getDocument(media.getProfiles()); + + if (getProfilesRes != null) { + NodeList profiles = getProfilesRes.getElementsByTagName("trt:Profiles"); + for (int i = 0; i < profiles.getLength(); i++) { + int mx = 0, vx = 0; + Element profile = (Element) profiles.item(i); + + // get the video source and its width + Element videoConfig = (Element) profile.getElementsByTagName("tt:VideoSourceConfiguration").item(0); + if (videoConfig == null) continue; // we want a profile with a video source + Element sourceToken = (Element) videoConfig.getElementsByTagName("tt:SourceToken").item(0); + Element bounds = (Element) videoConfig.getElementsByTagName("tt:Bounds").item(0); + vx = Integer.parseInt(bounds.getAttribute("width")); + + // get the video encoder and its width, if applicable; only for better profile selection + Element encoderConfig = (Element) profile.getElementsByTagName("tt:VideoEncoderConfiguration").item(0); + if (encoderConfig != null) { + Element widthElem = (Element) encoderConfig.getElementsByTagName("tt:Width").item(0); + mx = Integer.parseInt(widthElem.getTextContent()); + } + + // if video source bigger than current, replace + if (vx >= videoWidth) { + log("Video width larger. Setting videoSource..."); + videoSource = sourceToken.getTextContent(); + videoWidth = vx; + } + // replace media profile only if it's larger and the attached source is no smaller + if (mx >= mediaWidth && vx >= videoWidth) { + log("Both widths larger. Setting mediaProfile..."); + mediaProfile = profile.getAttribute("token"); + mediaWidth = mx; + } + } + } + + if (mediaProfile != null) + log("Set media profile: " + mediaProfile); + if (videoSource != null) + log("Set video source: " + videoSource); + + // if multi-step operations, send each operation + StringBuilder sb = new StringBuilder(); + for (String[] c : cmds) { + switch (c[0]) { + case "ptz": + if (c.length < 4) { + sb.append("Error sending ptz message - missing pan, tilt, and/or zoom"); + break; + } + sb.append(ptz.continuousMove(mediaProfile, Float.parseFloat(c[1]), Float.parseFloat(c[2]), Float.parseFloat(c[3]))); + break; + case "storepreset": + if (c.length < 2) { + sb.append("Error storing preset - missing name"); + break; + } + sb.append(ptz.setPreset(mediaProfile, c[1])); + break; + case "recallpreset": + if (c.length < 2) { + sb.append("Error recalling preset - missing name"); + break; + } + sb.append(ptz.gotoPreset(mediaProfile, c[1])); + break; + case "movefocus": + if (c.length < 2) { + sb.append("Error moving focus - missing amount"); + break; + } + if (c[1].equals("0") || c[1].equals("0.0")) + sb.append(img.stop(videoSource)); + else + sb.append(img.moveFocus(videoSource, Float.parseFloat(c[1]))); + break; + case "iris": + if (c.length < 2) { + sb.append("Error sending iris increment message - no value given"); + break; + } + sb.append(img.incrementIris(videoSource, c[1])); + break; + case "wiper": + sb.append(ptz.wiperOneshot(mediaProfile)); + break; + case "autofocus": + if (c.length < 2) { + sb.append("Error sending autofocus message - no value given"); + break; + } + sb.append(img.setFocus(videoSource, c[1])); + break; + case "autoiris": + if (c.length < 2) { + sb.append("Error sending auto iris message - no value given"); + break; + } + sb.append(img.setIris(videoSource, c[1])); + break; + case "reboot": + sb.append(dev.systemReboot()); + break; + default: + sb.append("Unexpected cmd"); + break; + } + + sb.append("\n"); + } + + return sb.toString(); } /** Encode a STORE request */ diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/OpOnvifPTZ.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/OpOnvifPTZ.java index 2fb6b9688..cec6e850c 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/OpOnvifPTZ.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/OpOnvifPTZ.java @@ -1,7 +1,7 @@ /* * IRIS -- Intelligent Roadway Information System * Copyright (C) 2014-2015 AHMCT, University of California - * Copyright (C) 2016 Minnesota Department of Transportation + * Copyright (C) 2016-2023 Minnesota Department of Transportation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZCommandProp.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZCommandProp.java index de0641754..03f75cba6 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZCommandProp.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZCommandProp.java @@ -1,6 +1,6 @@ /* * IRIS -- Intelligent Roadway Information System - * Copyright (C) 2016 Minnesota Department of Transportation + * Copyright (C) 2016-2023 Minnesota Department of Transportation * Copyright (C) 2014-2015 AHMCT, University of California * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,6 @@ import java.security.NoSuchAlgorithmException; import java.io.UnsupportedEncodingException; -import us.mn.state.dot.tms.server.comm.onvifptz.lib.*; - /** * PTZ command property. * @@ -29,103 +27,84 @@ */ public class PTZCommandProp extends OnvifProp { - /** PTZ command params */ - private enum Param { - PAN_TILT ("continuouspantiltmove"), - ZOOM ("continuouszoommove"), - RECALL_PRESET ("gotoserverpresetno"), - FOCUS ("continuousfocusmove"), - IRIS ("continuousirismove"), - AUTO_FOCUS ("autofocus"), - AUTO_IRIS ("autoiris"); - - private Param(String c) { - cmd = c; - } - public final String cmd; - } - - /** Logger method */ - private void log(String s) { - OnvifPTZPoller.slog("PTZCommandProp:" + s); - } - /** Create a new PTZ command property */ - public PTZCommandProp(String service) { - switch (service) { - case "device": - service_path = "/onvif/device_service"; - break; - case "media": - service_path = ":80/onvif/media"; - break; - case "ptz": - service_path = ":80/onvif/ptz"; - break; - case "imaging": - service_path = ":80/onvif/imaging"; - break; - default: - service_path = "/onvif/device_service"; - } + public PTZCommandProp(String service, String u, String p) { + super(); + user = u; + pass = p; } - /** Set message to PanTiltZoom SOAP message */ + /** Adds ptz command to callback */ public void addPanTiltZoom(float p, float t, float z) { - log("Final URL to PTZService: " + url + service_path); - PTZService ptz = PTZService.getPTZService(url + service_path, "admin", "admin"); - message = ptz.getContinuousMoveDocument(p, t, z); - log("Message from PTZService: " + DOMUtils.getString(message)); - - // set service field for sending from OnvifProp - service = ptz; + cmds.add(new String[] { + "ptz", + String.valueOf(p), + String.valueOf(t), + String.valueOf(z) + }); } - /** Add a store preset param */ + /** Sets message to store preset */ public void addStorePreset(int p) { - log("Final URL to PTZService: " + url + service_path); - PTZService ptz = PTZService.getPTZService(url + service_path, "admin", "admin"); - message = ptz.setPresetDocument(String.valueOf(p)); - log("Message from PTZService: " + DOMUtils.getString(message)); - - // set service field for sending from OnvifProp - service = ptz; + cmds.add(new String[] { + "storepreset", + String.valueOf(p) + }); } - /** Add a recall preset param */ + /** Sets message to recall preset */ public void addRecallPreset(int p) { - log("Final URL to PTZService: " + url + service_path); - PTZService ptz = PTZService.getPTZService(url + service_path, "admin", "admin"); - message = ptz.gotoPresetDocument(String.valueOf(p)); - log("Message from PTZService: " + DOMUtils.getString(message)); + cmds.add(new String[] { + "recallpreset", + String.valueOf(p) + }); + } - // set service field for sending from OnvifProp - service = ptz; + /** Sets message to move the focus */ + public void addFocus(int f) { + cmds.add(new String[] { + "movefocus", + String.valueOf(f) + }); } - //TODO: implement focus and iris commands below + /** Adds iris command to callback */ + public void addIris(int i) { + cmds.add(new String[] { + "iris", + String.valueOf(i) + }); + } - ///** Add a focus param */ - //public void addFocus(int f) { - // - //} + /** Adds wiper oneshot to callback */ + public void addWiperOneshot() { + cmds.add(new String[] { "wiper" }); + } - ///** Add an iris param */ - //public void addIris(int i) { - // log("Final URL to ImagingService: " + url + service_path); - // ImagingService imaging = ImagingService.getImagingService(url + service_path, "admin", "admin"); - // message = setImagingSettingsDocument(vToken, "iris", i); - // log("Message from ImagingService: " + message); + /** Adds reboot to callback */ + public void addReboot() { + cmds.add(new String[] { "reboot" }); + } - // // set service field for sending from OnvifProp - // service = imaging; - //} + /** Sets message to auto focus */ + public void addAutoFocus(boolean on) { + cmds.add(new String[] { + "autofocus", + on ? "Auto" : "Manual" + }); + } - ///** Add an auto-focus param */ - //public void addAutoFocus(boolean on) { - //} + /** Sets message to auto iris */ + public void addAutoIris(boolean on) { + cmds.add(new String[] { + "autoiris", + on ? "Auto" : "Manual" + }); + } - ///** Add an auto-iris param */ - //public void addAutoIris(boolean on) { - //} + /** Appends autoiris and autofocus to callback */ + public void addAutoIrisAndFocus() { + cmds.add(new String[] { "autoiris", "Auto" }); + cmds.add(new String[] { "autofocus", "Auto" }); + } } diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/PTZService.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZService.java similarity index 77% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/PTZService.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/PTZService.java index d1dba088d..761907b19 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/PTZService.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/PTZService.java @@ -12,7 +12,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; @@ -29,7 +29,6 @@ public PTZService(String ptzServiceAddress, String u, String p) { namespace = "http://www.onvif.org/ver20/ptz/wsdl"; username = u; password = p; - authenticate = true; } public static PTZService getPTZService(String ptzServiceAddress, String u, String p) { @@ -122,7 +121,7 @@ public String getNode(String nToken) { } /** Document builder function for ContinuousMove */ - public Document getContinuousMoveDocument(float xVel, float yVel, float zVel) { + public Document getContinuousMoveDocument(String profile, float xVel, float yVel, float zVel) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -130,7 +129,7 @@ public Document getContinuousMoveDocument(float xVel, float yVel, float zVel) { body.appendChild(continuousMove); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profile)); continuousMove.appendChild(profileToken); Element velocity = doc.createElement("wsdl:Velocity"); @@ -153,17 +152,18 @@ public Document getContinuousMoveDocument(float xVel, float yVel, float zVel) { /** * Sends a continuous move operation to the PTZ node; currently uses hard-coded MediaProfile token * - * @param x the x value (pan speed) of the move [-1.0, 1.0] - * @param y the y value (tilt speed) of the move [-1.0, 1.0] - * @param z the zoom speed of the move [-1.0, 1.0] + * @param profile the profile to send the operation to + * @param x the x value (pan speed) of the move [-1.0, 1.0] + * @param y the y value (tilt speed) of the move [-1.0, 1.0] + * @param z the zoom speed of the move [-1.0, 1.0] */ - public String continuousMove(float x, float y, float z) { - Document doc = getContinuousMoveDocument(x, y, z); + public String continuousMove(String profile, float x, float y, float z) { + Document doc = getContinuousMoveDocument(profile, x, y, z); return sendRequestDocument(doc); } /** Document builder function for RelativeMove */ - public Document getRelativeMoveDocument(float x, float y, float z) { + public Document getRelativeMoveDocument(String profile, float x, float y, float z) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -171,7 +171,7 @@ public Document getRelativeMoveDocument(float x, float y, float z) { body.appendChild(relativeMove); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profile)); relativeMove.appendChild(profileToken); Element translation = doc.createElement("wsdl:Translation"); @@ -198,13 +198,13 @@ public Document getRelativeMoveDocument(float x, float y, float z) { * @param y the y value (tilt) of the move [-1.0, 1.0] * @param z the zoom of the move [-1.0, 1.0] */ - public String relativeMove(float x, float y, float z) { - Document doc = getRelativeMoveDocument(x, y, z); + public String relativeMove(String profile, float x, float y, float z) { + Document doc = getRelativeMoveDocument(profile, x, y, z); return sendRequestDocument(doc); } /** Document builder function for GetPresets */ - public Document getPresetsDocument() { + public Document getPresetsDocument(String profile) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -212,7 +212,7 @@ public Document getPresetsDocument() { body.appendChild(getPresetsElement); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profile)); getPresetsElement.appendChild(profileToken); return doc; @@ -222,13 +222,13 @@ public Document getPresetsDocument() { * Gets the list of PTZ presets for the PTZNode of the selected * MediaProfile; currently uses hardcoded profile token. */ - public String getPresets() { - Document doc = getPresetsDocument(); + public String getPresets(String profile) { + Document doc = getPresetsDocument(profile); return sendRequestDocument(doc); } /** Document builder function for GotoPreset */ - public Document gotoPresetDocument(String pToken) { + public Document gotoPresetDocument(String profToken, String pToken) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -236,7 +236,7 @@ public Document gotoPresetDocument(String pToken) { body.appendChild(gotoPresetElement); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profToken)); gotoPresetElement.appendChild(profileToken); Element presetToken = doc.createElement("wsdl:PresetToken"); @@ -248,17 +248,18 @@ public Document gotoPresetDocument(String pToken) { /** * Point the camera in a saved preset direction for the PTZNode of - * selected MediaProfile; currently uses hardcoded profile token. + * selected MediaProfile * - * @param pToken reference token to the saved PTZ preset + * @param profileToken reference to the media profile + * @param pToken reference token to the saved PTZ preset */ - public String gotoPreset(String pToken) { - Document doc = gotoPresetDocument(pToken); + public String gotoPreset(String profileToken, String pToken) { + Document doc = gotoPresetDocument(profileToken, pToken); return sendRequestDocument(doc); } /** Document builder function for SetPreset */ - public Document setPresetDocument(String pToken) { + public Document setPresetDocument(String profToken, String pToken) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -266,7 +267,7 @@ public Document setPresetDocument(String pToken) { body.appendChild(setPresetElement); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profToken)); setPresetElement.appendChild(profileToken); Element presetToken = doc.createElement("wsdl:PresetToken"); @@ -277,20 +278,20 @@ public Document setPresetDocument(String pToken) { } /** - * Saves the current position to a preset; currently uses hardcoded - * MediaProfile token. + * Saves the current position to a preset * * Calling this will overwrite if an existing preset has a matching token. * - * @param pToken unique reference token for the PTZ preset + * @param profileToken reference to the media profile + * @param pToken unique reference token for the PTZ preset */ - public String setPreset(String pToken) { - Document doc = setPresetDocument(pToken); + public String setPreset(String profileToken, String pToken) { + Document doc = setPresetDocument(profileToken, pToken); return sendRequestDocument(doc); } /** Document builder function for Stop */ - public Document getStopDocument() { + public Document getStopDocument(String profile) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -298,7 +299,7 @@ public Document getStopDocument() { body.appendChild(stop); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profile)); stop.appendChild(profileToken); Element panTilt = doc.createElement("wsdl:PanTilt"); @@ -313,8 +314,8 @@ public Document getStopDocument() { } /** Stops all ongoing PTZ movements */ - public String stop() { - Document doc = getStopDocument(); + public String stop(String profile) { + Document doc = getStopDocument(profile); return sendRequestDocument(doc); } @@ -323,7 +324,7 @@ public String stop() { * * Relevant for wiper and other extra functions */ - public Document getAuxiliaryCommandDocument(String command, String state) { + public Document getAuxiliaryCommandDocument(String profToken, String command, String state) { Document doc = getBaseDocument(); Element body = (Element) doc.getElementsByTagName("SOAP-ENV:Body").item(0); @@ -331,7 +332,7 @@ public Document getAuxiliaryCommandDocument(String command, String state) { body.appendChild(sendAuxiliaryCommand); Element profileToken = doc.createElement("wsdl:ProfileToken"); - profileToken.appendChild(doc.createTextNode("Profile1")); + profileToken.appendChild(doc.createTextNode(profToken)); sendAuxiliaryCommand.appendChild(profileToken); Element auxiliaryData = doc.createElement("wsdl:AuxiliaryData"); @@ -344,11 +345,20 @@ public Document getAuxiliaryCommandDocument(String command, String state) { /** * Calls SendAuxiliaryCommand, setting the wiper value * - * @param state the requested state ("On", "Off") of the wiper + * @param profileToken reference to the media profile + * @param state the requested state ("On", "Off") of the wiper */ - public String setWiper(String state) { - authenticate = true; - Document doc = getAuxiliaryCommandDocument("Wiper", state); + public String setWiper(String profileToken, String state) { + Document doc = getAuxiliaryCommandDocument(profileToken, "Wiper", state); return sendRequestDocument(doc); } + + /** + * Sets the wiper on and off immediately to emulate a single wiper action + */ + public String wiperOneshot(String profileToken) { + String onResponse = setWiper(profileToken, "On"); + String offResponse = setWiper(profileToken, "Off"); + return onResponse + offResponse; + } } diff --git a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/Service.java b/src/us/mn/state/dot/tms/server/comm/onvifptz/Service.java similarity index 95% rename from src/us/mn/state/dot/tms/server/comm/onvifptz/lib/Service.java rename to src/us/mn/state/dot/tms/server/comm/onvifptz/Service.java index b93e7bc62..5c2951d9d 100644 --- a/src/us/mn/state/dot/tms/server/comm/onvifptz/lib/Service.java +++ b/src/us/mn/state/dot/tms/server/comm/onvifptz/Service.java @@ -12,7 +12,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ -package us.mn.state.dot.tms.server.comm.onvifptz.lib; +package us.mn.state.dot.tms.server.comm.onvifptz; import java.net.HttpURLConnection; import java.net.URL; @@ -50,7 +50,11 @@ public abstract class Service { protected String namespace; protected String username; protected String password; - protected boolean authenticate; + + /** Logger method */ + protected void log(String s) { + OnvifPTZPoller.slog("PTZCommandProp:" + s); + } /** * Get the base document that all other services add to; creates the header @@ -169,14 +173,14 @@ public String sendRequestDocument(Document doc) { connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8"); - if (authenticate && !"".equals(username)) { + if (!"".equals(username)) { addSecurityHeaderDocument(doc); } else { - System.out.println("Sending unauthenticated request..."); + log("Sending unauthenticated request..."); } String soapRequest = DOMUtils.getString(doc); - System.out.println("Final SOAP request:\n" + soapRequest); + log("\nSending soapRequest to " + endpoint + ":\n" + soapRequest); try (OutputStream os = connection.getOutputStream()) { byte[] input = soapRequest.getBytes("utf-8");