Skip to content

Commit

Permalink
[ipcamera] Add new channel lastEventData for detailed extra data on a…
Browse files Browse the repository at this point in the history
…larms (openhab#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>
Signed-off-by: Michael Schmidt <mi.schmidt.83@gmail.com>
  • Loading branch information
Skinah authored and mischmidt83 committed Jan 9, 2022
1 parent a613618 commit 4a36985
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 21 deletions.
1 change: 1 addition & 0 deletions bundles/org.openhab.binding.ipcamera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand Down Expand Up @@ -63,6 +64,14 @@ private void processEvent(String content) {
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)) {
Expand Down Expand Up @@ -106,6 +115,7 @@ private void processEvent(String content) {
ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
}
break;
case "AudioAnomaly":
case "AudioMutation":
if ("Start".equals(action)) {
ipCameraHandler.audioDetected();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public void run() {
}
}
} 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ public static String fetchXML(String message, String sectionHeading, String key)
if (sectionHeaderBeginning > 0) {
result = result.substring(0, sectionHeaderBeginning);
}
if (!key.endsWith(">")) {
startIndex = result.indexOf(">");
if (startIndex != -1) {
return result.substring(startIndex + 1);
}
}
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private void processEvent(String content) {
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>")) {
Expand Down Expand Up @@ -120,7 +121,11 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public static enum FFmpegFormat {
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,7 @@ void pollingCameraConnection() {
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.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ public static enum RequestType {
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 = "";
Expand Down Expand Up @@ -334,7 +334,7 @@ public void processReply(String message) {
} 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")) {
Expand Down Expand Up @@ -376,7 +376,7 @@ HttpRequest requestBuilder(RequestType requestType, String xAddr) {
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;
Expand All @@ -396,16 +396,13 @@ HttpRequest requestBuilder(RequestType requestType, String xAddr) {
} 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 + ">"
Expand Down Expand Up @@ -437,25 +434,35 @@ String removeIPfromUrl(String url) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down Expand Up @@ -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"/>
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit 4a36985

Please sign in to comment.