}
return;// ffmpeg snapshot stream is still alive
}
+
+ // if ONVIF cam also use connection state which is updated by regular messages to camera
+ if (thing.getThingTypeUID().getId().equals(ONVIF_THING) && snapshotUri.isEmpty() && onvifCamera.isConnected()) {
+ return;
+ }
+
// Open a HTTP connection without sending any requests as we do not need a snapshot.
Bootstrap localBootstrap = mainBootstrap;
if (localBootstrap != null) {
break;
}
ch.writeAndFlush(request);
- } else { // an error occured
+ } else { // an error occurred
cameraCommunicationError(
"Connection Timeout: Check your IP and PORT are correct and the camera can be reached.");
}
return;
}
if (cameraConfig.getOnvifPort() > 0 && !onvifCamera.isConnected()) {
+ if (onvifCamera.isConnectError()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Camera is not reachable");
+ } else if (onvifCamera.isRefusedError()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Camera refused connection on ONVIF ports.");
+ }
logger.debug("About to connect to the IP Camera using the ONVIF PORT at IP:{}:{}", cameraConfig.getIp(),
cameraConfig.getOnvifPort());
onvifCamera.connect(thing.getThingTypeUID().getId().equals(ONVIF_THING));
+ return;
}
if ("ffmpeg".equals(snapshotUri)) {
snapshotIsFfmpeg();
if (!snapshotPolling) {
checkCameraConnection();
}
- if (!onvifCamera.isConnected()) {
- onvifCamera.connect(true);
- }
break;
case INSTAR_THING:
if (!snapshotPolling) {
}
private void tryConnecting() {
+ int firstDelay = 4;
+ int normalDelay = 12; // doesn't make sense to have faster retry than CONNECT_TIMEOUT, which is 10 seconds, if
+ // camera is off
if (!thing.getThingTypeUID().getId().equals(GENERIC_THING)
&& !thing.getThingTypeUID().getId().equals(DOORBIRD_THING) && cameraConfig.getOnvifPort() > 0) {
onvifCamera = new OnvifConnection(this, cameraConfig.getIp() + ":" + cameraConfig.getOnvifPort(),
onvifCamera.setSelectedMediaProfile(cameraConfig.getOnvifMediaProfile());
// Only use ONVIF events if it is not an API camera.
onvifCamera.connect(supportsOnvifEvents());
+
+ if (supportsOnvifEvents()) {
+ // it takes some time to try to retrieve the ONVIF snapshot and stream URLs and update internal members
+ // on first connect; if connection lost, doesn't make sense to poll to often
+ firstDelay = 12;
+ normalDelay = 30;
+ }
}
- cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, 4, 8, TimeUnit.SECONDS);
+ cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, firstDelay, normalDelay,
+ TimeUnit.SECONDS);
}
private boolean supportsOnvifEvents() {
import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.*;
+import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
+import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
+ private boolean connectError = false;
+ private boolean refusedError = false;
private boolean isConnected = false;
private int mediaProfileIndex = 0;
private String snapshotUri = "";
} else if (message.contains("RenewResponse")) {
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
} else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
- connecting.lock();
- try {
- isConnected = true;
- } finally {
- connecting.unlock();
- }
+ setIsConnected(true);
+ sendOnvifRequest(RequestType.GetCapabilities, deviceXAddr);
parseDateAndTime(message);
logger.debug("Openhabs UTC dateTime is:{}", getUTCdateTime());
} else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
parseXAddr(message);
sendOnvifRequest(RequestType.GetProfiles, mediaXAddr);
} else if (message.contains("GetProfilesResponse")) {// 3rd to be sent.
- connecting.lock();
- try {
- isConnected = true;
- } finally {
- connecting.unlock();
- }
+ setIsConnected(true);
parseProfiles(message);
sendOnvifRequest(RequestType.GetSnapshotUri, mediaXAddr);
sendOnvifRequest(RequestType.GetStreamUri, mediaXAddr);
@Override
public void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(0, 0, 70));
+ socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(20, 20, 20));
socketChannel.pipeline().addLast("HttpClientCodec", new HttpClientCodec());
socketChannel.pipeline().addLast("OnvifCodec", new OnvifCodec(getHandle()));
}
bootstrap = localBootstap;
}
if (!mainEventLoopGroup.isShuttingDown()) {
- localBootstap.connect(new InetSocketAddress(ipAddress, extractPortFromUrl(xAddr)))
- .addListener(new ChannelFutureListener() {
+ 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 when using xAddr:{}.", xAddr);
- if (isConnected) {
- disconnect();
- }
+ @Override
+ public void operationComplete(@Nullable ChannelFuture future) {
+ if (future == null) {
+ return;
+ }
+ if (future.isSuccess()) {
+ connectError = false;
+ Channel ch = future.channel();
+ ch.writeAndFlush(request);
+ } else { // an error occured
+ if (future.isDone() && !future.isCancelled()) {
+ Throwable cause = future.cause();
+ logger.trace("connect failed - cause {}", cause.getMessage());
+ if (cause instanceof ConnectTimeoutException) {
+ logger.debug("Camera is not reachable on IP {}", ipAddress);
+ connectError = true;
+ } else if ((cause instanceof ConnectException)
+ && cause.getMessage().contains("Connection refused")) {
+ logger.debug("Camera ONVIF port {} is refused.", onvifPort);
+ refusedError = true;
}
}
- });
+ if (isConnected) {
+ disconnect();
+ }
+ }
+ }
+ });
} else {
logger.debug("ONVIF message not sent as connection is shutting down");
}
}
}
+ public boolean isConnectError() {
+ return connectError;
+ }
+
+ public boolean isRefusedError() {
+ return refusedError;
+ }
+
public boolean isConnected() {
connecting.lock();
try {
}
}
+ public void setIsConnected(boolean isConnected) {
+ connecting.lock();
+ try {
+ this.isConnected = isConnected;
+ this.connectError = false;
+ this.refusedError = false;
+ } finally {
+ connecting.unlock();
+ }
+ }
+
private void cleanup() {
if (!isConnected && !mainEventLoopGroup.isShuttingDown()) {
try {
public void disconnect() {
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()) {
+ if (isConnected && usingEvents && !mainEventLoopGroup.isShuttingDown()) {
+ // Only makes sense to send if connected
// Some cameras may continue to send events even when they can't reach a server.
sendOnvifRequest(RequestType.Unsubscribe, subscriptionXAddr);
}
} else {
cleanup();
}
+
+ isConnected = false;// isConnected is not thread safe, connecting.lock() used as fix.
} finally {
connecting.unlock();
}