]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ipcamera] Improve ONVIF discovery and bug fixes. (#9199)
authorMatthew Skinner <matt@pcmus.com>
Mon, 7 Dec 2020 05:56:25 +0000 (16:56 +1100)
committerGitHub <noreply@github.com>
Mon, 7 Dec 2020 05:56:25 +0000 (21:56 -0800)
* Fix Offline detection and Improve discovery.
* Motion options bug fix.
* Message content bug fix.
* Fix all handlers to process all chunks as one.
* Remove password from FFmpeg command log.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/AmcrestHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DoorBirdHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/FoscamHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/InstarHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDiscoveryService.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifDiscovery.java
bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml

index 8117d1e4490f344970119fedc9c3676d84af9018..b48a34d7494644cc520639d6d180b3a9411bf9ef 100644 (file)
@@ -61,10 +61,7 @@ public class AmcrestHandler extends ChannelDuplexHandler {
         }
         try {
             String content = msg.toString();
-
-            if (!content.isEmpty()) {
-                ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
-            }
+            ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
             if (content.contains("Error: No Events")) {
                 if ("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion".equals(requestUrl)) {
                     ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
index 0a2c54c6811a240f8e2814e4cc9abdb53dda7082..6cdc1aaaa8864fcf8cd99d44c22e2a19dfc0085d 100644 (file)
@@ -55,11 +55,9 @@ public class DahuaHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        String content = msg.toString();
         try {
-            if (!content.isEmpty()) {
-                ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
-            }
+            String content = msg.toString();
+            ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
             // determine if the motion detection is turned on or off.
             if (content.contains("table.MotionDetect[0].Enable=true")) {
                 ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
index 777f001240fb7b525ba4895375626e515ef6b337..499712704bc148381341de0a8dde3567b6a4aa00 100644 (file)
@@ -51,13 +51,9 @@ public class DoorBirdHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        String content = msg.toString();
         try {
-            if (!content.isEmpty()) {
-                ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
-            } else {
-                return;
-            }
+            String content = msg.toString();
+            ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
             if (content.contains("doorbell:H")) {
                 ipCameraHandler.setChannelState(CHANNEL_DOORBELL, OnOffType.ON);
             }
@@ -70,7 +66,6 @@ public class DoorBirdHandler extends ChannelDuplexHandler {
             if (content.contains("motionsensor:H")) {
                 ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
             }
-
         } finally {
             ReferenceCountUtil.release(msg);
         }
index 6e353933a16fb7f101f50bc77c4eb2a29fc959e3..d0741d303cd172d1367ee5638a856c11020a02a4 100644 (file)
@@ -53,10 +53,12 @@ public class Ffmpeg {
     private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread();
     private int keepAlive = 8;
     private boolean running = false;
+    private String password;
 
     public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
             String input, String outArguments, String output, String username, String password) {
         this.format = format;
+        this.password = password;
         ipCameraHandler = handle;
         String altInput = input;
         // Input can be snapshots not just rtsp or http
@@ -169,7 +171,7 @@ public class Ffmpeg {
     public void startConverting() {
         if (!ipCameraFfmpegThread.isAlive()) {
             ipCameraFfmpegThread = new IpCameraFfmpegThread();
-            logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand);
+            logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand.replaceAll(password, "********"));
             ipCameraFfmpegThread.start();
             running = true;
             if (format.equals(FFmpegFormat.HLS)) {
index dd0dc570496a0265d3e93f3a2c845f9822b0dc49..ad969440e446afc08c7a7249f8e0f37296c0f3a7 100644 (file)
@@ -57,14 +57,9 @@ public class FoscamHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        String content = msg.toString();
         try {
-            if (!content.isEmpty()) {
-                ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
-            } else {
-                return;
-            }
-
+            String content = msg.toString();
+            ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
             ////////////// Motion Alarm //////////////
             if (content.contains("<motionDetectAlarm>")) {
                 if (content.contains("<motionDetectAlarm>0</motionDetectAlarm>")) {
@@ -115,7 +110,6 @@ public class FoscamHandler extends ChannelDuplexHandler {
                 ctx.close();
                 ipCameraHandler.logger.debug("End of FOSCAM handler reached, so closing the channel to the camera now");
             }
-
         } finally {
             ReferenceCountUtil.release(msg);
         }
index 74580916f34716416ae9202bb342d7bc6b9a0b2f..bb819c0ac341d31506c4636f7456f3a5ec611e51 100644 (file)
@@ -67,15 +67,10 @@ public class HikvisionHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        String content = "";
-        int debounce = 3;
         try {
-            content = msg.toString();
-            if (content.isEmpty()) {
-                return;
-            }
+            int debounce = 3;
+            String content = msg.toString();
             logger.trace("HTTP Result back from camera is \t:{}:", content);
-
             if (content.contains("--boundary")) {// Alarm checking goes in here//
                 if (content.contains("<EventNotificationAlert version=\"")) {
                     if (content.contains("hannelID>" + nvrChannel + "</")) {// some camera use c or <dynChannelID>
@@ -114,7 +109,8 @@ public class HikvisionHandler extends ChannelDuplexHandler {
                             countDown();
                             countDown();
                         }
-                    } else if (content.contains("<channelID>0</channelID>")) {// NVR uses channel 0 to say all channels
+                    } else if (content.contains("<channelID>0</channelID>")) {// NVR uses channel 0 to say all
+                                                                              // channels
                         if (content.contains("<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
                             if (vmdCount > 1) {
                                 vmdCount = 1;
index 86a1d583b1d90324494f9089a10af73c498aa0e5..a6bec633a5afe175583b83d5ec9f576374c747ab 100644 (file)
@@ -58,13 +58,10 @@ public class InstarHandler extends ChannelDuplexHandler {
         if (msg == null || ctx == null) {
             return;
         }
-        String content = "";
-        String value1 = "";
         try {
-            content = msg.toString();
-            if (content.isEmpty()) {
-                return;
-            }
+            String value1 = "";
+            String content = msg.toString();
+            ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
             switch (requestUrl) {
                 case "/param.cgi?cmd=getinfrared":
                     if (content.contains("var infraredstat=\"auto")) {
index 24cce552f1c35d3d9a8ff27a4f884e6a93a7d68c..46ba9ba62dbc8b537a9fff1ce680903e1738bcfa 100644 (file)
@@ -68,7 +68,7 @@ public class IpCameraDiscoveryService extends AbstractDiscoveryService {
         removeOlderResults(getTimestampOfLastScan());
         OnvifDiscovery onvifDiscovery = new OnvifDiscovery(this);
         try {
-            onvifDiscovery.discoverCameras(3702);// WS discovery
+            onvifDiscovery.discoverCameras();
         } catch (UnknownHostException | InterruptedException e) {
             logger.warn(
                     "IpCamera Discovery has an issue discovering the network settings to find cameras with. Try setting up the camera manually.");
index 4994cfb3e725b04a0ea270eb757e8bc8b2d8a4c4..e58e2fb398be5ed45983a6ca79f4b1e5b0ce0195 100644 (file)
@@ -249,7 +249,7 @@ public class IpCameraHandler extends BaseThingHandler {
                             }
                             if (contentType.contains("multipart")) {
                                 closeConnection = false;
-                                if (mjpegUri.contains(requestUrl)) {
+                                if (mjpegUri.equals(requestUrl)) {
                                     if (msg instanceof HttpMessage) {
                                         // very start of stream only
                                         ReferenceCountUtil.retain(msg, 1);
@@ -268,13 +268,13 @@ public class IpCameraHandler extends BaseThingHandler {
                     }
                 }
                 if (msg instanceof HttpContent) {
-                    if (mjpegUri.contains(requestUrl)) {
+                    if (mjpegUri.equals(requestUrl)) {
                         // multiple MJPEG stream packets come back as this.
                         ReferenceCountUtil.retain(msg, 1);
                         streamToGroup(msg, mjpegChannelGroup, true);
                     } else {
                         HttpContent content = (HttpContent) msg;
-                        // Found some cameras uses Content-Type: image/jpg instead of image/jpeg
+                        // Found some cameras use Content-Type: image/jpg instead of image/jpeg
                         if (contentType.contains("image/jp")) {
                             for (int i = 0; i < content.content().capacity(); i++) {
                                 incomingJpeg[bytesAlreadyRecieved++] = content.content().getByte(i);
@@ -304,8 +304,8 @@ public class IpCameraHandler extends BaseThingHandler {
                                     super.channelRead(ctx, reply);
                                 }
                             }
-                            // HIKVISION alertStream never has a LastHttpContent as it always stays open//
-                            if (contentType.contains("multipart")) {
+                            // Alarm Streams never have a LastHttpContent as they always stay open//
+                            else if (contentType.contains("multipart")) {
                                 if (bytesAlreadyRecieved != 0) {
                                     reply = incomingMessage;
                                     incomingMessage = "";
@@ -316,13 +316,14 @@ public class IpCameraHandler extends BaseThingHandler {
                             }
                             // Foscam needs this as will other cameras with chunks//
                             if (isChunked && bytesAlreadyRecieved != 0) {
+                                logger.debug("Reply is chunked.");
                                 reply = incomingMessage;
                                 super.channelRead(ctx, reply);
                             }
                         }
                     }
                 } else { // msg is not HttpContent
-                    // Foscam and Amcrest cameras need this
+                    // Foscam cameras need this
                     if (!contentType.contains("image/jp") && bytesAlreadyRecieved != 0) {
                         reply = incomingMessage;
                         logger.debug("Packet back from camera is {}", incomingMessage);
@@ -982,7 +983,6 @@ public class IpCameraHandler extends BaseThingHandler {
                     }
                 }
                 String input = (cameraConfig.getAlarmInputUrl().isEmpty()) ? rtspUri : cameraConfig.getAlarmInputUrl();
-                String outputOptions = "-f null -";
                 String filterOptions = "";
                 if (!audioAlarmEnabled) {
                     filterOptions = "-an";
@@ -991,16 +991,22 @@ public class IpCameraHandler extends BaseThingHandler {
                 }
                 if (!motionAlarmEnabled && !ffmpegSnapshotGeneration) {
                     filterOptions = filterOptions.concat(" -vn");
+                } else if (motionAlarmEnabled && !cameraConfig.getMotionOptions().isEmpty()) {
+                    String usersMotionOptions = cameraConfig.getMotionOptions();
+                    if (usersMotionOptions.startsWith("-")) {
+                        // Need to put the users custom options first in the chain before the motion is detected
+                        filterOptions += " " + usersMotionOptions + ",select='gte(scene," + motionThreshold
+                                + ")',metadata=print";
+                    } else {
+                        filterOptions = filterOptions + " " + usersMotionOptions + " -vf select='gte(scene,"
+                                + motionThreshold + ")',metadata=print";
+                    }
                 } else if (motionAlarmEnabled) {
                     filterOptions = filterOptions
                             .concat(" -vf select='gte(scene," + motionThreshold + ")',metadata=print");
                 }
-                if (!cameraConfig.getUser().isEmpty()) {
-                    filterOptions += " ";// add space as the Framework does not allow spaces at start of config.
-                }
                 ffmpegRtspHelper = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, input,
-                        filterOptions + cameraConfig.getMotionOptions(), outputOptions, cameraConfig.getUser(),
-                        cameraConfig.getPassword());
+                        filterOptions, "-f null -", cameraConfig.getUser(), cameraConfig.getPassword());
                 localAlarms = ffmpegRtspHelper;
                 if (localAlarms != null) {
                     localAlarms.startConverting();
@@ -1484,7 +1490,7 @@ public class IpCameraHandler extends BaseThingHandler {
     boolean streamIsStopped(String url) {
         ChannelTracking channelTracking = channelTrackingMap.get(url);
         if (channelTracking != null) {
-            if (channelTracking.getChannel().isOpen()) {
+            if (channelTracking.getChannel().isActive()) {
                 return false; // stream is running.
             }
         }
@@ -1534,20 +1540,21 @@ public class IpCameraHandler extends BaseThingHandler {
         }
     }
 
-    // runs every 8 seconds due to mjpeg streams not staying open unless they update this often.
+    /**
+     * {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep mjpeg and alarm
+     * streams open and more.
+     *
+     */
     void pollCameraRunnable() {
         // Snapshot should be first to keep consistent time between shots
-        if (!snapshotUri.isEmpty()) {
-            if (updateImageChannel) {
-                sendHttpGET(snapshotUri);
-            }
-        }
         if (streamingAutoFps) {
             updateAutoFps = true;
             if (!snapshotPolling && !ffmpegSnapshotGeneration) {
                 // Dont need to poll if creating from RTSP stream with FFmpeg or we are polling at full rate already.
                 sendHttpGET(snapshotUri);
             }
+        } else if (!snapshotUri.isEmpty() && !snapshotPolling) {// we need to check camera is still online.
+            sendHttpGET(snapshotUri);
         }
         // NOTE: Use lowPriorityRequests if get request is not needed every poll.
         if (!lowPriorityRequests.isEmpty()) {
index ca2596030c5b9ae762c7006aacdf4a7aeb382fe5..0e8f5600bd62ffd0c01faf5db9cde1bdef966a6d 100644 (file)
@@ -28,6 +28,7 @@ import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -42,19 +43,21 @@ import io.netty.bootstrap.Bootstrap;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelFactory;
-import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.DatagramChannel;
 import io.netty.channel.socket.DatagramPacket;
 import io.netty.channel.socket.InternetProtocolFamily;
 import io.netty.channel.socket.nio.NioDatagramChannel;
 import io.netty.util.CharsetUtil;
+import io.netty.util.concurrent.GlobalEventExecutor;
 
 /**
- * The {@link OnvifDiscovery} is responsible for finding cameras that are Onvif using UDP multicast.
+ * The {@link OnvifDiscovery} is responsible for finding cameras that are ONVIF using UDP multicast.
  *
  * @author Matthew Skinner - Initial contribution
  */
@@ -69,7 +72,8 @@ public class OnvifDiscovery {
         this.ipCameraDiscoveryService = ipCameraDiscoveryService;
     }
 
-    public @Nullable NetworkInterface getLocalNIF() {
+    public @Nullable List<NetworkInterface> getLocalNICs() {
+        List<NetworkInterface> results = new ArrayList<>(2);
         try {
             for (Enumeration<NetworkInterface> enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
                     .hasMoreElements();) {
@@ -79,13 +83,13 @@ public class OnvifDiscovery {
                     InetAddress inetAddress = enumIpAddr.nextElement();
                     if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().length() < 18
                             && inetAddress.isSiteLocalAddress()) {
-                        return networkInterface;
+                        results.add(networkInterface);
                     }
                 }
             }
         } catch (SocketException ex) {
         }
-        return null;
+        return results;
     }
 
     void searchReply(String url, String xml) {
@@ -180,23 +184,21 @@ public class OnvifDiscovery {
         return brand;
     }
 
-    public void discoverCameras(int port) throws UnknownHostException, InterruptedException {
-        String uuid = UUID.randomUUID().toString();
-        String xml = "";
-
-        if (port == 3702) {
-            xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"><e:Header><w:MessageID>uuid:"
-                    + uuid
-                    + "</w:MessageID><w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To><w:Action a:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action></e:Header><e:Body><d:Probe><d:Types xmlns:dp0=\"http://www.onvif.org/ver10/network/wsdl\">dp0:NetworkVideoTransmitter</d:Types></d:Probe></e:Body></e:Envelope>";
-        }
+    private DatagramPacket wsDiscovery() throws UnknownHostException {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"><e:Header><w:MessageID>uuid:"
+                + UUID.randomUUID()
+                + "</w:MessageID><w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To><w:Action a:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action></e:Header><e:Body><d:Probe><d:Types xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dp0=\"http://www.onvif.org/ver10/network/wsdl\">dp0:NetworkVideoTransmitter</d:Types></d:Probe></e:Body></e:Envelope>";
         ByteBuf discoveryProbeMessage = Unpooled.copiedBuffer(xml, 0, xml.length(), StandardCharsets.UTF_8);
-        InetSocketAddress localNetworkAddress = new InetSocketAddress(0);// Listen for replies on all connections.
-        InetSocketAddress multiCastAddress = new InetSocketAddress(InetAddress.getByName("239.255.255.250"), port);
-        DatagramPacket datagramPacket = new DatagramPacket(discoveryProbeMessage, multiCastAddress,
-                localNetworkAddress);
-        NetworkInterface networkInterface = getLocalNIF();
-        DatagramChannel datagramChannel;
+        return new DatagramPacket(discoveryProbeMessage,
+                new InetSocketAddress(InetAddress.getByName("239.255.255.250"), 3702), new InetSocketAddress(0));
+    }
 
+    public void discoverCameras() throws UnknownHostException, InterruptedException {
+        List<NetworkInterface> nics = getLocalNICs();
+        if (nics == null || nics.isEmpty()) {
+            return;
+        }
+        NetworkInterface networkInterface = nics.get(0);
         Bootstrap bootstrap = new Bootstrap().group(new NioEventLoopGroup())
                 .channelFactory(new ChannelFactory<NioDatagramChannel>() {
                     @Override
@@ -213,26 +215,21 @@ public class OnvifDiscovery {
                 }).option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_REUSEADDR, true)
                 .option(ChannelOption.IP_MULTICAST_LOOP_DISABLED, false).option(ChannelOption.SO_RCVBUF, 2048)
                 .option(ChannelOption.IP_MULTICAST_TTL, 255).option(ChannelOption.IP_MULTICAST_IF, networkInterface);
-
-        datagramChannel = (DatagramChannel) bootstrap.bind(localNetworkAddress).sync().channel();
-        datagramChannel.joinGroup(multiCastAddress, networkInterface).sync();
-        ChannelFuture chFuture;
-        if (port == 1900) {
-            String ssdp = "M-SEARCH * HTTP/1.1\n" + "HOST: 239.255.255.250:1900\n" + "MAN: \"ssdp:discover\"\n"
-                    + "MX: 1\n" + "ST: urn:dial-multiscreen-org:service:dial:1\n"
-                    + "USER-AGENT: Microsoft Edge/83.0.478.61 Windows\n" + "\n" + "";
-            ByteBuf ssdpProbeMessage = Unpooled.copiedBuffer(ssdp, 0, ssdp.length(), StandardCharsets.UTF_8);
-            datagramPacket = new DatagramPacket(ssdpProbeMessage, multiCastAddress, localNetworkAddress);
-            chFuture = datagramChannel.writeAndFlush(datagramPacket);
-        } else {
-            chFuture = datagramChannel.writeAndFlush(datagramPacket);
+        ChannelGroup openChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
+        for (NetworkInterface nic : nics) {
+            DatagramChannel datagramChannel = (DatagramChannel) bootstrap.option(ChannelOption.IP_MULTICAST_IF, nic)
+                    .bind(new InetSocketAddress(0)).sync().channel();
+            datagramChannel
+                    .joinGroup(new InetSocketAddress(InetAddress.getByName("239.255.255.250"), 3702), networkInterface)
+                    .sync();
+            openChannels.add(datagramChannel);
+        }
+        if (!openChannels.isEmpty()) {
+            openChannels.writeAndFlush(wsDiscovery());
+            TimeUnit.SECONDS.sleep(6);
+            openChannels.close();
+            processCameraReplys();
+            bootstrap.config().group().shutdownGracefully();
         }
-        chFuture.awaitUninterruptibly(2000);
-        chFuture = datagramChannel.closeFuture();
-        TimeUnit.SECONDS.sleep(5);
-        datagramChannel.close();
-        chFuture.awaitUninterruptibly(6000);
-        processCameraReplys();
-        bootstrap.config().group().shutdownGracefully();
     }
 }
index 2081f08faecbc80e231868eaf8bb0ed959d6cd56..1e354d4a30e9f01f74036510489169ffac05474e 100644 (file)
                <category>Light</category>
        </channel-type>
 
-       <channel-type id="enablePrivacyMode">
+       <channel-type id="enablePrivacyMode" advanced="true">
                <item-type>Switch</item-type>
                <label>Enable Privacy Mode</label>
                <description>Turn the Privacy Mode on and off.</description>