[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>
This commit is contained in:
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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
|
|
@ -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.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
@ -63,6 +64,14 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
|||
return;
|
||||
}
|
||||
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) {
|
||||
case "VideoMotion":
|
||||
if ("Start".equals(action)) {
|
||||
|
@ -106,6 +115,7 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
|||
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
|
||||
}
|
||||
break;
|
||||
case "AudioAnomaly":
|
||||
case "AudioMutation":
|
||||
if ("Start".equals(action)) {
|
||||
ipCameraHandler.audioDetected();
|
||||
|
|
|
@ -171,7 +171,7 @@ public class Ffmpeg {
|
|||
}
|
||||
}
|
||||
} 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 {
|
||||
switch (format) {
|
||||
case GIF:
|
||||
|
|
|
@ -96,6 +96,12 @@ public class Helper {
|
|||
if (sectionHeaderBeginning > 0) {
|
||||
result = result.substring(0, sectionHeaderBeginning);
|
||||
}
|
||||
if (!key.endsWith(">")) {
|
||||
startIndex = result.indexOf(">");
|
||||
if (startIndex != -1) {
|
||||
return result.substring(startIndex + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
|||
if (content.contains("hannelID>" + nvrChannel) || content.contains("<channelID>0</channelID>")) {
|
||||
final int debounce = 3;
|
||||
String eventType = Helper.fetchXML(content, "", "<eventType>");
|
||||
ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content));
|
||||
switch (eventType) {
|
||||
case "videoloss":
|
||||
if (content.contains("<eventState>inactive</eventState>")) {
|
||||
|
@ -120,7 +121,11 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
|||
String content = msg.toString();
|
||||
logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||
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 {
|
||||
String replyElement = Helper.fetchXML(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<");
|
||||
switch (replyElement) {
|
||||
|
|
|
@ -132,6 +132,7 @@ public class IpCameraBindingConstants {
|
|||
public static final String CHANNEL_EXTERNAL_LIGHT = "externalLight";
|
||||
public static final String CHANNEL_DOORBELL = "doorBell";
|
||||
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_START_STREAM = "startStream";
|
||||
public static final String CHANNEL_ENABLE_PRIVACY_MODE = "enablePrivacyMode";
|
||||
|
|
|
@ -1366,7 +1366,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||
snapshotIsFfmpeg();
|
||||
} else {
|
||||
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 password = "";
|
||||
private int onvifPort = 80;
|
||||
private String deviceXAddr = "/onvif/device_service";
|
||||
private String eventXAddr = "/onvif/device_service";
|
||||
private String mediaXAddr = "/onvif/device_service";
|
||||
private String deviceXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||
private String eventXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||
private String mediaXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||
@SuppressWarnings("unused")
|
||||
private String imagingXAddr = "/onvif/device_service";
|
||||
private String ptzXAddr = "/onvif/ptz_service";
|
||||
private String subscriptionXAddr = "/onvif/device_service";
|
||||
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
|
||||
private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
|
||||
private boolean isConnected = false;
|
||||
private int mediaProfileIndex = 0;
|
||||
private String snapshotUri = "";
|
||||
|
@ -334,7 +334,7 @@ public class OnvifConnection {
|
|||
} else if (message.contains("GetEventPropertiesResponse")) {
|
||||
sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
|
||||
} else if (message.contains("CreatePullPointSubscriptionResponse")) {
|
||||
subscriptionXAddr = removeIPfromUrl(Helper.fetchXML(message, "SubscriptionReference>", "Address>"));
|
||||
subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
|
||||
logger.debug("subscriptionXAddr={}", subscriptionXAddr);
|
||||
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
|
||||
} else if (message.contains("GetStatusResponse")) {
|
||||
|
@ -376,7 +376,7 @@ public class OnvifConnection {
|
|||
String getXmlCache = getXml(requestType);
|
||||
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
||||
|| 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\"";
|
||||
}
|
||||
String headers;
|
||||
|
@ -396,16 +396,13 @@ public class OnvifConnection {
|
|||
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
||||
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=\"");
|
||||
request.headers().add("Content-Type",
|
||||
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
||||
request.headers().add("Charset", "utf-8");
|
||||
if (onvifPort != 80) {
|
||||
request.headers().set("Host", ipAddress + ":" + onvifPort);
|
||||
} else {
|
||||
request.headers().set("Host", ipAddress);
|
||||
}
|
||||
request.headers().set("Host", extractIPportFromUrl(xAddr));
|
||||
request.headers().set("Connection", HttpHeaderValues.CLOSE);
|
||||
request.headers().set("Accept-Encoding", "gzip, deflate");
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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()) {
|
||||
deviceXAddr = temp;
|
||||
logger.debug("deviceXAddr:{}", deviceXAddr);
|
||||
}
|
||||
temp = removeIPfromUrl(Helper.fetchXML(message, "<tt:Events", "tt:XAddr"));
|
||||
temp = Helper.fetchXML(message, "<tt:Events", "tt:XAddr");
|
||||
if (!temp.isEmpty()) {
|
||||
subscriptionXAddr = eventXAddr = temp;
|
||||
logger.debug("eventsXAddr:{}", eventXAddr);
|
||||
}
|
||||
temp = removeIPfromUrl(Helper.fetchXML(message, "<tt:Media", "tt:XAddr"));
|
||||
temp = Helper.fetchXML(message, "<tt:Media", "tt:XAddr");
|
||||
if (!temp.isEmpty()) {
|
||||
mediaXAddr = temp;
|
||||
logger.debug("mediaXAddr:{}", mediaXAddr);
|
||||
}
|
||||
|
||||
ptzXAddr = removeIPfromUrl(Helper.fetchXML(message, "<tt:PTZ", "tt:XAddr"));
|
||||
ptzXAddr = Helper.fetchXML(message, "<tt:PTZ", "tt:XAddr");
|
||||
if (ptzXAddr.isEmpty()) {
|
||||
ptzDevice = false;
|
||||
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="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
||||
<channel id="lastMotionType" typeId="lastMotionType"/>
|
||||
<channel id="lastEventData" typeId="lastEventData"/>
|
||||
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
||||
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
||||
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
||||
|
@ -1710,6 +1711,7 @@
|
|||
<channel id="mp4History" typeId="mp4History"/>
|
||||
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
|
||||
<channel id="lastMotionType" typeId="lastMotionType"/>
|
||||
<channel id="lastEventData" typeId="lastEventData"/>
|
||||
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
|
||||
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
|
||||
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
|
||||
|
@ -2367,6 +2369,13 @@
|
|||
<state readOnly="true"/>
|
||||
</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">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Motion Alarm</label>
|
||||
|
|
Loading…
Reference in New Issue