From 894ae20f50039a400f0eae09ed30d901606209e2 Mon Sep 17 00:00:00 2001 From: Matthew Skinner Date: Sun, 24 Oct 2021 20:36:20 +1100 Subject: [PATCH] [ipcamera] Improvements and fix 503 errors go to offline with Hik (#11419) * Stop hik logging 401 with digest. Signed-off-by: Matthew Skinner * Improve and fix generic cams Signed-off-by: Matthew Skinner * Stop dahua IntelliFrame logging Signed-off-by: Matthew Skinner * Catch IllegalStateException Signed-off-by: Matthew Skinner * Trial reusing channels. Signed-off-by: Matthew Skinner * Tidy up Signed-off-by: Matthew Skinner * cleanup 2 Signed-off-by: Matthew Skinner * Cleanup 3 Signed-off-by: Matthew Skinner * Disable checking connection with event stream. Signed-off-by: Matthew Skinner * Bug fix Signed-off-by: Matthew Skinner * more cleanup Signed-off-by: Matthew Skinner * more cleanup Signed-off-by: Matthew Skinner * Reduce logging to only whats needed. Signed-off-by: Matthew Skinner * fix offline detection. Signed-off-by: Matthew Skinner * fixes to ipcamera.mjpeg Signed-off-by: Matthew Skinner * reverse some connection checks Signed-off-by: Matthew Skinner Signed-off-by: Dave J Schoepel --- .../ipcamera/internal/ChannelTracking.java | 10 ++ .../ipcamera/internal/DahuaHandler.java | 4 +- .../binding/ipcamera/internal/Ffmpeg.java | 3 +- .../ipcamera/internal/HikvisionHandler.java | 3 - .../ipcamera/internal/MyNettyAuthHandler.java | 38 +++-- .../internal/handler/IpCameraHandler.java | 134 ++++++++++-------- .../internal/onvif/OnvifConnection.java | 4 +- .../internal/servlet/CameraServlet.java | 10 +- .../internal/servlet/StreamOutput.java | 7 +- .../resources/OH-INF/thing/thing-types.xml | 8 ++ 10 files changed, 125 insertions(+), 96 deletions(-) diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/ChannelTracking.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/ChannelTracking.java index fd42f29c91186..52a429bb853eb 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/ChannelTracking.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/ChannelTracking.java @@ -15,6 +15,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; /** * The {@link ChannelTracking} Can be used to find the handle for a HTTP channel if you know the URL. The reply can @@ -43,6 +44,15 @@ public Channel getChannel() { return channel; } + /** + * Closes the channel, but keeps the HTTP reply stored in the tracker. + * + * @return ChannelFuture + */ + public ChannelFuture closeChannel() { + return channel.close(); + } + public String getReply() { return storedReply; } diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java index 05a6fd7c2a32a..816233f2619a2 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java @@ -49,7 +49,7 @@ public DahuaHandler(IpCameraHandler handler, int nvrChannel) { } private void processEvent(String content) { - int startIndex = content.indexOf("Code=", 12) + 5;// skip --myboundary + int startIndex = content.indexOf("Code=", 12) + 5;// skip --myboundary and Code= int endIndex = content.indexOf(";", startIndex + 1); if (startIndex == -1 || endIndex == -1) { ipCameraHandler.logger.debug("Code= not found in Dahua event. Content was:{}", content); @@ -177,7 +177,9 @@ private void processEvent(String content) { case "LensMaskClose": ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF); break; + // Skip these so they are not logged. case "TimeChange": + case "IntelliFrame": case "NTPAdjustTime": case "StorageChange": case "Reboot": diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java index 9ca8a20711cd9..b940e76e8b4d7 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java @@ -96,7 +96,7 @@ public void checkKeepAlive() { } private class IpCameraFfmpegThread extends Thread { - private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2); + private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(1); public int countOfMotions; IpCameraFfmpegThread() { @@ -220,6 +220,7 @@ public void stopConverting() { Process localProcess = process; if (localProcess != null) { localProcess.destroyForcibly(); + process = null; } if (format.equals(FFmpegFormat.HLS)) { ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.OFF); diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java index a2e86da0677c2..51ae91b6e16fa 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java @@ -196,9 +196,6 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms } } break; - default: - logger.debug("Unhandled reply-{}.", content); - break; } } } finally { diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/MyNettyAuthHandler.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/MyNettyAuthHandler.java index 63df45cd5f6f1..f5089acb51886 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/MyNettyAuthHandler.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/MyNettyAuthHandler.java @@ -89,7 +89,9 @@ public void processAuth(String authenticate, String httpMethod, String requestUR /////// Fresh Digest Authenticate method follows as Basic is already handled and returned //////// realm = Helper.searchString(authenticate, "realm=\""); if (realm.isEmpty()) { - logger.warn("Could not find a valid WWW-Authenticate response in :{}", authenticate); + logger.warn( + "No valid WWW-Authenticate in response. Has the camera activated the illegal login lock? Details:{}", + authenticate); return; } nonce = Helper.searchString(authenticate, "nonce=\""); @@ -142,23 +144,17 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms if (msg == null || ctx == null) { return; } - boolean closeConnection = true; - String authenticate = ""; if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; if (response.status().code() == 401) { + ctx.close(); if (!response.headers().isEmpty()) { + String authenticate = ""; for (CharSequence name : response.headers().names()) { for (CharSequence value : response.headers().getAll(name)) { if (name.toString().equalsIgnoreCase("WWW-Authenticate")) { authenticate = value.toString(); } - if (name.toString().equalsIgnoreCase("Connection") - && value.toString().contains("keep-alive")) { - // closeConnection = false; - // trial this for a while to see if it solves too many bytes with digest turned on. - closeConnection = true; - } } } if (!authenticate.isEmpty()) { @@ -167,24 +163,22 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms ipCameraHandler.cameraConfigError( "Camera gave no WWW-Authenticate: Your login details must be wrong."); } - if (closeConnection) { - ctx.close();// needs to be here - } } } else if (response.status().code() != 200) { - logger.debug("Camera at IP:{} gave a reply with a response code of :{}", - ipCameraHandler.cameraConfig.getIp(), response.status().code()); + ctx.close(); + switch (response.status().code()) { + case 403: + logger.warn( + "403 Forbidden: Check camera setup or has the camera activated the illegal login lock?"); + break; + default: + logger.debug("Camera at IP:{} gave a reply with a response code of :{}", + ipCameraHandler.cameraConfig.getIp(), response.status().code()); + break; + } } } // Pass the Message back to the pipeline for the next handler to process// super.channelRead(ctx, msg); } - - @Override - public void handlerAdded(@Nullable ChannelHandlerContext ctx) { - } - - @Override - public void handlerRemoved(@Nullable ChannelHandlerContext ctx) { - } } diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java index 92c2c462ccc7c..b96ff172083e3 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; @@ -123,7 +124,7 @@ public class IpCameraHandler extends BaseThingHandler { public final Logger logger = LoggerFactory.getLogger(getClass()); public final IpCameraDynamicStateDescriptionProvider stateDescriptionProvider; - private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); + private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2); private GroupTracker groupTracker; public CameraConfig cameraConfig = new CameraConfig(); @@ -140,6 +141,7 @@ public class IpCameraHandler extends BaseThingHandler { public @Nullable Ffmpeg ffmpegSnapshot = null; public boolean streamingAutoFps = false; public boolean motionDetected = false; + public Instant lastSnapshotRequest = Instant.now(); public Instant currentSnapshotTime = Instant.now(); private @Nullable ScheduledFuture cameraConnectionJob = null; private @Nullable ScheduledFuture pollCameraJob = null; @@ -197,7 +199,6 @@ private class CommonCameraHandler extends ChannelDuplexHandler { private String boundary = ""; private Object reply = new Object(); private String requestUrl = ""; - private boolean closeConnection = true; private boolean isChunked = false; public void setURL(String url) { @@ -223,11 +224,6 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms case "content-length": bytesToRecieve = Integer.parseInt(response.headers().getAsString(name)); break; - case "connection": - if (response.headers().getAsString(name).contains("keep-alive")) { - closeConnection = false; - } - break; case "transfer-encoding": if (response.headers().getAsString(name).contains("chunked")) { isChunked = true; @@ -236,7 +232,6 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms } } if (contentType.contains("multipart")) { - closeConnection = false; if (mjpegUri.equals(requestUrl)) { if (msg instanceof HttpMessage) { // very start of stream only @@ -278,14 +273,7 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms } if (content instanceof LastHttpContent) { processSnapshot(incomingJpeg); - // testing next line and if works need to do a full cleanup of this function. - closeConnection = true; - if (closeConnection) { - ctx.close(); - } else { - bytesToRecieve = 0; - bytesAlreadyRecieved = 0; - } + ctx.close(); } } else { // incomingMessage that is not an IMAGE if (incomingMessage.isEmpty()) { @@ -353,18 +341,6 @@ else if (contentType.contains("multipart")) { } } - @Override - public void channelReadComplete(@Nullable ChannelHandlerContext ctx) { - } - - @Override - public void handlerAdded(@Nullable ChannelHandlerContext ctx) { - } - - @Override - public void handlerRemoved(@Nullable ChannelHandlerContext ctx) { - } - @Override public void exceptionCaught(@Nullable ChannelHandlerContext ctx, @Nullable Throwable cause) { if (cause == null || ctx == null) { @@ -394,9 +370,6 @@ public void userEventTriggered(@Nullable ChannelHandlerContext ctx, @Nullable Ob case DAHUA_THING: urlToKeepOpen = "/cgi-bin/eventManager.cgi?action=attach&codes=[All]"; break; - case HIKVISION_THING: - urlToKeepOpen = "/ISAPI/Event/notification/alertStream"; - break; case DOORBIRD_THING: urlToKeepOpen = "/bha-api/monitor.cgi?ring=doorbell,motionsensor"; break; @@ -407,6 +380,7 @@ public void userEventTriggered(@Nullable ChannelHandlerContext ctx, @Nullable Ob return; // don't auto close this as it is for the alarms. } } + logger.debug("Closing an idle channel for camera:{}", cameraConfig.getIp()); ctx.close(); } } @@ -515,6 +489,10 @@ public String getTinyUrl(String httpRequestURL) { } private void checkCameraConnection() { + if (snapshotUri.isEmpty() || snapshotPolling) { + // Already polling or camera has RTSP only and no HTTP server + return; + } Bootstrap localBootstrap = mainBootstrap; if (localBootstrap != null) { ChannelFuture chFuture = localBootstrap @@ -688,7 +666,8 @@ public void startStreamServer() { } public void openCamerasStream() { - threadPool.schedule(this::openMjpegStream, 500, TimeUnit.MILLISECONDS); + closeChannel(getTinyUrl(mjpegUri)); + mainEventLoopGroup.schedule(this::openMjpegStream, 0, TimeUnit.MILLISECONDS); } private void openMjpegStream() { @@ -719,7 +698,7 @@ public void closeChannel(String url) { * open large amounts of channels. This may help to keep it under control and WARN the user every 8 seconds this is * still occurring. */ - void cleanChannels() { + private void cleanChannels() { for (Channel channel : openChannels) { boolean oldChannel = true; for (ChannelTracking channelTracking : channelTrackingMap.values()) { @@ -727,7 +706,7 @@ void cleanChannels() { channelTrackingMap.remove(channelTracking.getRequestUrl()); } if (channelTracking.getChannel() == channel) { - logger.trace("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl()); + logger.debug("Open channel to camera is used for URL:{}", channelTracking.getRequestUrl()); oldChannel = false; } } @@ -977,7 +956,7 @@ public void motionDetected(String thisAlarmsChannel) { if (cameraConfig.getUpdateImageWhen().contains("2")) { if (!firstMotionAlarm) { if (!snapshotUri.isEmpty()) { - sendHttpGET(snapshotUri); + updateSnapshot(); } firstMotionAlarm = true;// reset back to false when the jpg arrives. } @@ -995,7 +974,7 @@ public void audioDetected() { if (cameraConfig.getUpdateImageWhen().contains("3")) { if (!firstAudioAlarm) { if (!snapshotUri.isEmpty()) { - sendHttpGET(snapshotUri); + updateSnapshot(); } firstAudioAlarm = true;// reset back to false when the jpg arrives. } @@ -1161,7 +1140,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { updateImageChannel = false; } else { updateImageChannel = true; - sendHttpGET(snapshotUri);// Allows this to change Image FPS on demand + updateSnapshot();// Allows this to change Image FPS on demand } } else { Ffmpeg localSnaps = ffmpegSnapshot; @@ -1194,7 +1173,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } onvifCamera.setAbsolutePan(Float.valueOf(command.toString())); - threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); + mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); } return; case CHANNEL_TILT: @@ -1219,7 +1198,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } onvifCamera.setAbsoluteTilt(Float.valueOf(command.toString())); - threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); + mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); } return; case CHANNEL_ZOOM: @@ -1244,7 +1223,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } onvifCamera.setAbsoluteZoom(Float.valueOf(command.toString())); - threadPool.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); + mainEventLoopGroup.schedule(this::sendPTZRequest, 500, TimeUnit.MILLISECONDS); } return; } @@ -1316,11 +1295,14 @@ private void bringCameraOnline() { Future localFuture = cameraConnectionJob; if (localFuture != null) { localFuture.cancel(false); + cameraConnectionJob = null; } - if (cameraConfig.getGifPreroll() > 0 || cameraConfig.getUpdateImageWhen().contains("1")) { - snapshotPolling = true; - snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 1000, cameraConfig.getPollTime(), - TimeUnit.MILLISECONDS); + if (!snapshotUri.isEmpty()) { + if (cameraConfig.getGifPreroll() > 0 || cameraConfig.getUpdateImageWhen().contains("1")) { + snapshotPolling = true; + snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 1000, + cameraConfig.getPollTime(), TimeUnit.MILLISECONDS); + } } pollCameraJob = threadPool.scheduleWithFixedDelay(this::pollCameraRunnable, 1000, 8000, TimeUnit.MILLISECONDS); @@ -1341,10 +1323,10 @@ private void bringCameraOnline() { } void snapshotIsFfmpeg() { - bringCameraOnline(); snapshotUri = "";// ffmpeg is a valid option. Simplify further checks. logger.debug( "Binding has no snapshot url. Will use your CPU and FFmpeg to create snapshots from the cameras RTSP."); + bringCameraOnline(); if (!rtspUri.isEmpty()) { updateImageChannel = false; ffmpegSnapshotGeneration = true; @@ -1363,7 +1345,7 @@ void pollingCameraConnection() { if (snapshotUri.isEmpty() || "ffmpeg".equals(snapshotUri)) { snapshotIsFfmpeg(); } else { - sendHttpRequest("GET", snapshotUri, null); + updateSnapshot(); } return; } @@ -1375,7 +1357,7 @@ void pollingCameraConnection() { if ("ffmpeg".equals(snapshotUri)) { snapshotIsFfmpeg(); } else if (!snapshotUri.isEmpty()) { - sendHttpRequest("GET", snapshotUri, null); + updateSnapshot(); } else if (!rtspUri.isEmpty()) { snapshotIsFfmpeg(); } else { @@ -1410,7 +1392,7 @@ boolean streamIsStopped(String url) { void snapshotRunnable() { // Snapshot should be first to keep consistent time between shots - sendHttpGET(snapshotUri); + updateSnapshot(); if (snapCount > 0) { if (--snapCount == 0) { setupFfmpegFormat(FFmpegFormat.GIF); @@ -1418,9 +1400,18 @@ void snapshotRunnable() { } } + private void takeSnapshot() { + sendHttpGET(snapshotUri); + } + + private void updateSnapshot() { + lastSnapshotRequest = Instant.now(); + mainEventLoopGroup.schedule(this::takeSnapshot, 0, TimeUnit.MILLISECONDS); + } + public byte[] getSnapshot() { if (!isOnline) { - // Keep streams open when the camera goes offline so they dont stop. + // Single gray pixel JPG to keep streams open when the camera goes offline so they dont stop. return new byte[] { (byte) 0xff, (byte) 0xd8, (byte) 0xff, (byte) 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, (byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, @@ -1431,8 +1422,10 @@ public byte[] getSnapshot() { (byte) 0xff, (byte) 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, (byte) 0xff, (byte) 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, (byte) 0xd2, (byte) 0xcf, 0x20, (byte) 0xff, (byte) 0xd9 }; } - if (!snapshotPolling && !ffmpegSnapshotGeneration) { - sendHttpGET(snapshotUri); + // Most cameras will return a 503 busy error if snapshot is faster than 1 second + long lastUpdatedMs = Duration.between(lastSnapshotRequest, Instant.now()).toMillis(); + if (!snapshotPolling && !ffmpegSnapshotGeneration && lastUpdatedMs >= cameraConfig.getPollTime()) { + updateSnapshot(); } lockCurrentSnapshot.lock(); try { @@ -1464,26 +1457,19 @@ public void startSnapshotPolling() { if (snapshotPolling || ffmpegSnapshotGeneration) { return; // Already polling or creating with FFmpeg from RTSP } - if (streamingSnapshotMjpeg || streamingAutoFps) { + if (streamingSnapshotMjpeg || streamingAutoFps || cameraConfig.getUpdateImageWhen().contains("4")) { snapshotPolling = true; - snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 200, cameraConfig.getPollTime(), - TimeUnit.MILLISECONDS); - } else if (cameraConfig.getUpdateImageWhen().contains("4")) { // During Motion Alarms - snapshotPolling = true; - snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 200, cameraConfig.getPollTime(), + snapshotJob = threadPool.scheduleWithFixedDelay(this::snapshotRunnable, 0, cameraConfig.getPollTime(), TimeUnit.MILLISECONDS); } } /** - * {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep mjpeg and alarm + * {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep alarm * streams open and more. * */ void pollCameraRunnable() { - if (!snapshotUri.isEmpty() && !snapshotPolling) {// we need to check camera is still online. - checkCameraConnection(); - } // NOTE: Use lowPriorityRequests if get request is not needed every poll. if (!lowPriorityRequests.isEmpty()) { if (lowPriorityCounter >= lowPriorityRequests.size()) { @@ -1494,13 +1480,29 @@ void pollCameraRunnable() { // what needs to be done every poll// switch (thing.getThingTypeUID().getId()) { case GENERIC_THING: + if (!snapshotUri.isEmpty() && !snapshotPolling) { + checkCameraConnection(); + } + // RTSP stream has stopped and we need it for snapshots + if (ffmpegSnapshotGeneration) { + Ffmpeg localSnapshot = ffmpegSnapshot; + if (localSnapshot != null && !localSnapshot.getIsAlive()) { + localSnapshot.startConverting(); + } + } break; case ONVIF_THING: + if (!snapshotPolling) { + checkCameraConnection(); + } if (!onvifCamera.isConnected()) { onvifCamera.connect(true); } break; case INSTAR_THING: + if (!snapshotPolling) { + checkCameraConnection(); + } noMotionDetected(CHANNEL_MOTION_ALARM); noMotionDetected(CHANNEL_PIR_ALARM); noAudioDetected(); @@ -1517,6 +1519,9 @@ void pollCameraRunnable() { sendHttpGET("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=AudioMutation"); break; case DAHUA_THING: + if (!snapshotPolling) { + checkCameraConnection(); + } // Check for alarms, channel for NVRs appears not to work at filtering. if (streamIsStopped("/cgi-bin/eventManager.cgi?action=attach&codes=[All]")) { logger.info("The alarm stream was not running for camera {}, re-starting it now", @@ -1525,6 +1530,9 @@ void pollCameraRunnable() { } break; case DOORBIRD_THING: + if (!snapshotPolling) { + checkCameraConnection(); + } // Check for alarms, channel for NVRs appears not to work at filtering. if (streamIsStopped("/bha-api/monitor.cgi?ring=doorbell,motionsensor")) { logger.info("The alarm stream was not running for camera {}, re-starting it now", @@ -1541,7 +1549,7 @@ void pollCameraRunnable() { if (localHLS != null) { localHLS.checkKeepAlive(); } - if (openChannels.size() > 18) { + if (openChannels.size() > 10) { logger.debug("There are {} open Channels being tracked.", openChannels.size()); cleanChannels(); } @@ -1550,7 +1558,7 @@ void pollCameraRunnable() { @Override public void initialize() { cameraConfig = getConfigAs(CameraConfig.class); - threadPool = Executors.newScheduledThreadPool(4); + threadPool = Executors.newScheduledThreadPool(2); mainEventLoopGroup = new NioEventLoopGroup(3); snapshotUri = getCorrectUrlFormat(cameraConfig.getSnapshotUrl()); mjpegUri = getCorrectUrlFormat(cameraConfig.getMjpegUrl()); diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java index 81fdc50c5e699..95a0e109f2198 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java @@ -333,8 +333,6 @@ public void processReply(String message) { } } else if (message.contains("GetEventPropertiesResponse")) { sendOnvifRequest(requestBuilder(RequestType.CreatePullPointSubscription, eventXAddr)); - } else if (message.contains("SubscribeResponse")) { - logger.info("Onvif Subscribe appears to be working for Alarms/Events."); } else if (message.contains("CreatePullPointSubscriptionResponse")) { subscriptionXAddr = removeIPfromUrl(Helper.fetchXML(message, "SubscriptionReference>", "Address>")); logger.debug("subscriptionXAddr={}", subscriptionXAddr); @@ -863,7 +861,7 @@ public void disconnect() { sendOnvifRequest(requestBuilder(RequestType.Unsubscribe, subscriptionXAddr)); } // give time for the Unsubscribe request to be sent to the camera. - threadPool.schedule(this::cleanup, 100, TimeUnit.MILLISECONDS); + threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS); } else { cleanup(); } diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/CameraServlet.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/CameraServlet.java index d8f439151c553..f66e2f80b64ed 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/CameraServlet.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/CameraServlet.java @@ -160,10 +160,12 @@ public void run() { do { try { output.sendSnapshotBasedFrame(handler.getSnapshot()); - Thread.sleep(1005); + Thread.sleep(handler.cameraConfig.getPollTime()); } catch (InterruptedException | IOException e) { // Never stop streaming until IOException. Occurs when browser stops the stream. openSnapshotStreams.removeStream(output); + logger.debug("Now there are {} snapshots.mjpeg streams open.", + openSnapshotStreams.getNumberOfStreams()); if (openSnapshotStreams.isEmpty()) { handler.streamingSnapshotMjpeg = false; handler.stopSnapshotPolling(); @@ -187,8 +189,9 @@ public void run() { } else { ChannelTracking tracker = handler.channelTrackingMap.get(handler.mjpegUri); if (tracker == null || !tracker.getChannel().isOpen()) { - logger.warn("Not the first stream requested but the stream from camera was closed"); + logger.debug("Not the first stream requested but the stream from camera was closed"); handler.openCamerasStream(); + openStreams.closeAllStreams(); } output = new StreamOutput(resp, handler.mjpegContentType); openStreams.addStream(output); @@ -199,6 +202,7 @@ public void run() { } catch (InterruptedException | IOException e) { // Never stop streaming until IOException. Occurs when browser stops the stream. openStreams.removeStream(output); + logger.debug("Now there are {} ipcamera.mjpeg streams open.", openStreams.getNumberOfStreams()); if (openStreams.isEmpty()) { if (output.isSnapshotBased) { Ffmpeg localMjpeg = handler.ffmpegMjpeg; @@ -231,6 +235,8 @@ else if (counter % 8 == 0 || counter < 3) { } catch (InterruptedException | IOException e) { // Never stop streaming until IOException. Occurs when browser stops the stream. openAutoFpsStreams.removeStream(output); + logger.debug("Now there are {} autofps.mjpeg streams open.", + openAutoFpsStreams.getNumberOfStreams()); if (openAutoFpsStreams.isEmpty()) { handler.streamingAutoFps = false; logger.debug("All autofps.mjpeg streams have stopped."); diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/StreamOutput.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/StreamOutput.java index c2a30bd9865e6..03d7a82387af7 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/StreamOutput.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/servlet/StreamOutput.java @@ -73,7 +73,12 @@ public void sendSnapshotBasedFrame(byte[] currentSnapshot) throws IOException { } public void queueFrame(byte[] frame) { - fifo.add(frame); + try { + fifo.add(frame); + } catch (IllegalStateException e) { + fifo.remove(); + fifo.add(frame); + } } public void updateContentType(String contentType) { diff --git a/bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml index 81a64fedaefe7..c5b4dd7f40be3 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml @@ -313,6 +313,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -559,6 +560,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -859,6 +861,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -1149,6 +1152,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -1408,6 +1412,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -1685,6 +1690,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -1969,6 +1975,7 @@ Default is "1000" which is 1 second. 1000 + true @@ -2237,6 +2244,7 @@ Default is "1000" which is 1 second. 1000 + true