Skip to content

Commit

Permalink
[ipcamera] Fix ONVIF fails to reconnect (openhab#13396)
Browse files Browse the repository at this point in the history
* Fix reconnecting issues
* Cleanup logging.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
Signed-off-by: Andras Uhrin <andras.uhrin@gmail.com>
  • Loading branch information
Skinah authored and andrasU committed Nov 12, 2022
1 parent 48ac6c2 commit da97fda
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,17 @@ public String getTinyUrl(String httpRequestURL) {
}

private void checkCameraConnection() {
if (snapshotUri.isEmpty() || snapshotPolling) {
// Already polling or camera has RTSP only and no HTTP server
if (snapshotPolling) {// Currently polling a real URL for snapshots, so camera must be online.
return;
} else if (ffmpegSnapshotGeneration) {// Use RTSP stream creating snapshots to know camera is online.
Ffmpeg localSnapshot = ffmpegSnapshot;
if (localSnapshot != null && !localSnapshot.getIsAlive()) {
cameraCommunicationError("FFmpeg Snapshots Stopped: Check your camera can be reached.");
return;
}
return;// ffmpeg snapshot stream is still alive
}
// Open a HTTP connection without sending any requests as we do not need a snapshot.
Bootstrap localBootstrap = mainBootstrap;
if (localBootstrap != null) {
ChannelFuture chFuture = localBootstrap
Expand Down Expand Up @@ -1362,6 +1369,7 @@ void pollingCameraConnection() {
if (snapshotUri.isEmpty() || "ffmpeg".equals(snapshotUri)) {
snapshotIsFfmpeg();
} else {
ffmpegSnapshotGeneration = false;
updateSnapshot();
}
return;
Expand All @@ -1374,6 +1382,7 @@ void pollingCameraConnection() {
if ("ffmpeg".equals(snapshotUri)) {
snapshotIsFfmpeg();
} else if (!snapshotUri.isEmpty()) {
ffmpegSnapshotGeneration = false;
updateSnapshot();
} else if (!rtspUri.isEmpty()) {
snapshotIsFfmpeg();
Expand Down Expand Up @@ -1497,16 +1506,9 @@ void pollCameraRunnable() {
// what needs to be done every poll//
switch (thing.getThingTypeUID().getId()) {
case GENERIC_THING:
if (!snapshotUri.isEmpty() && !snapshotPolling) {
if (!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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -114,6 +115,7 @@ public static enum RequestType {
private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
private @Nullable Bootstrap bootstrap;
private EventLoopGroup mainEventLoopGroup = new NioEventLoopGroup(2);
private ReentrantLock connecting = new ReentrantLock();
private String ipAddress = "";
private String user = "";
private String password = "";
Expand Down Expand Up @@ -308,7 +310,12 @@ public void processReply(String message) {
} else if (message.contains("RenewResponse")) {
sendOnvifRequest(requestBuilder(RequestType.PullMessages, subscriptionXAddr));
} else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
isConnected = true;
connecting.lock();
try {
isConnected = true;
} finally {
connecting.unlock();
}
sendOnvifRequest(requestBuilder(RequestType.GetCapabilities, deviceXAddr));
parseDateAndTime(message);
logger.debug("Openhabs UTC dateTime is:{}", getUTCdateTime());
Expand Down Expand Up @@ -355,7 +362,8 @@ public void processReply(String message) {
} else if (message.contains("GetSnapshotUriResponse")) {
snapshotUri = removeIPfromUrl(Helper.fetchXML(message, ":MediaUri", ":Uri"));
logger.debug("GetSnapshotUri:{}", snapshotUri);
if (ipCameraHandler.snapshotUri.isEmpty()) {
if (ipCameraHandler.snapshotUri.isEmpty()
&& !"ffmpeg".equals(ipCameraHandler.cameraConfig.getSnapshotUrl())) {
ipCameraHandler.snapshotUri = snapshotUri;
}
} else if (message.contains("GetStreamUriResponse")) {
Expand Down Expand Up @@ -512,6 +520,7 @@ String createDigest(String nOnce, String dateTime) {
@SuppressWarnings("null")
public void sendOnvifRequest(HttpRequest request) {
if (bootstrap == null) {
mainEventLoopGroup = new NioEventLoopGroup(2);
bootstrap = new Bootstrap();
bootstrap.group(mainEventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
Expand All @@ -530,24 +539,28 @@ public void initChannel(SocketChannel socketChannel) throws Exception {
}
});
}
bootstrap.connect(new InetSocketAddress(ipAddress, onvifPort)).addListener(new ChannelFutureListener() {
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.isDone() && 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();
@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();
}
}
}
}
});
});
} else {
logger.debug("ONVIF message not sent as connection is shutting down");
}
}

OnvifConnection getHandle() {
Expand Down Expand Up @@ -833,42 +846,59 @@ public void sendEventRequest(RequestType requestType) {
}

public void connect(boolean useEvents) {
if (!isConnected) {
sendOnvifRequest(requestBuilder(RequestType.GetSystemDateAndTime, deviceXAddr));
usingEvents = useEvents;
connecting.lock();
try {
if (!isConnected) {
logger.debug("Connecting {} to ONVIF", ipAddress);
threadPool = Executors.newScheduledThreadPool(2);
sendOnvifRequest(requestBuilder(RequestType.GetSystemDateAndTime, deviceXAddr));
usingEvents = useEvents;
}
} finally {
connecting.unlock();
}
}

public boolean isConnected() {
return isConnected;
connecting.lock();
try {
return isConnected;
} finally {
connecting.unlock();
}
}

private void cleanup() {
mainEventLoopGroup.shutdownGracefully();
isConnected = false;
if (!mainEventLoopGroup.isShutdown()) {
if (!isConnected && !mainEventLoopGroup.isShuttingDown()) {
try {
mainEventLoopGroup.shutdownGracefully();
mainEventLoopGroup.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warn("ONVIF was not cleanly shutdown, due to being interrupted");
} finally {
logger.debug("Eventloop is shutdown:{}", mainEventLoopGroup.isShutdown());
bootstrap = null;
threadPool.shutdown();
}
}
threadPool.shutdown();
}

public void disconnect() {
if (bootstrap != null) {
if (usingEvents && isConnected && !mainEventLoopGroup.isShuttingDown()) {
// Some cameras may continue to send events even when they can't reach a server.
sendOnvifRequest(requestBuilder(RequestType.Unsubscribe, subscriptionXAddr));
connecting.lock();// Lock out multiple disconnect()/connect() attempts as we try to send Unsubscribe.
try {
isConnected = false;// isConnected is not thread safe, connecting.lock() used as fix.
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));
}
// give time for the Unsubscribe request to be sent, shutdownGracefully will try to send it first.
threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS);
} else {
cleanup();
}
// give time for the Unsubscribe request to be sent to the camera.
threadPool.schedule(this::cleanup, 50, TimeUnit.MILLISECONDS);
} else {
cleanup();
} finally {
connecting.unlock();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1913,7 +1913,7 @@
<advanced>true</advanced>
</parameter>

<parameter name="nvrChannel" type="integer" required="true" min="1" max="9" groupName="Settings">
<parameter name="nvrChannel" type="integer" required="true" min="1" max="99" groupName="Settings">
<label>NVR Input Channel</label>
<description>Set this to 1 if it is a stand alone camera, or to the input channel number if you use a compatible
NVR.
Expand Down

0 comments on commit da97fda

Please sign in to comment.