]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ipcamera] FIX: TAPO branded cameras require xAddr port to be different (#15073)
authorMatthew Skinner <matt@pcmus.com>
Sun, 16 Jul 2023 11:32:40 +0000 (21:32 +1000)
committerGitHub <noreply@github.com>
Sun, 16 Jul 2023 11:32:40 +0000 (13:32 +0200)
* FIX TAPO branded cameras require xAddr port to be different from the
main ONVIF PORT
* Fix for old API instar cameras.

---------

Signed-off-by: Matthew Skinner <matt@pcmus.com>
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/InstarHandler.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/OnvifConnection.java

index c728d633e20cf4e5b5de1bef2ce93438169b2dfa..48c3b4813be216d81b757dd5cfe3d24541b66c67 100644 (file)
@@ -176,6 +176,8 @@ public class Ffmpeg {
                             case SNAPSHOT:
                                 notFrozen = true;// RTSP_ALARMS, MJPEG and SNAPSHOT all set this to true, no break.
                                 break;
+                            default:
+                                break;
                         }
                     }
                 }
index 6fe407687cd9d986a325828f6c0ff7b9e2173eae..0aaee77ec096a683895ed26a9b7241ab8d451b3b 100644 (file)
@@ -384,7 +384,9 @@ public class InstarHandler extends ChannelDuplexHandler {
         ArrayList<String> lowPriorityRequests = new ArrayList<String>(7);
         lowPriorityRequests.add("/param.cgi?cmd=getaudioalarmattr");
         lowPriorityRequests.add("/cgi-bin/hi3510/param.cgi?cmd=getmdattr");
-        lowPriorityRequests.add("/param.cgi?cmd=getalarmattr");
+        if (ipCameraHandler.newInstarApi) {// old API cameras get a error 404 response to this
+            lowPriorityRequests.add("/param.cgi?cmd=getalarmattr");
+        }
         lowPriorityRequests.add("/param.cgi?cmd=getinfrared");
         lowPriorityRequests.add("/param.cgi?cmd=getoverlayattr&-region=1");
         lowPriorityRequests.add("/param.cgi?cmd=getpirattr");
index 546d8741a77769bea53042e53b8ced66ee5d6722..ad380b0f0368619b1e986a528a7cc0d0e5ae6ffa 100644 (file)
@@ -364,7 +364,6 @@ public class IpCameraHandler extends BaseThingHandler {
         }
 
         @Override
-        @SuppressWarnings("PMD.CompareObjectsWithEquals")
         public void userEventTriggered(@Nullable ChannelHandlerContext ctx, @Nullable Object evt) throws Exception {
             if (ctx == null) {
                 return;
@@ -384,7 +383,7 @@ public class IpCameraHandler extends BaseThingHandler {
                     }
                     ChannelTracking channelTracking = channelTrackingMap.get(urlToKeepOpen);
                     if (channelTracking != null) {
-                        if (channelTracking.getChannel() == ctx.channel()) {
+                        if (channelTracking.getChannel().equals(ctx.channel())) {
                             return; // don't auto close this as it is for the alarms.
                         }
                     }
@@ -745,7 +744,6 @@ public class IpCameraHandler extends BaseThingHandler {
      * open large amounts of channels. This may help to keep it under control and WARN the user every 8 seconds this is
      * still occurring.
      */
-    @SuppressWarnings("PMD.CompareObjectsWithEquals")
     private void cleanChannels() {
         for (Channel channel : openChannels) {
             boolean oldChannel = true;
@@ -753,7 +751,7 @@ public class IpCameraHandler extends BaseThingHandler {
                 if (!channelTracking.getChannel().isOpen() && channelTracking.getReply().isEmpty()) {
                     channelTrackingMap.remove(channelTracking.getRequestUrl());
                 }
-                if (channelTracking.getChannel() == channel) {
+                if (channelTracking.getChannel().equals(channel)) {
                     logger.debug("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl());
                     oldChannel = false;
                 }
index 6930a6e2cc872248f5eff3efe95240d5c7a5c0b6..fbf47b8263b901c0b260ce42d3d7ecefd57e1c07 100644 (file)
@@ -58,7 +58,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
 import io.netty.handler.codec.http.HttpClientCodec;
 import io.netty.handler.codec.http.HttpHeaderValues;
 import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpVersion;
 import io.netty.handler.timeout.IdleStateHandler;
 
@@ -308,7 +307,7 @@ public class OnvifConnection {
         if (message.contains("PullMessagesResponse")) {
             eventRecieved(message);
         } else if (message.contains("RenewResponse")) {
-            sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
+            sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
         } else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
             connecting.lock();
             try {
@@ -320,7 +319,7 @@ public class OnvifConnection {
             logger.debug("Openhabs UTC dateTime is:{}", getUTCdateTime());
         } else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
             parseXAddr(message);
-            sendOnvifRequest(requestBuilder(RequestType.GetProfiles, mediaXAddr));
+            sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
         } else if (message.contains("GetProfilesResponse")) {// 3rd to be sent.
             connecting.lock();
             try {
@@ -329,25 +328,25 @@ public class OnvifConnection {
                 connecting.unlock();
             }
             parseProfiles(message);
-            sendOnvifRequest(requestBuilder(RequestType.GetSnapshotUri, mediaXAddr));
-            sendOnvifRequest(requestBuilder(RequestType.GetStreamUri, mediaXAddr));
+            sendOnvifRequest(RequestType.GetSnapshotUri, mediaXAddr);
+            sendOnvifRequest(RequestType.GetStreamUri, mediaXAddr);
             if (ptzDevice) {
                 sendPTZRequest(RequestType.GetNodes);
             }
             if (usingEvents) {// stops API cameras from getting sent ONVIF events.
-                sendOnvifRequest(requestBuilder(RequestType.GetEventProperties, eventXAddr));
-                sendOnvifRequest(requestBuilder(RequestType.GetServiceCapabilities, eventXAddr));
+                sendOnvifRequest(RequestType.GetEventProperties, eventXAddr);
+                sendOnvifRequest(RequestType.GetServiceCapabilities, eventXAddr);
             }
         } else if (message.contains("GetServiceCapabilitiesResponse")) {
             if (message.contains("WSSubscriptionPolicySupport=\"true\"")) {
-                sendOnvifRequest(requestBuilder(RequestType.Subscribe, eventXAddr));
+                sendOnvifRequest(RequestType.Subscribe, eventXAddr);
             }
         } else if (message.contains("GetEventPropertiesResponse")) {
-            sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr));
+            sendOnvifRequest(RequestType.CreatePullPointSubscription, eventXAddr);
         } else if (message.contains("CreatePullPointSubscriptionResponse")) {
             subscriptionXAddr = Helper.fetchXML(message, "SubscriptionReference>", "Address>");
             logger.debug("subscriptionXAddr={}", subscriptionXAddr);
-            sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
+            sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
         } else if (message.contains("GetStatusResponse")) {
             processPTZLocation(message);
         } else if (message.contains("GetPresetsResponse")) {
@@ -380,54 +379,6 @@ public class OnvifConnection {
         }
     }
 
-    HttpRequest requestBuilder(RequestType requestType, String xAddr) {
-        logger.trace("Sending ONVIF request:{}", requestType);
-        String security = "";
-        String extraEnvelope = "";
-        String headerTo = "";
-        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\">" + xAddr + "</a:To>";
-            extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
-        }
-        String headers;
-        if (!password.isEmpty() && !requestType.equals(RequestType.GetSystemDateAndTime)) {
-            String nonce = createNonce();
-            String dateTime = getUTCdateTime();
-            String digest = createDigest(nonce, dateTime);
-            security = "<Security s:mustUnderstand=\"1\" xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><UsernameToken><Username>"
-                    + user
-                    + "</Username><Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"
-                    + digest
-                    + "</Password><Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">"
-                    + encodeBase64(nonce)
-                    + "</Nonce><Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
-                    + dateTime + "</Created></UsernameToken></Security>";
-            headers = "<s:Header>" + security + headerTo + "</s:Header>";
-        } else {// GetSystemDateAndTime must not be password protected as per spec.
-            headers = "";
-        }
-        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");
-        request.headers().set("Host", ipAddress + ":" + onvifPort);
-        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 + ">"
-                + headers
-                + "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
-                + getXmlCache + "</s:Body></s:Envelope>";
-        request.headers().add("SOAPAction", "\"" + actionString + "/" + requestType + "\"");
-        ByteBuf bbuf = Unpooled.copiedBuffer(fullXml, StandardCharsets.UTF_8);
-        request.headers().set("Content-Length", bbuf.readableBytes());
-        request.content().clear().writeBytes(bbuf);
-        return request;
-    }
-
     /**
      * The {@link removeIPfromUrl} Will throw away all text before the cameras IP, also removes the IP and the PORT
      * leaving just the URL.
@@ -456,6 +407,19 @@ public class OnvifConnection {
         return "";
     }
 
+    int extractPortFromUrl(String url) {
+        int startIndex = url.indexOf("//") + 2;// skip past http://
+        startIndex = url.indexOf(":", startIndex);
+        if (startIndex == -1) {// no port defined so use port 80
+            return 80;
+        }
+        int endIndex = url.indexOf("/", startIndex);// skip past any :port to the slash /
+        if (endIndex == -1) {
+            return 80;
+        }
+        return Integer.parseInt(url.substring(startIndex + 1, endIndex));
+    }
+
     void parseXAddr(String message) {
         // Normally I would search '<tt:XAddr>' instead but Foscam needed this work around.
         String temp = Helper.fetchXML(message, "<tt:Device", "tt:XAddr");
@@ -536,19 +500,64 @@ public class OnvifConnection {
         return Base64.getEncoder().encodeToString(encryptedRaw);
     }
 
-    @SuppressWarnings("null")
-    public void sendOnvifRequest(HttpRequest request) {
-        if (bootstrap == null) {
+    public void sendOnvifRequest(RequestType requestType, String xAddr) {
+        logger.trace("Sending ONVIF request:{}", requestType);
+        String security = "";
+        String extraEnvelope = "";
+        String headerTo = "";
+        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\">" + xAddr + "</a:To>";
+            extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
+        }
+        String headers;
+        if (!password.isEmpty() && !requestType.equals(RequestType.GetSystemDateAndTime)) {
+            String nonce = createNonce();
+            String dateTime = getUTCdateTime();
+            String digest = createDigest(nonce, dateTime);
+            security = "<Security s:mustUnderstand=\"1\" xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><UsernameToken><Username>"
+                    + user
+                    + "</Username><Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"
+                    + digest
+                    + "</Password><Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">"
+                    + encodeBase64(nonce)
+                    + "</Nonce><Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
+                    + dateTime + "</Created></UsernameToken></Security>";
+            headers = "<s:Header>" + security + headerTo + "</s:Header>";
+        } else {// GetSystemDateAndTime must not be password protected as per spec.
+            headers = "";
+        }
+        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");
+        request.headers().set("Host", ipAddress + ":" + onvifPort);
+        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 + ">"
+                + headers
+                + "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+                + getXmlCache + "</s:Body></s:Envelope>";
+        request.headers().add("SOAPAction", "\"" + actionString + "/" + requestType + "\"");
+        ByteBuf bbuf = Unpooled.copiedBuffer(fullXml, StandardCharsets.UTF_8);
+        request.headers().set("Content-Length", bbuf.readableBytes());
+        request.content().clear().writeBytes(bbuf);
+
+        Bootstrap localBootstap = bootstrap;
+        if (localBootstap == null) {
             mainEventLoopGroup = new NioEventLoopGroup(2);
-            bootstrap = new Bootstrap();
-            bootstrap.group(mainEventLoopGroup);
-            bootstrap.channel(NioSocketChannel.class);
-            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
-            bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
-            bootstrap.option(ChannelOption.SO_SNDBUF, 1024 * 8);
-            bootstrap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
-            bootstrap.option(ChannelOption.TCP_NODELAY, true);
-            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
+            localBootstap = new Bootstrap();
+            localBootstap.group(mainEventLoopGroup);
+            localBootstap.channel(NioSocketChannel.class);
+            localBootstap.option(ChannelOption.SO_KEEPALIVE, true);
+            localBootstap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
+            localBootstap.option(ChannelOption.SO_SNDBUF, 1024 * 8);
+            localBootstap.option(ChannelOption.SO_RCVBUF, 1024 * 1024);
+            localBootstap.option(ChannelOption.TCP_NODELAY, true);
+            localBootstap.handler(new ChannelInitializer<SocketChannel>() {
 
                 @Override
                 public void initChannel(SocketChannel socketChannel) throws Exception {
@@ -557,26 +566,28 @@ public class OnvifConnection {
                     socketChannel.pipeline().addLast("OnvifCodec", new OnvifCodec(getHandle()));
                 }
             });
+            bootstrap = localBootstap;
         }
         if (!mainEventLoopGroup.isShuttingDown()) {
-            bootstrap.connect(new InetSocketAddress(ipAddress, onvifPort)).addListener(new ChannelFutureListener() {
-
-                @Override
-                public void operationComplete(@Nullable ChannelFuture future) {
-                    if (future == null) {
-                        return;
-                    }
-                    if (future.isSuccess()) {
-                        Channel ch = future.channel();
-                        ch.writeAndFlush(request);
-                    } else { // an error occured
-                        logger.debug("Camera is not reachable on ONVIF port:{} or the port may be wrong.", onvifPort);
-                        if (isConnected) {
-                            disconnect();
+            localBootstap.connect(new InetSocketAddress(ipAddress, extractPortFromUrl(xAddr)))
+                    .addListener(new ChannelFutureListener() {
+
+                        @Override
+                        public void operationComplete(@Nullable ChannelFuture future) {
+                            if (future == null) {
+                                return;
+                            }
+                            if (future.isSuccess()) {
+                                Channel ch = future.channel();
+                                ch.writeAndFlush(request);
+                            } else { // an error occured
+                                logger.debug("Camera is not reachable when using xAddr:{}.", xAddr);
+                                if (isConnected) {
+                                    disconnect();
+                                }
+                            }
                         }
-                    }
-                }
-            });
+                    });
         } else {
             logger.debug("ONVIF message not sent as connection is shutting down");
         }
@@ -621,7 +632,7 @@ public class OnvifConnection {
     public void eventRecieved(String eventMessage) {
         String topic = Helper.fetchXML(eventMessage, "Topic", "tns1:");
         if (topic.isEmpty()) {
-            sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
+            sendOnvifRequest(RequestType.Renew, subscriptionXAddr);
             return;
         }
         String dataName = Helper.fetchXML(eventMessage, "tt:Data", "Name=\"");
@@ -751,7 +762,7 @@ public class OnvifConnection {
             default:
                 logger.debug("Please report this camera has an un-implemented ONVIF event. Topic:{}", topic);
         }
-        sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
+        sendOnvifRequest(RequestType.Renew, subscriptionXAddr);
     }
 
     public boolean supportsPTZ() {
@@ -898,11 +909,11 @@ public class OnvifConnection {
             logger.debug("ONVIF was not connected when a PTZ request was made, connecting now");
             connect(usingEvents);
         }
-        sendOnvifRequest(requestBuilder(requestType, ptzXAddr));
+        sendOnvifRequest(requestType, ptzXAddr);
     }
 
     public void sendEventRequest(RequestType requestType) {
-        sendOnvifRequest(requestBuilder(requestType, eventXAddr));
+        sendOnvifRequest(requestType, eventXAddr);
     }
 
     public void connect(boolean useEvents) {
@@ -911,9 +922,9 @@ public class OnvifConnection {
             if (!isConnected) {
                 logger.debug("Connecting {} to ONVIF", ipAddress);
                 threadPool = Executors.newScheduledThreadPool(2);
-                sendOnvifRequest(requestBuilder(RequestType.GetSystemDateAndTime, deviceXAddr));
+                sendOnvifRequest(RequestType.GetSystemDateAndTime, deviceXAddr);
                 usingEvents = useEvents;
-                sendOnvifRequest(requestBuilder(RequestType.GetCapabilities, deviceXAddr));
+                sendOnvifRequest(RequestType.GetCapabilities, deviceXAddr);
             }
         } finally {
             connecting.unlock();
@@ -951,7 +962,7 @@ public class OnvifConnection {
             if (bootstrap != null) {
                 if (usingEvents && !mainEventLoopGroup.isShuttingDown()) {
                     // Some cameras may continue to send events even when they can't reach a server.
-                    sendOnvifRequest(requestBuilder(RequestType.Unsubscribe, subscriptionXAddr));
+                    sendOnvifRequest(RequestType.Unsubscribe, subscriptionXAddr);
                 }
                 // give time for the Unsubscribe request to be sent, shutdownGracefully will try to send it first.
                 threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS);