From dfdbfa67c35cb12615ae4b22c5154459204f87ee Mon Sep 17 00:00:00 2001 From: Matthew Skinner Date: Tue, 21 May 2024 05:11:09 +1000 Subject: [PATCH] [ipcamera] Fix ONVIF alarms streams may stop and not restart on some cameras. (#16777) * Fault find stream Signed-off-by: Matthew Skinner --- .../ipcamera/internal/HikvisionHandler.java | 4 +- .../internal/handler/IpCameraHandler.java | 51 ++++++------------- .../ipcamera/internal/onvif/OnvifCodec.java | 10 +++- .../internal/onvif/OnvifConnection.java | 22 ++++---- 4 files changed, 37 insertions(+), 50 deletions(-) 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 4e49c4116b799..7216b486c53cc 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 @@ -178,11 +178,11 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms removeChannels.add(channel); } // start checking the input IO status - ipCameraHandler.lowPriorityRequests.set(0, + ipCameraHandler.lowPriorityRequests.add(0, "/ISAPI/System/IO/inputs/" + ipCameraHandler.cameraConfig.getNvrChannel() + "/status"); } else { // start checking the input IO status - ipCameraHandler.lowPriorityRequests.set(0, + ipCameraHandler.lowPriorityRequests.add(0, "/ISAPI/System/IO/inputs/" + ipCameraHandler.cameraConfig.getNvrChannel() + "/status"); } ipCameraHandler.removeChannels(removeChannels); 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 6a6650de9d5c8..0004c56957216 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 @@ -61,6 +61,7 @@ import org.openhab.binding.ipcamera.internal.MyNettyAuthHandler; import org.openhab.binding.ipcamera.internal.ReolinkHandler; import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection; +import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection.RequestType; import org.openhab.binding.ipcamera.internal.servlet.CameraServlet; import org.openhab.core.OpenHAB; import org.openhab.core.library.types.DecimalType; @@ -1556,19 +1557,13 @@ void pollCameraRunnable() { // what needs to be done every poll// switch (thing.getThingTypeUID().getId()) { case GENERIC_THING: - if (!snapshotPolling) { - checkCameraConnection(); - } + checkCameraConnection(); break; case ONVIF_THING: - if (!snapshotPolling) { - checkCameraConnection(); - } + onvifCamera.sendOnvifRequest(RequestType.Renew, onvifCamera.subscriptionXAddr); break; case INSTAR_THING: - if (!snapshotPolling) { - checkCameraConnection(); - } + checkCameraConnection(); noMotionDetected(CHANNEL_MOTION_ALARM); noMotionDetected(CHANNEL_PIR_ALARM); noMotionDetected(CHANNEL_HUMAN_ALARM); @@ -1588,19 +1583,14 @@ void pollCameraRunnable() { sendHttpGET("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=AudioMutation"); break; case REOLINK_THING: - if (cameraConfig.getNvrChannel() > 0) { - sendHttpGET("/api.cgi?cmd=GetAiState&channel=" + cameraConfig.getNvrChannel() + "&user=" - + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword()); - sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + "&user=" - + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword()); - } else if (!snapshotPolling) { - checkCameraConnection(); + if (cameraConfig.getOnvifPort() == 0) { + sendHttpGET("/api.cgi?cmd=GetAiState&channel=" + cameraConfig.getNvrChannel() + reolinkAuth); + sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + reolinkAuth); + } else { + onvifCamera.sendOnvifRequest(RequestType.Renew, onvifCamera.subscriptionXAddr); } 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", @@ -1609,9 +1599,6 @@ 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", @@ -1733,20 +1720,14 @@ public void initialize() { "[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\"admin\" }}}]"); } if (snapshotUri.isEmpty()) { - if (cameraConfig.getNvrChannel() < 1) { - snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=openHAB" + reolinkAuth; - } else { - snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=" + (cameraConfig.getNvrChannel() - 1) - + "&rs=openHAB" + reolinkAuth; - } + // ReolinkHandler will change the snapshotUri in the response to /api.cgi?cmd=Login + snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=" + cameraConfig.getNvrChannel() + "&rs=openHAB" + + reolinkAuth; } + // channel numbers for snapshots start at 0, while the rtsp start at 1 if (rtspUri.isEmpty()) { - if (cameraConfig.getNvrChannel() < 1) { - rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_01_main"; - } else { - rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_0" + cameraConfig.getNvrChannel() - + "_main"; - } + rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_0" + + (cameraConfig.getNvrChannel() + 1) + "_main"; } break; } @@ -1777,7 +1758,7 @@ private boolean supportsOnvifEvents() { case ONVIF_THING: return true; case REOLINK_THING: - if (cameraConfig.getNvrChannel() < 1) { + if (cameraConfig.getOnvifPort() > 0) { return true; } } diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifCodec.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifCodec.java index 091213c95ae81..7c54ad978d1ef 100644 --- a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifCodec.java +++ b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifCodec.java @@ -20,6 +20,7 @@ import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.CharsetUtil; @@ -46,6 +47,11 @@ public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object ms return; } try { + if (msg instanceof HttpResponse response) { + if (response.status().code() != 200) { + logger.trace("ONVIF replied with code {} message is {}", response.status().code(), msg); + } + } if (msg instanceof HttpContent content) { incomingMessage += content.content().toString(CharsetUtil.UTF_8); } @@ -65,11 +71,11 @@ public void userEventTriggered(@Nullable ChannelHandlerContext ctx, @Nullable Ob } if (evt instanceof IdleStateEvent) { IdleStateEvent e = (IdleStateEvent) evt; - logger.trace("IdleStateEvent received: {}", e.state()); + logger.debug("IdleStateEvent received: {}", e.state()); onvifConnection.setIsConnected(false); ctx.close(); } else { - logger.trace("Other ONVIF netty channel event occurred: {}", evt); + logger.debug("ONVIF netty channel event occurred: {}", evt); } } 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 e6cd9d10d8b94..d65e2802978a5 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 @@ -128,7 +128,7 @@ public enum RequestType { @SuppressWarnings("unused") private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service"; private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service"; - private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service"; + public String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service"; private boolean isConnected = false; private int mediaProfileIndex = 0; private String rtspUri = ""; @@ -269,7 +269,7 @@ private String getXml(RequestType requestType) { + mediaProfileTokens.get(mediaProfileIndex) + ""; case Renew: - return "PT1M"; + return "PT10S"; case GetConfigurations: return ""; case GetConfigurationOptions: @@ -310,8 +310,8 @@ public void processReply(String message) { logger.trace("ONVIF reply is: {}", message); if (message.contains("PullMessagesResponse")) { eventRecieved(message); - } else if (message.contains("RenewResponse")) { sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr); + } else if (message.contains("RenewResponse")) { } else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent. setIsConnected(true);// Instar profile T only cameras need this parseDateAndTime(message); @@ -380,6 +380,8 @@ public void processReply(String message) { ipCameraHandler.rtspUri = rtspUri; } } + } else { + logger.trace("Unhandled ONVIF reply is: {}", message); } } @@ -568,7 +570,7 @@ public void sendOnvifRequest(RequestType requestType, String xAddr) { @Override public void initChannel(SocketChannel socketChannel) throws Exception { - socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(20, 20, 20)); + socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(0, 0, 18)); socketChannel.pipeline().addLast("HttpClientCodec", new HttpClientCodec()); socketChannel.pipeline().addLast("OnvifCodec", new OnvifCodec(getHandle())); } @@ -583,14 +585,14 @@ public void operationComplete(@Nullable ChannelFuture future) { if (future == null) { return; } - if (future.isSuccess()) { + if (future.isDone() && future.isSuccess()) { Channel ch = future.channel(); ch.writeAndFlush(request); } else { // an error occurred if (future.isDone() && !future.isCancelled()) { Throwable cause = future.cause(); String msg = cause.getMessage(); - logger.debug("connect failed - cause {}", cause.getMessage()); + logger.debug("Connect failed - cause is: {}", cause.getMessage()); if (cause instanceof ConnectTimeoutException) { usingEvents = false;// Prevent Unsubscribe from being sent ipCameraHandler.cameraCommunicationError( @@ -601,9 +603,8 @@ public void operationComplete(@Nullable ChannelFuture future) { ipCameraHandler.cameraCommunicationError( "Camera refused to connect when using ONVIF to port:" + port); } - } - if (isConnected) { - disconnect(); + } else { + ipCameraHandler.cameraCommunicationError("Camera failed to connect due to being cancelled"); } } } @@ -652,7 +653,7 @@ public void gotoPreset(int index) { public void eventRecieved(String eventMessage) { String topic = Helper.fetchXML(eventMessage, "Topic", "tns1:"); if (topic.isEmpty()) { - sendOnvifRequest(RequestType.Renew, subscriptionXAddr); + logger.debug("No ONVIF Events occured in the last 8 seconds"); return; } String dataName = Helper.fetchXML(eventMessage, "tt:Data", "Name=\""); @@ -782,7 +783,6 @@ public void eventRecieved(String eventMessage) { default: logger.debug("Please report this camera has an un-implemented ONVIF event. Topic: {}", topic); } - sendOnvifRequest(RequestType.Renew, subscriptionXAddr); } public boolean supportsPTZ() {