]> git.basschouten.com Git - openhab-addons.git/blob
8521f97e49c5c45834f3109d4cbf96e40b7c6f67
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13
14 package org.openhab.binding.ipcamera.internal;
15
16 import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.*;
17
18 import java.nio.charset.StandardCharsets;
19 import java.util.ArrayList;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
24 import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.OnOffType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.binding.ThingHandler;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.RefreshType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import io.netty.buffer.ByteBuf;
36 import io.netty.buffer.Unpooled;
37 import io.netty.channel.ChannelDuplexHandler;
38 import io.netty.channel.ChannelHandlerContext;
39 import io.netty.handler.codec.http.DefaultFullHttpRequest;
40 import io.netty.handler.codec.http.FullHttpRequest;
41 import io.netty.handler.codec.http.HttpHeaderNames;
42 import io.netty.handler.codec.http.HttpHeaderValues;
43 import io.netty.handler.codec.http.HttpMethod;
44 import io.netty.handler.codec.http.HttpVersion;
45 import io.netty.util.ReferenceCountUtil;
46
47 /**
48  * The {@link HikvisionHandler} is responsible for handling commands, which are
49  * sent to one of the channels.
50  *
51  * @author Matthew Skinner - Initial contribution
52  */
53
54 @NonNullByDefault
55 public class HikvisionHandler extends ChannelDuplexHandler {
56     private final Logger logger = LoggerFactory.getLogger(getClass());
57     private IpCameraHandler ipCameraHandler;
58     private int nvrChannel;
59     private int lineCount, vmdCount, leftCount, takenCount, faceCount, pirCount, fieldCount;
60
61     public HikvisionHandler(ThingHandler handler, int nvrChannel) {
62         ipCameraHandler = (IpCameraHandler) handler;
63         this.nvrChannel = nvrChannel;
64     }
65
66     // This handles the incoming http replies back from the camera.
67     @Override
68     public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object msg) throws Exception {
69         if (msg == null || ctx == null) {
70             return;
71         }
72         String content = "";
73         int debounce = 3;
74         try {
75             content = msg.toString();
76             if (content.isEmpty()) {
77                 return;
78             }
79             logger.trace("HTTP Result back from camera is \t:{}:", content);
80
81             if (content.contains("--boundary")) {// Alarm checking goes in here//
82                 if (content.contains("<EventNotificationAlert version=\"")) {
83                     if (content.contains("hannelID>" + nvrChannel + "</")) {// some camera use c or <dynChannelID>
84                         if (content.contains("<eventType>linedetection</eventType>")) {
85                             ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
86                             lineCount = debounce;
87                         }
88                         if (content.contains("<eventType>fielddetection</eventType>")) {
89                             ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
90                             fieldCount = debounce;
91                         }
92                         if (content.contains("<eventType>VMD</eventType>")) {
93                             ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
94                             vmdCount = debounce;
95                         }
96                         if (content.contains("<eventType>facedetection</eventType>")) {
97                             ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
98                             faceCount = debounce;
99                         }
100                         if (content.contains("<eventType>unattendedBaggage</eventType>")) {
101                             ipCameraHandler.setChannelState(CHANNEL_ITEM_LEFT, OnOffType.ON);
102                             leftCount = debounce;
103                         }
104                         if (content.contains("<eventType>attendedBaggage</eventType>")) {
105                             ipCameraHandler.setChannelState(CHANNEL_ITEM_TAKEN, OnOffType.ON);
106                             takenCount = debounce;
107                         }
108                         if (content.contains("<eventType>PIR</eventType>")) {
109                             ipCameraHandler.motionDetected(CHANNEL_PIR_ALARM);
110                             pirCount = debounce;
111                         }
112                         if (content.contains("<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
113                             if (vmdCount > 1) {
114                                 vmdCount = 1;
115                             }
116                             countDown();
117                             countDown();
118                         }
119                     } else if (content.contains("<channelID>0</channelID>")) {// NVR uses channel 0 to say all channels
120                         if (content.contains("<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
121                             if (vmdCount > 1) {
122                                 vmdCount = 1;
123                             }
124                             countDown();
125                             countDown();
126                         }
127                     }
128                     countDown();
129                 }
130             } else {
131                 String replyElement = Helper.fetchXML(content, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<");
132                 switch (replyElement) {
133                     case "MotionDetection version=":
134                         ipCameraHandler.storeHttpReply(
135                                 "/ISAPI/System/Video/inputs/channels/" + nvrChannel + "01/motionDetection", content);
136                         if (content.contains("<enabled>true</enabled>")) {
137                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
138                         } else if (content.contains("<enabled>false</enabled>")) {
139                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
140                         }
141                         break;
142                     case "IOInputPort version=":
143                         ipCameraHandler.storeHttpReply("/ISAPI/System/IO/inputs/" + nvrChannel, content);
144                         if (content.contains("<enabled>true</enabled>")) {
145                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_EXTERNAL_ALARM_INPUT, OnOffType.ON);
146                         } else if (content.contains("<enabled>false</enabled>")) {
147                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
148                         }
149                         if (content.contains("<triggering>low</triggering>")) {
150                             ipCameraHandler.setChannelState(CHANNEL_TRIGGER_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
151                         } else if (content.contains("<triggering>high</triggering>")) {
152                             ipCameraHandler.setChannelState(CHANNEL_TRIGGER_EXTERNAL_ALARM_INPUT, OnOffType.ON);
153                         }
154                         break;
155                     case "LineDetection":
156                         ipCameraHandler.storeHttpReply("/ISAPI/Smart/LineDetection/" + nvrChannel + "01", content);
157                         if (content.contains("<enabled>true</enabled>")) {
158                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.ON);
159                         } else if (content.contains("<enabled>false</enabled>")) {
160                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.OFF);
161                         }
162                         break;
163                     case "TextOverlay version=":
164                         ipCameraHandler.storeHttpReply(
165                                 "/ISAPI/System/Video/inputs/channels/" + nvrChannel + "/overlays/text/1", content);
166                         String text = Helper.fetchXML(content, "<enabled>true</enabled>", "<displayText>");
167                         ipCameraHandler.setChannelState(CHANNEL_TEXT_OVERLAY, StringType.valueOf(text));
168                         break;
169                     case "AudioDetection version=":
170                         ipCameraHandler.storeHttpReply("/ISAPI/Smart/AudioDetection/channels/" + nvrChannel + "01",
171                                 content);
172                         if (content.contains("<enabled>true</enabled>")) {
173                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
174                         } else if (content.contains("<enabled>false</enabled>")) {
175                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
176                         }
177                         break;
178                     case "IOPortStatus version=":
179                         if (content.contains("<ioState>active</ioState>")) {
180                             ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.ON);
181                         } else if (content.contains("<ioState>inactive</ioState>")) {
182                             ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
183                         }
184                         break;
185                     case "FieldDetection version=":
186                         ipCameraHandler.storeHttpReply("/ISAPI/Smart/FieldDetection/" + nvrChannel + "01", content);
187                         if (content.contains("<enabled>true</enabled>")) {
188                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_FIELD_DETECTION_ALARM, OnOffType.ON);
189                         } else if (content.contains("<enabled>false</enabled>")) {
190                             ipCameraHandler.setChannelState(CHANNEL_ENABLE_FIELD_DETECTION_ALARM, OnOffType.OFF);
191                         }
192                         break;
193                     case "ResponseStatus version=":
194                         ////////////////// External Alarm Input ///////////////
195                         if (content.contains(
196                                 "<requestURL>/ISAPI/System/IO/inputs/" + nvrChannel + "/status</requestURL>")) {
197                             // Stops checking the external alarm if camera does not have feature.
198                             if (content.contains("<statusString>Invalid Operation</statusString>")) {
199                                 ipCameraHandler.lowPriorityRequests.remove(0);
200                                 ipCameraHandler.logger.debug(
201                                         "Stopping checks for alarm inputs as camera appears to be missing this feature.");
202                             }
203                         }
204                         break;
205                     default:
206                         if (content.contains("<EventNotificationAlert")) {
207                             if (content.contains("hannelID>" + nvrChannel + "</")
208                                     || content.contains("<channelID>0</channelID>")) {// some camera use c or
209                                                                                       // <dynChannelID>
210                                 if (content.contains(
211                                         "<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
212                                     if (vmdCount > 1) {
213                                         vmdCount = 1;
214                                     }
215                                     countDown();
216                                     countDown();
217                                 }
218                                 countDown();
219                             }
220                         } else {
221                             logger.debug("Unhandled reply-{}.", content);
222                         }
223                         break;
224                 }
225             }
226         } finally {
227             ReferenceCountUtil.release(msg);
228         }
229     }
230
231     // This does debouncing of the alarms
232     void countDown() {
233         if (lineCount > 1) {
234             lineCount--;
235         } else if (lineCount == 1) {
236             ipCameraHandler.setChannelState(CHANNEL_LINE_CROSSING_ALARM, OnOffType.OFF);
237             lineCount--;
238         }
239         if (vmdCount > 1) {
240             vmdCount--;
241         } else if (vmdCount == 1) {
242             ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
243             vmdCount--;
244         }
245         if (leftCount > 1) {
246             leftCount--;
247         } else if (leftCount == 1) {
248             ipCameraHandler.setChannelState(CHANNEL_ITEM_LEFT, OnOffType.OFF);
249             leftCount--;
250         }
251         if (takenCount > 1) {
252             takenCount--;
253         } else if (takenCount == 1) {
254             ipCameraHandler.setChannelState(CHANNEL_ITEM_TAKEN, OnOffType.OFF);
255             takenCount--;
256         }
257         if (faceCount > 1) {
258             faceCount--;
259         } else if (faceCount == 1) {
260             ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
261             faceCount--;
262         }
263         if (pirCount > 1) {
264             pirCount--;
265         } else if (pirCount == 1) {
266             ipCameraHandler.setChannelState(CHANNEL_PIR_ALARM, OnOffType.OFF);
267             pirCount--;
268         }
269         if (fieldCount > 1) {
270             fieldCount--;
271         } else if (fieldCount == 1) {
272             ipCameraHandler.setChannelState(CHANNEL_FIELD_DETECTION_ALARM, OnOffType.OFF);
273             fieldCount--;
274         }
275         if (fieldCount == 0 && pirCount == 0 && faceCount == 0 && takenCount == 0 && leftCount == 0 && vmdCount == 0
276                 && lineCount == 0) {
277             ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
278         }
279     }
280
281     public void hikSendXml(String httpPutURL, String xml) {
282         logger.trace("Body for PUT:{} is going to be:{}", httpPutURL, xml);
283         FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("PUT"), httpPutURL);
284         request.headers().set(HttpHeaderNames.HOST, ipCameraHandler.cameraConfig.getIp());
285         request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
286         request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/xml; charset=\"UTF-8\"");
287         ByteBuf bbuf = Unpooled.copiedBuffer(xml, StandardCharsets.UTF_8);
288         request.headers().set(HttpHeaderNames.CONTENT_LENGTH, bbuf.readableBytes());
289         request.content().clear().writeBytes(bbuf);
290         ipCameraHandler.sendHttpPUT(httpPutURL, request);
291     }
292
293     public void hikChangeSetting(String httpGetPutURL, String removeElement, String replaceRemovedElementWith) {
294         ChannelTracking localTracker = ipCameraHandler.channelTrackingMap.get(httpGetPutURL);
295         if (localTracker == null) {
296             ipCameraHandler.sendHttpGET(httpGetPutURL);
297             logger.debug(
298                     "Did not have a reply stored before hikChangeSetting was run, try again shortly as a reply has just been requested.");
299             return;
300         }
301         String body = localTracker.getReply();
302         if (body.isEmpty()) {
303             logger.debug(
304                     "Did not have a reply stored before hikChangeSetting was run, try again shortly as a reply has just been requested.");
305             ipCameraHandler.sendHttpGET(httpGetPutURL);
306         } else {
307             logger.trace("An OLD reply from the camera was:{}", body);
308             if (body.contains("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")) {
309                 body = body.substring("<?xml version=\"1.0\" encoding=\"UTF-8\"?>".length());
310             }
311             int elementIndexStart = body.indexOf("<" + removeElement + ">");
312             int elementIndexEnd = body.indexOf("</" + removeElement + ">");
313             body = body.substring(0, elementIndexStart) + replaceRemovedElementWith
314                     + body.substring(elementIndexEnd + removeElement.length() + 3, body.length());
315             logger.trace("Body for this PUT is going to be:{}", body);
316             localTracker.setReply(body);
317             FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("PUT"),
318                     httpGetPutURL);
319             request.headers().set(HttpHeaderNames.HOST, ipCameraHandler.cameraConfig.getIp());
320             request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
321             request.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/xml; charset=\"UTF-8\"");
322             ByteBuf bbuf = Unpooled.copiedBuffer(body, StandardCharsets.UTF_8);
323             request.headers().set(HttpHeaderNames.CONTENT_LENGTH, bbuf.readableBytes());
324             request.content().clear().writeBytes(bbuf);
325             ipCameraHandler.sendHttpPUT(httpGetPutURL, request);
326         }
327     }
328
329     // This handles the commands that come from the Openhab event bus.
330     public void handleCommand(ChannelUID channelUID, Command command) {
331         if (command instanceof RefreshType) {
332             switch (channelUID.getId()) {
333                 case CHANNEL_ENABLE_AUDIO_ALARM:
334                     ipCameraHandler.sendHttpGET("/ISAPI/Smart/AudioDetection/channels/" + nvrChannel + "01");
335                     return;
336                 case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
337                     ipCameraHandler.sendHttpGET("/ISAPI/Smart/LineDetection/" + nvrChannel + "01");
338                     return;
339                 case CHANNEL_ENABLE_FIELD_DETECTION_ALARM:
340                     ipCameraHandler.logger.debug("FieldDetection command");
341                     ipCameraHandler.sendHttpGET("/ISAPI/Smart/FieldDetection/" + nvrChannel + "01");
342                     return;
343                 case CHANNEL_ENABLE_MOTION_ALARM:
344                     ipCameraHandler
345                             .sendHttpGET("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "01/motionDetection");
346                     return;
347                 case CHANNEL_TEXT_OVERLAY:
348                     ipCameraHandler
349                             .sendHttpGET("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "/overlays/text/1");
350                     return;
351                 case CHANNEL_ENABLE_EXTERNAL_ALARM_INPUT:
352                     ipCameraHandler.sendHttpGET("/ISAPI/System/IO/inputs/" + nvrChannel);
353                     return;
354                 case CHANNEL_TRIGGER_EXTERNAL_ALARM_INPUT:
355                     ipCameraHandler.sendHttpGET("/ISAPI/System/IO/inputs/" + nvrChannel);
356                     return;
357             }
358             return; // Return as we have handled the refresh command above and don't need to
359                     // continue further.
360         } // end of "REFRESH"
361         switch (channelUID.getId()) {
362             case CHANNEL_TEXT_OVERLAY:
363                 logger.debug("Changing text overlay to {}", command.toString());
364                 if (command.toString().isEmpty()) {
365                     hikChangeSetting("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "/overlays/text/1",
366                             "enabled", "<enabled>false</enabled>");
367                 } else {
368                     hikChangeSetting("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "/overlays/text/1",
369                             "displayText", "<displayText>" + command.toString() + "</displayText>");
370                     hikChangeSetting("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "/overlays/text/1",
371                             "enabled", "<enabled>true</enabled>");
372                 }
373                 return;
374             case CHANNEL_ENABLE_EXTERNAL_ALARM_INPUT:
375                 logger.debug("Changing enabled state of the external input 1 to {}", command.toString());
376                 if (OnOffType.ON.equals(command)) {
377                     hikChangeSetting("/ISAPI/System/IO/inputs/" + nvrChannel, "enabled", "<enabled>true</enabled>");
378                 } else {
379                     hikChangeSetting("/ISAPI/System/IO/inputs/" + nvrChannel, "enabled", "<enabled>false</enabled>");
380                 }
381                 return;
382             case CHANNEL_TRIGGER_EXTERNAL_ALARM_INPUT:
383                 logger.debug("Changing triggering state of the external input 1 to {}", command.toString());
384                 if (OnOffType.OFF.equals(command)) {
385                     hikChangeSetting("/ISAPI/System/IO/inputs/" + nvrChannel, "triggering",
386                             "<triggering>low</triggering>");
387                 } else {
388                     hikChangeSetting("/ISAPI/System/IO/inputs/" + nvrChannel, "triggering",
389                             "<triggering>high</triggering>");
390                 }
391                 return;
392             case CHANNEL_ENABLE_PIR_ALARM:
393                 if (OnOffType.ON.equals(command)) {
394                     hikChangeSetting("/ISAPI/WLAlarm/PIR", "enabled", "<enabled>true</enabled>");
395                 } else {
396                     hikChangeSetting("/ISAPI/WLAlarm/PIR", "enabled", "<enabled>false</enabled>");
397                 }
398                 return;
399             case CHANNEL_ENABLE_AUDIO_ALARM:
400                 if (OnOffType.ON.equals(command)) {
401                     hikChangeSetting("/ISAPI/Smart/AudioDetection/channels/" + nvrChannel + "01", "enabled",
402                             "<enabled>true</enabled>");
403                 } else {
404                     hikChangeSetting("/ISAPI/Smart/AudioDetection/channels/" + nvrChannel + "01", "enabled",
405                             "<enabled>false</enabled>");
406                 }
407                 return;
408             case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
409                 if (OnOffType.ON.equals(command)) {
410                     hikChangeSetting("/ISAPI/Smart/LineDetection/" + nvrChannel + "01", "enabled",
411                             "<enabled>true</enabled>");
412                 } else {
413                     hikChangeSetting("/ISAPI/Smart/LineDetection/" + nvrChannel + "01", "enabled",
414                             "<enabled>false</enabled>");
415                 }
416                 return;
417             case CHANNEL_ENABLE_MOTION_ALARM:
418                 if (OnOffType.ON.equals(command)) {
419                     hikChangeSetting("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "01/motionDetection",
420                             "enabled", "<enabled>true</enabled>");
421                 } else {
422                     hikChangeSetting("/ISAPI/System/Video/inputs/channels/" + nvrChannel + "01/motionDetection",
423                             "enabled", "<enabled>false</enabled>");
424                 }
425                 return;
426             case CHANNEL_ENABLE_FIELD_DETECTION_ALARM:
427                 if (OnOffType.ON.equals(command)) {
428                     hikChangeSetting("/ISAPI/Smart/FieldDetection/" + nvrChannel + "01", "enabled",
429                             "<enabled>true</enabled>");
430                 } else {
431                     hikChangeSetting("/ISAPI/Smart/FieldDetection/" + nvrChannel + "01", "enabled",
432                             "<enabled>false</enabled>");
433                 }
434                 return;
435             case CHANNEL_ACTIVATE_ALARM_OUTPUT:
436                 if (OnOffType.ON.equals(command)) {
437                     hikSendXml("/ISAPI/System/IO/outputs/" + nvrChannel + "/trigger",
438                             "<IOPortData version=\"1.0\" xmlns=\"http://www.hikvision.com/ver10/XMLSchema\">\r\n    <outputState>high</outputState>\r\n</IOPortData>\r\n");
439                 } else {
440                     hikSendXml("/ISAPI/System/IO/outputs/" + nvrChannel + "/trigger",
441                             "<IOPortData version=\"1.0\" xmlns=\"http://www.hikvision.com/ver10/XMLSchema\">\r\n    <outputState>low</outputState>\r\n</IOPortData>\r\n");
442                 }
443                 return;
444             case CHANNEL_FFMPEG_MOTION_CONTROL:
445                 if (OnOffType.ON.equals(command)) {
446                     ipCameraHandler.motionAlarmEnabled = true;
447                 } else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
448                     ipCameraHandler.motionAlarmEnabled = false;
449                     ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
450                 } else {
451                     ipCameraHandler.motionAlarmEnabled = true;
452                     ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
453                     ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
454                 }
455                 ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
456                 return;
457         }
458     }
459
460     // If a camera does not need to poll a request as often as snapshots, it can be
461     // added here. Binding steps through the list.
462     public ArrayList<String> getLowPriorityRequests() {
463         ArrayList<String> lowPriorityRequests = new ArrayList<String>(1);
464         lowPriorityRequests.add("/ISAPI/System/IO/inputs/" + nvrChannel + "/status"); // must stay in element 0.
465         return lowPriorityRequests;
466     }
467 }