[ipcamera] Add new channel lastEventData for detailed extra data on alarms (#11748)
* Add new channel * Last Event Data channel finished. * Remove info logging. * Fix bugs * Fix ONVIF wont use different ports in xaddr paths. Signed-off-by: Matthew Skinner <matt@pcmus.com>pull/11762/head
parent
47dec045e3
commit
6077ce3c44
|
@ -238,6 +238,7 @@ The channels are kept consistent as much as possible from brand to brand to make
|
||||||
| `itemLeft` | Switch (read only) | Will turn ON if an API camera detects an item has been left behind. |
|
| `itemLeft` | Switch (read only) | Will turn ON if an API camera detects an item has been left behind. |
|
||||||
| `itemTaken` | Switch (read only) | Will turn ON if an API camera detects an item has been stolen. |
|
| `itemTaken` | Switch (read only) | Will turn ON if an API camera detects an item has been stolen. |
|
||||||
| `lastMotionType` | String | Cameras with multiple alarm types will update this with which alarm last detected motion, i.e. a lineCrossing, faceDetection or item stolen alarm. You can also use this to create a timestamp of when the last motion was detected by creating a rule when this channel changes. |
|
| `lastMotionType` | String | Cameras with multiple alarm types will update this with which alarm last detected motion, i.e. a lineCrossing, faceDetection or item stolen alarm. You can also use this to create a timestamp of when the last motion was detected by creating a rule when this channel changes. |
|
||||||
|
| `lastEventData` | String | Detailed information about the last smart alarm that can contain information like which Line number was crossed and in which direction. The channel `lastMotionType` will hold the name of the alarm that this data belongs to. |
|
||||||
| `lineCrossingAlarm` | Switch (read only) | Will turn on if the API camera detects motion has crossed a line. |
|
| `lineCrossingAlarm` | Switch (read only) | Will turn on if the API camera detects motion has crossed a line. |
|
||||||
| `mjpegUrl` | String | The URL for the ipcamera.mjpeg stream. |
|
| `mjpegUrl` | String | The URL for the ipcamera.mjpeg stream. |
|
||||||
| `motionAlarm` | Switch (read only) | The status of the 'video motion' events in ONVIF and API cameras. Also see `cellMotionAlarm` as these can give different results. |
|
| `motionAlarm` | Switch (read only) | The status of the 'video motion' events in ONVIF and API cameras. Also see `cellMotionAlarm` as these can give different results. |
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
import org.openhab.core.library.types.OnOffType;
|
||||||
import org.openhab.core.library.types.PercentType;
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
|
@ -63,6 +64,14 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String action = content.substring(startIndex, endIndex);
|
String action = content.substring(startIndex, endIndex);
|
||||||
|
startIndex = content.indexOf(";data=", startIndex);
|
||||||
|
if (startIndex > 0) {
|
||||||
|
endIndex = content.lastIndexOf("}");
|
||||||
|
if (endIndex > 0) {
|
||||||
|
String data = content.substring(startIndex + 6, endIndex + 1);
|
||||||
|
ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case "VideoMotion":
|
case "VideoMotion":
|
||||||
if ("Start".equals(action)) {
|
if ("Start".equals(action)) {
|
||||||
|
@ -106,6 +115,7 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
||||||
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
|
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "AudioAnomaly":
|
||||||
case "AudioMutation":
|
case "AudioMutation":
|
||||||
if ("Start".equals(action)) {
|
if ("Start".equals(action)) {
|
||||||
ipCameraHandler.audioDetected();
|
ipCameraHandler.audioDetected();
|
||||||
|
|
|
@ -171,7 +171,7 @@ public class Ffmpeg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("An error occured trying to process the messages from FFmpeg.");
|
logger.warn("An IO error occured trying to start FFmpeg:{}", e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case GIF:
|
case GIF:
|
||||||
|
|
|
@ -96,6 +96,12 @@ public class Helper {
|
||||||
if (sectionHeaderBeginning > 0) {
|
if (sectionHeaderBeginning > 0) {
|
||||||
result = result.substring(0, sectionHeaderBeginning);
|
result = result.substring(0, sectionHeaderBeginning);
|
||||||
}
|
}
|
||||||
|
if (!key.endsWith(">")) {
|
||||||
|
startIndex = result.indexOf(">");
|
||||||
|
if (startIndex != -1) {
|
||||||
|
return result.substring(startIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
||||||
if (content.contains("hannelID>" + nvrChannel) || content.contains("<channelID>0</channelID>")) {
|
if (content.contains("hannelID>" + nvrChannel) || content.contains("<channelID>0</channelID>")) {
|
||||||
final int debounce = 3;
|
final int debounce = 3;
|
||||||
String eventType = Helper.fetchXML(content, "", "<eventType>");
|
String eventType = Helper.fetchXML(content, "", "<eventType>");
|
||||||
|
ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content));
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case "videoloss":
|
case "videoloss":
|
||||||
if (content.contains("<eventState>inactive</eventState>")) {
|
if (content.contains("<eventState>inactive</eventState>")) {
|
||||||
|
@ -120,7 +121,11 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
||||||
String content = msg.toString();
|
String content = msg.toString();
|
||||||
logger.trace("HTTP Result back from camera is \t:{}:", content);
|
logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
if (content.startsWith("--boundary")) {// Alarm checking goes in here//
|
if (content.startsWith("--boundary")) {// Alarm checking goes in here//
|
||||||
processEvent(content);
|
int startIndex = content.indexOf("<");// skip to start of XML content
|
||||||
|
if (startIndex != -1) {
|
||||||
|
String eventData = content.substring(startIndex, content.length());
|
||||||
|
processEvent(eventData);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String replyElement = Helper.fetchXML(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<");
|
String replyElement = Helper.fetchXML(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<");
|
||||||
switch (replyElement) {
|
switch (replyElement) {
|
||||||
|
|
|
@ -132,6 +132,7 @@ public class IpCameraBindingConstants {
|
||||||
public static final String CHANNEL_EXTERNAL_LIGHT = "externalLight";
|
public static final String CHANNEL_EXTERNAL_LIGHT = "externalLight";
|
||||||
public static final String CHANNEL_DOORBELL = "doorBell";
|
public static final String CHANNEL_DOORBELL = "doorBell";
|
||||||
public static final String CHANNEL_LAST_MOTION_TYPE = "lastMotionType";
|
public static final String CHANNEL_LAST_MOTION_TYPE = "lastMotionType";
|
||||||
|
public static final String CHANNEL_LAST_EVENT_DATA = "lastEventData";
|
||||||
public static final String CHANNEL_GOTO_PRESET = "gotoPreset";
|
public static final String CHANNEL_GOTO_PRESET = "gotoPreset";
|
||||||
public static final String CHANNEL_START_STREAM = "startStream";
|
public static final String CHANNEL_START_STREAM = "startStream";
|
||||||
public static final String CHANNEL_ENABLE_PRIVACY_MODE = "enablePrivacyMode";
|
public static final String CHANNEL_ENABLE_PRIVACY_MODE = "enablePrivacyMode";
|
||||||
|
|
|
@ -1366,7 +1366,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||||
snapshotIsFfmpeg();
|
snapshotIsFfmpeg();
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"Camera failed to report a valid Snaphot and/or RTSP URL. See readme on how to use the SNAPSHOT_URL_OVERRIDE feature.");
|
"Camera failed to report a valid Snaphot and/or RTSP URL. Check user/pass is correct, or use the advanced configs to manually provide a URL.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,13 +119,13 @@ public class OnvifConnection {
|
||||||
private String user = "";
|
private String user = "";
|
||||||
private String password = "";
|
private String password = "";
|
||||||
private int onvifPort = 80;
|
private int onvifPort = 80;
|
||||||
private String deviceXAddr = "/onvif/device_service";
|
private String deviceXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||||
private String eventXAddr = "/onvif/device_service";
|
private String eventXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||||
private String mediaXAddr = "/onvif/device_service";
|
private String mediaXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private String imagingXAddr = "/onvif/device_service";
|
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||||
private String ptzXAddr = "/onvif/ptz_service";
|
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
|
||||||
private String subscriptionXAddr = "/onvif/device_service";
|
private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||||
private boolean isConnected = false;
|
private boolean isConnected = false;
|
||||||
private int mediaProfileIndex = 0;
|
private int mediaProfileIndex = 0;
|
||||||
private String snapshotUri = "";
|
private String snapshotUri = "";
|
||||||
|
@ -334,7 +334,7 @@ public class OnvifConnection {
|
||||||
} else if (message.contains("GetEventPropertiesResponse")) {
|
} else if (message.contains("GetEventPropertiesResponse")) {
|
||||||
sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
|
sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
|
||||||
} else if (message.contains("CreatePullPointSubscriptionResponse")) {
|
} else if (message.contains("CreatePullPointSubscriptionResponse")) {
|
||||||
subscriptionXAddr = removeIPfromUrl(Helper.fetchXML(message, "SubscriptionReference>", "Address>"));
|
subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
|
||||||
logger.debug("subscriptionXAddr={}", subscriptionXAddr);
|
logger.debug("subscriptionXAddr={}", subscriptionXAddr);
|
||||||
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
|
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
|
||||||
} else if (message.contains("GetStatusResponse")) {
|
} else if (message.contains("GetStatusResponse")) {
|
||||||
|
@ -376,7 +376,7 @@ public class OnvifConnection {
|
||||||
String getXmlCache = getXml(requestType);
|
String getXmlCache = getXml(requestType);
|
||||||
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
||||||
|| requestType.equals(RequestType.Renew) || requestType.equals(RequestType.Unsubscribe)) {
|
|| requestType.equals(RequestType.Renew) || requestType.equals(RequestType.Unsubscribe)) {
|
||||||
headerTo = "<a:To s:mustUnderstand=\"1\">http://" + ipAddress + xAddr + "</a:To>";
|
headerTo = "<a:To s:mustUnderstand=\"1\">" + xAddr + "</a:To>";
|
||||||
extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
||||||
}
|
}
|
||||||
String headers;
|
String headers;
|
||||||
|
@ -396,16 +396,13 @@ public class OnvifConnection {
|
||||||
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
||||||
headers = "";
|
headers = "";
|
||||||
}
|
}
|
||||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"), xAddr);
|
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"),
|
||||||
|
removeIPfromUrl(xAddr));
|
||||||
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
||||||
request.headers().add("Content-Type",
|
request.headers().add("Content-Type",
|
||||||
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
||||||
request.headers().add("Charset", "utf-8");
|
request.headers().add("Charset", "utf-8");
|
||||||
if (onvifPort != 80) {
|
request.headers().set("Host", extractIPportFromUrl(xAddr));
|
||||||
request.headers().set("Host", ipAddress + ":" + onvifPort);
|
|
||||||
} else {
|
|
||||||
request.headers().set("Host", ipAddress);
|
|
||||||
}
|
|
||||||
request.headers().set("Connection", HttpHeaderValues.CLOSE);
|
request.headers().set("Connection", HttpHeaderValues.CLOSE);
|
||||||
request.headers().set("Accept-Encoding", "gzip, deflate");
|
request.headers().set("Accept-Encoding", "gzip, deflate");
|
||||||
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
|
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
|
||||||
|
@ -437,25 +434,35 @@ public class OnvifConnection {
|
||||||
return url.substring(index);
|
return url.substring(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String extractIPportFromUrl(String url) {
|
||||||
|
int startIndex = url.indexOf("//") + 2;
|
||||||
|
int endIndex = url.indexOf("/", startIndex);// skip past any :port to the slash /
|
||||||
|
if (startIndex != -1 && endIndex != -1) {
|
||||||
|
return url.substring(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
logger.debug("We hit an issue extracting IP:PORT from url:{}", url);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
void parseXAddr(String message) {
|
void parseXAddr(String message) {
|
||||||
// Normally I would search '<tt:XAddr>' instead but Foscam needed this work around.
|
// Normally I would search '<tt:XAddr>' instead but Foscam needed this work around.
|
||||||
String temp = removeIPfromUrl(Helper.fetchXML(message, "<tt:Device", "tt:XAddr"));
|
String temp = Helper.fetchXML(message, "<tt:Device", "tt:XAddr");
|
||||||
if (!temp.isEmpty()) {
|
if (!temp.isEmpty()) {
|
||||||
deviceXAddr = temp;
|
deviceXAddr = temp;
|
||||||
logger.debug("deviceXAddr:{}", deviceXAddr);
|
logger.debug("deviceXAddr:{}", deviceXAddr);
|
||||||
}
|
}
|
||||||
temp = removeIPfromUrl(Helper.fetchXML(message, "<tt:Events", "tt:XAddr"));
|
temp = Helper.fetchXML(message, "<tt:Events", "tt:XAddr");
|
||||||
if (!temp.isEmpty()) {
|
if (!temp.isEmpty()) {
|
||||||
subscriptionXAddr = eventXAddr = temp;
|
subscriptionXAddr = eventXAddr = temp;
|
||||||
logger.debug("eventsXAddr:{}", eventXAddr);
|
logger.debug("eventsXAddr:{}", eventXAddr);
|
||||||
}
|
}
|
||||||
temp = removeIPfromUrl(Helper.fetchXML(message, "<tt:Media", "tt:XAddr"));
|
temp = Helper.fetchXML(message, "<tt:Media", "tt:XAddr");
|
||||||
if (!temp.isEmpty()) {
|
if (!temp.isEmpty()) {
|
||||||
mediaXAddr = temp;
|
mediaXAddr = temp;
|
||||||
logger.debug("mediaXAddr:{}", mediaXAddr);
|
logger.debug("mediaXAddr:{}", mediaXAddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptzXAddr = removeIPfromUrl(Helper.fetchXML(message, "<tt:PTZ", "tt:XAddr"));
|
ptzXAddr = Helper.fetchXML(message, "<tt:PTZ", "tt:XAddr");
|
||||||
if (ptzXAddr.isEmpty()) {
|
if (ptzXAddr.isEmpty()) {
|
||||||
ptzDevice = false;
|
ptzDevice = false;
|
||||||
logger.trace("Camera must not support PTZ, it failed to give a <tt:PTZ><tt:XAddr>:{}", message);
|
logger.trace("Camera must not support PTZ, it failed to give a <tt:PTZ><tt:XAddr>:{}", message);
|
||||||
|
|
|
@ -880,6 +880,7 @@
|
||||||
<channel id="mp4History" typeId="mp4History"/>
|
<channel id="mp4History" typeId="mp4History"/>
|
||||||
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
||||||
<channel id="lastMotionType" typeId="lastMotionType"/>
|
<channel id="lastMotionType" typeId="lastMotionType"/>
|
||||||
|
<channel id="lastEventData" typeId="lastEventData"/>
|
||||||
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
||||||
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
||||||
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
||||||
|
@ -1710,6 +1711,7 @@
|
||||||
<channel id="mp4History" typeId="mp4History"/>
|
<channel id="mp4History" typeId="mp4History"/>
|
||||||
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
||||||
<channel id="lastMotionType" typeId="lastMotionType"/>
|
<channel id="lastMotionType" typeId="lastMotionType"/>
|
||||||
|
<channel id="lastEventData" typeId="lastEventData"/>
|
||||||
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
||||||
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
||||||
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
||||||
|
@ -2367,6 +2369,13 @@
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="lastEventData" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Last Event Data</label>
|
||||||
|
<description>A string that contains detailed data on the last alarm that was triggered.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="motionAlarm">
|
<channel-type id="motionAlarm">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Motion Alarm</label>
|
<label>Motion Alarm</label>
|
||||||
|
|
Loading…
Reference in New Issue