Various Onvif fixes for IpCamera (#17732)

I made various changes and fixes to the ONVIF connection in the IpCamera binding.

Signed-off-by: David Masshardt <david@masshardt.ch>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
David Masshardt 2024-11-11 20:11:25 +01:00 committed by Ciprian Pascu
parent f51793d25b
commit aa13775a79
10 changed files with 394 additions and 182 deletions

View File

@ -209,6 +209,11 @@ If you do not specify any of these, the binding will use the default which shoul
| `gifPreroll`| Store this many snapshots from BEFORE you trigger a GIF creation. Default: `0` will not use snapshots and will instead use a realtime stream from the ffmpegInput URL |
| `ipWhitelist`| Enter any IPs inside brackets that you wish to allow to access the video stream. `DISABLE` the default value will turn this feature off. Example: `ipWhitelist="(127.0.0.1)(192.168.0.99)"` |
| `ptzContinuous`| If set to false (default) the camera will move using Relative commands, If set to true the camera will instead use continuous movements and will require an `OFF` command to stop the movement. |
| `onvifEventServiceType`| ONVIF event method to use. If camera does not report event capabilities, the event method can be forced here. |
| | `0` - Auto detect event capabilities. (Default) ONVIF event capabilities are detected automatically. PullMessages is prefered over WSBaseNotification because there is no way to determine if an WSBaseNotification subscription exists on startup. |
| | `1` - ONVIF events disabled. |
| | `2` - Force ONVIF PullMessages event method even if the camera does not claim to support this. |
| | `3` - Force ONVIF WSBaseSubscription event method even if the camera does not claim to support this. |
## Channels

View File

@ -29,6 +29,7 @@ public class CameraConfig {
private String password = "";
public boolean useToken = true;
private int onvifMediaProfile;
private int onvifEventServiceType;
private int pollTime;
private String ffmpegInput = "";
private String snapshotUrl = "";
@ -54,6 +55,10 @@ public class CameraConfig {
return onvifMediaProfile;
}
public int getOnvifEventServiceType() {
return onvifEventServiceType;
}
public String getFfmpegInputOptions() {
return ffmpegInputOptions;
}

View File

@ -12,6 +12,7 @@
*/
package org.openhab.binding.ipcamera.internal;
import java.io.ByteArrayInputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@ -19,7 +20,11 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.w3c.dom.Document;
/**
* The {@link Helper} class has static functions that help the IpCamera binding not need as many external libs.
@ -105,6 +110,13 @@ public class Helper {
return result;
}
public static Document loadXMLFromString(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
ByteArrayInputStream inputStream = new ByteArrayInputStream(xml.getBytes());
return builder.parse(inputStream);
}
/**
* The {@link encodeSpecialChars} Is used to replace spaces with %20 in Strings meant for URL queries.
*

View File

@ -106,7 +106,11 @@ public class ReolinkHandler extends ChannelDuplexHandler {
getAbilityResponse[0].error.detail);
return;
}
ipCameraHandler.reolinkScheduleVersion = getAbilityResponse[0].value.ability.scheduleVersion.ver;
if (getAbilityResponse[0].value.ability.scheduleVersion == null) {
ipCameraHandler.logger.debug("Camera has no Schedule support.");
} else {
ipCameraHandler.reolinkScheduleVersion = getAbilityResponse[0].value.ability.scheduleVersion.ver;
}
if (getAbilityResponse[0].value.ability.supportFtpEnable == null
|| getAbilityResponse[0].value.ability.supportFtpEnable.permit == 0) {
ipCameraHandler.logger.debug("Camera has no Enable FTP support.");

View File

@ -61,7 +61,6 @@ import org.openhab.binding.ipcamera.internal.IpCameraDynamicStateDescriptionProv
import org.openhab.binding.ipcamera.internal.MyNettyAuthHandler;
import org.openhab.binding.ipcamera.internal.ReolinkHandler;
import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection;
import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection.RequestType;
import org.openhab.binding.ipcamera.internal.servlet.CameraServlet;
import org.openhab.core.OpenHAB;
import org.openhab.core.library.types.DecimalType;
@ -1577,12 +1576,7 @@ public class IpCameraHandler extends BaseThingHandler {
checkCameraConnection();
break;
case ONVIF_THING:
onvifCamera.sendOnvifRequest(RequestType.Renew, onvifCamera.subscriptionXAddr);
if (onvifCamera.pullMessageRequests.intValue() == 0) {
logger.info("The alarm stream was not running for ONVIF camera {}, re-starting it now",
cameraConfig.getIp());
onvifCamera.sendOnvifRequest(RequestType.PullMessages, onvifCamera.subscriptionXAddr);
}
onvifCamera.checkAndRenewEventSubscription();
break;
case INSTAR_THING:
checkCameraConnection();
@ -1609,12 +1603,7 @@ public class IpCameraHandler extends BaseThingHandler {
sendHttpGET("/api.cgi?cmd=GetAiState&channel=" + cameraConfig.getNvrChannel() + reolinkAuth);
sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + reolinkAuth);
} else {
onvifCamera.sendOnvifRequest(RequestType.Renew, onvifCamera.subscriptionXAddr);
if (onvifCamera.pullMessageRequests.intValue() == 0) {
logger.debug("The alarm stream was not running for Reolink camera {}, re-starting it now",
cameraConfig.getIp());
onvifCamera.sendOnvifRequest(RequestType.PullMessages, onvifCamera.subscriptionXAddr);
}
onvifCamera.checkAndRenewEventSubscription();
}
break;
case DAHUA_THING:

View File

@ -53,6 +53,10 @@ public class OnvifCodec extends ChannelDuplexHandler {
switch (response.status().code()) {
case 200:
break;
case 400:
onvifConnection.processBadRequest(requestType);
ctx.close();
return;
case 401:
if (!response.headers().isEmpty()) {
for (CharSequence name : response.headers().names()) {
@ -111,6 +115,7 @@ public class OnvifCodec extends ChannelDuplexHandler {
@Override
public void handlerRemoved(@Nullable ChannelHandlerContext ctx) {
if (requestType == RequestType.PullMessages) {
onvifConnection.lastPullMessageReceivedTimestamp = System.currentTimeMillis();
onvifConnection.pullMessageRequests.decrementAndGet();
}
}

View File

@ -45,6 +45,9 @@ import org.openhab.core.types.StateOption;
import org.openhab.core.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
@ -130,16 +133,18 @@ public class OnvifConnection {
@SuppressWarnings("unused")
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
public String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
public String subscriptionXAddr = "";
public String subscriptionId = "";
private boolean isConnected = false;
private int mediaProfileIndex = 0;
private String rtspUri = "";
private IpCameraHandler ipCameraHandler;
private boolean supportsEvents = false; // camera has replied that it can do events
// Use/skip events even if camera support them. API cameras skip, as their own methods give better results.
private boolean usingEvents = false;
private int onvifEventServiceType = 0; // 0 = disabled, 1 = PullMessages, 2 = WSBaseSubscription
public AtomicInteger pullMessageRequests = new AtomicInteger();
private long createSubscriptionTimestamp;
public long lastPullMessageReceivedTimestamp;
// These hold the cameras PTZ position in the range that the camera uses, ie
// mine is -1 to +1
@ -228,7 +233,7 @@ public class OnvifConnection {
case GetProfiles:
return "<GetProfiles xmlns=\"http://www.onvif.org/ver10/media/wsdl\"/>";
case GetServiceCapabilities:
return "<GetServiceCapabilities xmlns=\"http://docs.oasis-open.org/wsn/b-2/\"></GetServiceCapabilities>";
return "<GetServiceCapabilities xmlns=\"http://www.onvif.org/ver10/events/wsdl\"></GetServiceCapabilities>";
case GetSnapshotUri:
return "<GetSnapshotUri xmlns=\"http://www.onvif.org/ver10/media/wsdl\"><ProfileToken>"
+ mediaProfileTokens.get(mediaProfileIndex) + "</ProfileToken></GetSnapshotUri>";
@ -238,14 +243,14 @@ public class OnvifConnection {
case GetSystemDateAndTime:
return "<GetSystemDateAndTime xmlns=\"http://www.onvif.org/ver10/device/wsdl\"/>";
case Subscribe:
return "<Subscribe xmlns=\"http://docs.oasis-open.org/wsn/b-2/\"><ConsumerReference><Address>http://"
return "<Subscribe xmlns=\"http://docs.oasis-open.org/wsn/b-2\" xmlns:wsa=\"http://www.w3.org/2005/08/addressing\"><ConsumerReference><wsa:Address>http://"
+ ipCameraHandler.hostIp + ":" + SERVLET_PORT + "/ipcamera/"
+ ipCameraHandler.getThing().getUID().getId()
+ "/OnvifEvent</Address></ConsumerReference></Subscribe>";
+ "/OnvifEvent</wsa:Address></ConsumerReference><InitialTerminationTime>PT600S</InitialTerminationTime></Subscribe>";
case Unsubscribe:
return "<Unsubscribe xmlns=\"http://docs.oasis-open.org/wsn/b-2/\"></Unsubscribe>";
return "<Unsubscribe xmlns=\"http://docs.oasis-open.org/wsn/b-2\"></Unsubscribe>";
case PullMessages:
return "<PullMessages xmlns=\"http://www.onvif.org/ver10/events/wsdl\"><Timeout>PT8S</Timeout><MessageLimit>1</MessageLimit></PullMessages>";
return "<PullMessages xmlns=\"http://www.onvif.org/ver10/events/wsdl\"><Timeout>PT8S</Timeout><MessageLimit>10</MessageLimit></PullMessages>";
case GetEventProperties:
return "<GetEventProperties xmlns=\"http://www.onvif.org/ver10/events/wsdl\"/>";
case RelativeMoveLeft:
@ -273,7 +278,7 @@ public class OnvifConnection {
+ mediaProfileTokens.get(mediaProfileIndex)
+ "</ProfileToken><Translation><Zoom x=\"-0.0240506344\" xmlns=\"http://www.onvif.org/ver10/schema\"/></Translation></RelativeMove>";
case Renew:
return "<Renew xmlns=\"http://docs.oasis-open.org/wsn/b-2\"><TerminationTime>PT10S</TerminationTime></Renew>";
return "<Renew xmlns=\"http://docs.oasis-open.org/wsn/b-2\"><TerminationTime>PT600S</TerminationTime></Renew>";
case GetConfigurations:
return "<GetConfigurations xmlns=\"http://www.onvif.org/ver20/ptz/wsdl\"></GetConfigurations>";
case GetConfigurationOptions:
@ -314,19 +319,24 @@ public class OnvifConnection {
logger.trace("ONVIF {} reply is: {}", requestType, message);
switch (requestType) {
case CreatePullPointSubscription:
supportsEvents = true;
subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
int start = message.indexOf("<dom0:SubscriptionId");
int end = message.indexOf("</dom0:SubscriptionId>");
if (start > -1 && end > start) {
subscriptionId = message.substring(start, end + 22);
setSubscriptionXAddr(message);
if (!subscriptionXAddr.isEmpty()) {
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
}
logger.debug("subscriptionXAddr={} subscriptionId={}", subscriptionXAddr, subscriptionId);
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
break;
case Subscribe:
setSubscriptionXAddr(message);
break;
case GetCapabilities:
parseXAddr(message);
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
setOnvifEventServiceType(message.contains("WSPullPointSupport>true"),
message.contains("WSSubscriptionPolicySupport>true"));
if (!getEventsSupported() && ipCameraHandler.cameraConfig.getOnvifEventServiceType() != 1) {
// If the camera does not report event capabilities here we also check with GetServiceCapabilities.
sendOnvifRequest(RequestType.GetServiceCapabilities, mediaXAddr);
} else {
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
}
break;
case GetDeviceInformation:
break;
@ -339,14 +349,13 @@ public class OnvifConnection {
sendPTZRequest(RequestType.GetNodes);
}
if (usingEvents) {// stops API cameras from getting sent ONVIF events.
sendOnvifRequest(RequestType.GetEventProperties, eventXAddr);
sendOnvifRequest(RequestType.GetServiceCapabilities, eventXAddr);
createSubscription();
}
break;
case GetServiceCapabilities:
if (message.contains("WSSubscriptionPolicySupport=\"true\"")) {
sendOnvifRequest(RequestType.Subscribe, eventXAddr);
}
setOnvifEventServiceType(message.contains("WSPullPointSupport=\"true\""),
message.contains("WSSubscriptionPolicySupport=\"true\""));
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
break;
case GetSnapshotUri:
String url = Helper.fetchXML(message, ":MediaUri", ":Uri");
@ -378,11 +387,16 @@ public class OnvifConnection {
parseDateAndTime(message);
break;
case PullMessages:
eventRecieved(message);
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
try {
eventRecieved(message);
} catch (Exception e) {
logger.error("Error processing PullMessages error:\n{}\nmessage: {}", e.toString(), message);
}
if (!subscriptionXAddr.isEmpty()) {
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
}
break;
case GetEventProperties:
sendOnvifRequest(RequestType.CreatePullPointSubscription, eventXAddr);
break;
case Renew:
break;
@ -409,6 +423,108 @@ public class OnvifConnection {
}
}
private void setOnvifEventServiceType(boolean cameraSupportsPullPointSupport,
boolean cameraSupportsSubscriptionPolicySupport) {
if (cameraSupportsPullPointSupport && ipCameraHandler.cameraConfig.getOnvifEventServiceType() == 0
|| ipCameraHandler.cameraConfig.getOnvifEventServiceType() == 2) {
onvifEventServiceType = 1;
} else if (cameraSupportsSubscriptionPolicySupport
&& ipCameraHandler.cameraConfig.getOnvifEventServiceType() == 0
|| ipCameraHandler.cameraConfig.getOnvifEventServiceType() == 3) {
onvifEventServiceType = 2;
}
}
public void processBadRequest(RequestType requestType) {
logger.trace("ONVIF {} processing bad request for camera {}.", requestType, ipAddress);
switch (requestType) {
case CreatePullPointSubscription:
subscriptionXAddr = "";
logger.debug("Camera {} returned bad request on CreatePullPointSubscription. Trying again later.",
ipAddress);
break;
case Subscribe:
subscriptionXAddr = "";
logger.debug("Camera {} returned bad request on WSBaseSubscription. Trying again later.", ipAddress);
break;
case GetServiceCapabilities:
logger.debug(
"Camera {} returned bad request on GetServiceCapabilities. Cannot auto detect supported event types.",
ipAddress);
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
break;
case PullMessages:
logger.debug("PullMessages returned bad request for camera {}, re-creating subscription now",
ipAddress);
createSubscription();
break;
case Renew:
logger.debug("Renew subscription returned bad request for camera {}, re-creating subscription now",
ipAddress);
createSubscription();
break;
default:
break;
}
}
void setSubscriptionXAddr(String message) {
subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
int start = message.indexOf("<dom0:SubscriptionId");
int end = message.indexOf("</dom0:SubscriptionId>");
if (start > -1 && end > start) {
subscriptionId = message.substring(start, end + 22);
}
logger.debug("subscriptionXAddr={} subscriptionId={}", subscriptionXAddr, subscriptionId);
}
public void createSubscription() {
if (!getEventsSupported()) {
// ONVIF events are disabled or not supported.
return;
}
// Only send new subscription every 5 seconds if the camera is offline or there are already too much
// subscriptions.
if (createSubscriptionTimestamp == 0) {
createSubscriptionTimestamp = System.currentTimeMillis();
} else if (System.currentTimeMillis() - createSubscriptionTimestamp < 5000) {
// Subscription sent less than 5 seconds ago.
return;
}
// Prefer PullPoint events over WSBaseSubscription because there is no way to check if a WSBaseSubscription is
// already registered on the camera.
if (onvifEventServiceType == 1) {
sendOnvifRequest(RequestType.CreatePullPointSubscription, eventXAddr);
} else if (onvifEventServiceType == 2) {
sendOnvifRequest(RequestType.Subscribe, eventXAddr);
}
}
/**
* This method should be executed regularly to renew the event subscription and to check if a new subscription is
* needed.
*/
public void checkAndRenewEventSubscription() {
if (getEventsSupported()) {
// If we get events via PullMessages check if a PullMessages request is running or we just received an
// answer in the last second. If this is not the case create a new PullMessages subscription.
if (onvifEventServiceType == 1 && pullMessageRequests.intValue() == 0
&& System.currentTimeMillis() - lastPullMessageReceivedTimestamp > 1000) {
logger.debug("The alarm stream was not running for camera {}, re-starting it now", ipAddress);
createSubscription();
} else if (!subscriptionXAddr.isEmpty()) {
// Renew the active subscription.
sendOnvifRequest(RequestType.Renew, subscriptionXAddr);
} else {
// The camera claims to have event support, but no subscription was created yet. Try to create a new
// subscription.
createSubscription();
}
}
}
/**
* The {@link removeIPandPortFromUrl} Will throw away all text before the cameras IP, also removes the IP and the
* PORT
@ -460,7 +576,7 @@ public class OnvifConnection {
}
temp = Helper.fetchXML(message, "<tt:Events", "tt:XAddr");
if (!temp.isEmpty()) {
subscriptionXAddr = eventXAddr = temp;
eventXAddr = temp;
logger.debug("eventsXAddr: {}", eventXAddr);
}
temp = Helper.fetchXML(message, "<tt:Media", "tt:XAddr");
@ -698,147 +814,178 @@ public class OnvifConnection {
}
public void eventRecieved(String eventMessage) {
String topic = Helper.fetchXML(eventMessage, "Topic", "tns1:");
if (topic.isEmpty()) {
logger.trace("No ONVIF Events occured in the last 8 seconds");
Document xmlDocument;
try {
xmlDocument = Helper.loadXMLFromString(eventMessage);
} catch (Exception e) {
logger.error("Error parsing ONVIF xml.", e);
return;
}
String dataName = Helper.fetchXML(eventMessage, "tt:Data", "Name=\"");
String dataValue = Helper.fetchXML(eventMessage, "tt:Data", "Value=\"");
logger.debug("ONVIF Event Topic: {}, Data: {}, Value: {}", topic, dataName, dataValue);
switch (topic) {
case "RuleEngine/CellMotionDetector/Motion":
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_CELL_MOTION_ALARM);
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_CELL_MOTION_ALARM);
}
break;
case "VideoAnalytics/Motion":
if ("Trigger".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if ("Normal".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "RuleEngine/tnsaxis:VMD3/vmd3_video_1":
case "RuleEngine/MotionRegionDetector/Motion":
case "VideoSource/MotionAlarm":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "AudioAnalytics/Audio/DetectedSound":
if ("true".equals(dataValue)) {
ipCameraHandler.audioDetected();
} else if ("false".equals(dataValue)) {
ipCameraHandler.noAudioDetected();
}
break;
case "RuleEngine/FieldDetector/ObjectsInside":
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
}
break;
case "RuleEngine/LineDetector/Crossed":
if ("ObjectId".equals(dataName)) {
ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
} else {
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
}
break;
case "RuleEngine/TamperDetector/Tamper":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.OFF);
}
break;
case "Device/tnsaxis:HardwareFailure/StorageFailure":
case "Device/HardwareFailure/StorageFailure":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooDark/AnalyticsService":
case "VideoSource/ImageTooDark/ImagingService":
case "VideoSource/ImageTooDark/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/GlobalSceneChange/AnalyticsService":
case "VideoSource/GlobalSceneChange/ImagingService":
case "VideoSource/GlobalSceneChange/RecordingService":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBright/AnalyticsService":
case "VideoSource/ImageTooBright/ImagingService":
case "VideoSource/ImageTooBright/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBlurry/AnalyticsService":
case "VideoSource/ImageTooBlurry/ImagingService":
case "VideoSource/ImageTooBlurry/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/Visitor":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/VehicleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/DogCatDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/FaceDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/PeopleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
}
break;
default:
logger.debug("Please report this camera has an un-implemented ONVIF event. Topic: {}", topic);
NodeList NotificationMessageNodeList = xmlDocument.getElementsByTagName("wsnt:NotificationMessage");
for (int i = 0; i < NotificationMessageNodeList.getLength(); i++) {
Element notificationMessageElement = (Element) NotificationMessageNodeList.item(i);
Element topicElement = (Element) notificationMessageElement.getElementsByTagName("wsnt:Topic").item(0);
String topic = topicElement.getFirstChild().getNodeValue().replace("tns1:", "");
String sourceName = "";
String sourceValue = "";
Element sourceElement = (Element) notificationMessageElement.getElementsByTagName("tt:Source").item(0);
if (sourceElement != null) {
Element sourceItemElement = (Element) sourceElement.getElementsByTagName("tt:SimpleItem").item(0);
sourceName = sourceItemElement.getAttributes().getNamedItem("Name").getNodeValue();
sourceValue = sourceItemElement.getAttributes().getNamedItem("Value").getNodeValue();
}
Element dataElement = (Element) notificationMessageElement.getElementsByTagName("tt:Data").item(0);
if (dataElement == null) {
// Events without data element are not relevant.
continue;
}
Element dataItemElement = (Element) dataElement.getElementsByTagName("tt:SimpleItem").item(0);
String dataName = dataItemElement.getAttributes().getNamedItem("Name").getNodeValue();
String dataValue = dataItemElement.getAttributes().getNamedItem("Value").getNodeValue();
logger.debug("ONVIF Event Topic: {}, Source name: {}, Source value: {}, Data name: {}, Data value: {}",
topic, sourceName, sourceValue, dataName, dataValue);
switch (topic) {
case "RuleEngine/CellMotionDetector/Motion":
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_CELL_MOTION_ALARM);
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_CELL_MOTION_ALARM);
}
break;
case "VideoAnalytics/Motion":
if ("Trigger".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if ("Normal".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "RuleEngine/tnsaxis:VMD3/vmd3_video_1":
case "RuleEngine/MotionRegionDetector/Motion":
case "VideoSource/MotionAlarm":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
}
break;
case "AudioAnalytics/Audio/DetectedSound":
if ("true".equals(dataValue)) {
ipCameraHandler.audioDetected();
} else if ("false".equals(dataValue)) {
ipCameraHandler.noAudioDetected();
}
break;
case "RuleEngine/FieldDetector/ObjectsInside":
if ("true".equals(dataValue)) {
ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
} else if ("false".equals(dataValue)) {
ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
}
break;
case "RuleEngine/LineDetector/Crossed":
if ("ObjectId".equals(dataName)) {
ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
} else {
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
}
break;
case "RuleEngine/TamperDetector/Tamper":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TAMPER_ALARM, OnOffType.OFF);
}
break;
case "Device/tnsaxis:HardwareFailure/StorageFailure":
case "Device/HardwareFailure/StorageFailure":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_STORAGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooDark/AnalyticsService":
case "VideoSource/ImageTooDark/ImagingService":
case "VideoSource/ImageTooDark/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/GlobalSceneChange/AnalyticsService":
case "VideoSource/GlobalSceneChange/ImagingService":
case "VideoSource/GlobalSceneChange/RecordingService":
if ("true".equals(dataValue) || "1".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue) || "0".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBright/AnalyticsService":
case "VideoSource/ImageTooBright/ImagingService":
case "VideoSource/ImageTooBright/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BRIGHT_ALARM, OnOffType.OFF);
}
break;
case "VideoSource/ImageTooBlurry/AnalyticsService":
case "VideoSource/ImageTooBlurry/ImagingService":
case "VideoSource/ImageTooBlurry/RecordingService":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/Visitor":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/VehicleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/DogCatDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/FaceDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/PeopleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
}
break;
default:
logger.debug("Please report this camera has an un-implemented ONVIF event. Topic: {}", topic);
}
}
}
@ -1018,7 +1165,7 @@ public class OnvifConnection {
}
public boolean getEventsSupported() {
return supportsEvents;
return onvifEventServiceType > 0;
}
public void setIsConnected(boolean isConnected) {
@ -1049,7 +1196,8 @@ public class OnvifConnection {
connecting.lock();// Lock out multiple disconnect()/connect() attempts as we try to send Unsubscribe.
try {
if (bootstrap != null) {
if (isConnected && usingEvents && !mainEventLoopGroup.isShuttingDown()) {
if (isConnected && usingEvents && !mainEventLoopGroup.isShuttingDown()
&& !subscriptionXAddr.isEmpty()) {
// Only makes sense to send if connected
// Some cameras may continue to send events even when they can't reach a server.
sendOnvifRequest(RequestType.Unsubscribe, subscriptionXAddr);

View File

@ -79,7 +79,9 @@ public class CameraServlet extends IpCameraServlet {
snapshotData.close();
break;
case "/OnvifEvent":
handler.onvifCamera.eventRecieved(req.getReader().toString());
ServletInputStream inputStream = req.getInputStream();
String xmlData = new String(inputStream.readAllBytes(), "UTF-8");
handler.onvifCamera.eventRecieved(xmlData);
break;
default:
logger.debug("Recieved unknown request \tPOST:{}", pathInfo);

View File

@ -526,6 +526,12 @@ thing-type.config.ipcamera.onvif.onvifMediaProfile.label = ONVIF Media Profile
thing-type.config.ipcamera.onvif.onvifMediaProfile.description = Cameras can supply more than one stream at different resolutions and formats. 0 selects the main-stream and 1 or above are the sub-streams. Sometimes you need to turn on sub-streams in the cameras setup before they can be used.
thing-type.config.ipcamera.onvif.onvifPort.label = ONVIF Port
thing-type.config.ipcamera.onvif.onvifPort.description = The port your camera uses for ONVIF connections. This is needed for PTZ movement, alarm events and auto discovery of RTSP and snapshot URLs.
thing-type.config.ipcamera.onvif.onvifEventServiceType.label = ONVIF event method
thing-type.config.ipcamera.onvif.onvifEventServiceType.description = ONVIF event method to use. If camera does not report event capabilities, the event method can be forced here.
thing-type.config.ipcamera.onvif.onvifEventServiceType.option.0 = Auto detect (0)
thing-type.config.ipcamera.onvif.onvifEventServiceType.option.1 = Disabled (1)
thing-type.config.ipcamera.onvif.onvifEventServiceType.option.2 = Force PullMessages (2)
thing-type.config.ipcamera.onvif.onvifEventServiceType.option.3 = Force WSBaseSubscription (3)
thing-type.config.ipcamera.onvif.password.label = Password
thing-type.config.ipcamera.onvif.password.description = Enter the password for your camera. Leave blank if your camera does not use one.
thing-type.config.ipcamera.onvif.pollTime.label = Poll Time
@ -590,6 +596,12 @@ thing-type.config.ipcamera.reolink.onvifMediaProfile.label = ONVIF Media Profile
thing-type.config.ipcamera.reolink.onvifMediaProfile.description = Cameras can supply more than one stream at different resolutions and formats. 0 selects the main-stream and 1 or above are the sub-streams. Sometimes you need to turn on sub-streams in the cameras setup before they can be used.
thing-type.config.ipcamera.reolink.onvifPort.label = ONVIF Port
thing-type.config.ipcamera.reolink.onvifPort.description = The port your camera uses for ONVIF connections. This is needed for PTZ movement, alarm events and auto discovery of RTSP and snapshot URLs.
thing-type.config.ipcamera.reolink.onvifEventServiceType.label = ONVIF event method
thing-type.config.ipcamera.reolink.onvifEventServiceType.description = ONVIF event method to use. If camera does not report event capabilities, the event method can be forced here.
thing-type.config.ipcamera.reolink.onvifEventServiceType.option.0 = Auto detect (0)
thing-type.config.ipcamera.reolink.onvifEventServiceType.option.1 = Disabled (1)
thing-type.config.ipcamera.reolink.onvifEventServiceType.option.2 = Force PullMessages (2)
thing-type.config.ipcamera.reolink.onvifEventServiceType.option.3 = Force WSBaseSubscription (3)
thing-type.config.ipcamera.reolink.password.label = Password
thing-type.config.ipcamera.reolink.password.description = Enter the password for your camera. Leave blank if your camera does not use one.
thing-type.config.ipcamera.reolink.pollTime.label = Poll Time

View File

@ -548,6 +548,21 @@
<default>0</default>
</parameter>
<parameter name="onvifEventServiceType" type="integer" groupName="Settings" multiple="false">
<label>ONVIF event method:</label>
<description>ONVIF event method to use. If camera does not report event capabilities, the event method can be forced
here.
</description>
<default>0</default>
<advanced>true</advanced>
<options>
<option value="0">Auto detect (0)</option>
<option value="1">Disabled (1)</option>
<option value="2">Force PullMessages (2)</option>
<option value="3">Force WSBaseSubscription (3)</option>
</options>
</parameter>
<parameter name="ipWhitelist" type="text" required="false" groupName="Settings">
<label>IP Whitelist</label>
<description>Enter any IP's inside (brackets) that you wish to allow to access the video stream. 'DISABLE' will
@ -2572,6 +2587,21 @@
<default>0</default>
</parameter>
<parameter name="onvifEventServiceType" type="integer" groupName="Settings" multiple="false">
<label>ONVIF event method:</label>
<description>ONVIF event method to use. If camera does not report event capabilities, the event method can be forced
here.
</description>
<default>0</default>
<advanced>true</advanced>
<options>
<option value="0">Auto detect (0)</option>
<option value="1">Disabled (1)</option>
<option value="2">Force PullMessages (2)</option>
<option value="3">Force WSBaseSubscription (3)</option>
</options>
</parameter>
<parameter name="pollTime" type="integer" required="true" min="1000" groupName="Settings" unit="ms">
<label>Poll Time</label>
<description>Most features are made on demand and not polled, but some features require a regular snapshot to work.