[ipcamera] Improve ONVIF discovery and bug fixes. (#9199)
* Fix Offline detection and Improve discovery. * Motion options bug fix. * Message content bug fix. * Fix all handlers to process all chunks as one. * Remove password from FFmpeg command log. Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
@@ -61,10 +61,7 @@ public class AmcrestHandler extends ChannelDuplexHandler {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String content = msg.toString();
|
String content = msg.toString();
|
||||||
|
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
if (!content.isEmpty()) {
|
|
||||||
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
|
||||||
}
|
|
||||||
if (content.contains("Error: No Events")) {
|
if (content.contains("Error: No Events")) {
|
||||||
if ("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion".equals(requestUrl)) {
|
if ("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion".equals(requestUrl)) {
|
||||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||||
|
|||||||
@@ -55,11 +55,9 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
|||||||
if (msg == null || ctx == null) {
|
if (msg == null || ctx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String content = msg.toString();
|
|
||||||
try {
|
try {
|
||||||
if (!content.isEmpty()) {
|
String content = msg.toString();
|
||||||
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
}
|
|
||||||
// determine if the motion detection is turned on or off.
|
// determine if the motion detection is turned on or off.
|
||||||
if (content.contains("table.MotionDetect[0].Enable=true")) {
|
if (content.contains("table.MotionDetect[0].Enable=true")) {
|
||||||
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
|
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
|
||||||
|
|||||||
@@ -51,13 +51,9 @@ public class DoorBirdHandler extends ChannelDuplexHandler {
|
|||||||
if (msg == null || ctx == null) {
|
if (msg == null || ctx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String content = msg.toString();
|
|
||||||
try {
|
try {
|
||||||
if (!content.isEmpty()) {
|
String content = msg.toString();
|
||||||
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (content.contains("doorbell:H")) {
|
if (content.contains("doorbell:H")) {
|
||||||
ipCameraHandler.setChannelState(CHANNEL_DOORBELL, OnOffType.ON);
|
ipCameraHandler.setChannelState(CHANNEL_DOORBELL, OnOffType.ON);
|
||||||
}
|
}
|
||||||
@@ -70,7 +66,6 @@ public class DoorBirdHandler extends ChannelDuplexHandler {
|
|||||||
if (content.contains("motionsensor:H")) {
|
if (content.contains("motionsensor:H")) {
|
||||||
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
|
ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
ReferenceCountUtil.release(msg);
|
ReferenceCountUtil.release(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ public class Ffmpeg {
|
|||||||
private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread();
|
private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread();
|
||||||
private int keepAlive = 8;
|
private int keepAlive = 8;
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
|
private String password;
|
||||||
|
|
||||||
public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
|
public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
|
||||||
String input, String outArguments, String output, String username, String password) {
|
String input, String outArguments, String output, String username, String password) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
this.password = password;
|
||||||
ipCameraHandler = handle;
|
ipCameraHandler = handle;
|
||||||
String altInput = input;
|
String altInput = input;
|
||||||
// Input can be snapshots not just rtsp or http
|
// Input can be snapshots not just rtsp or http
|
||||||
@@ -169,7 +171,7 @@ public class Ffmpeg {
|
|||||||
public void startConverting() {
|
public void startConverting() {
|
||||||
if (!ipCameraFfmpegThread.isAlive()) {
|
if (!ipCameraFfmpegThread.isAlive()) {
|
||||||
ipCameraFfmpegThread = new IpCameraFfmpegThread();
|
ipCameraFfmpegThread = new IpCameraFfmpegThread();
|
||||||
logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand);
|
logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand.replaceAll(password, "********"));
|
||||||
ipCameraFfmpegThread.start();
|
ipCameraFfmpegThread.start();
|
||||||
running = true;
|
running = true;
|
||||||
if (format.equals(FFmpegFormat.HLS)) {
|
if (format.equals(FFmpegFormat.HLS)) {
|
||||||
|
|||||||
@@ -57,14 +57,9 @@ public class FoscamHandler extends ChannelDuplexHandler {
|
|||||||
if (msg == null || ctx == null) {
|
if (msg == null || ctx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String content = msg.toString();
|
|
||||||
try {
|
try {
|
||||||
if (!content.isEmpty()) {
|
String content = msg.toString();
|
||||||
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////// Motion Alarm //////////////
|
////////////// Motion Alarm //////////////
|
||||||
if (content.contains("<motionDetectAlarm>")) {
|
if (content.contains("<motionDetectAlarm>")) {
|
||||||
if (content.contains("<motionDetectAlarm>0</motionDetectAlarm>")) {
|
if (content.contains("<motionDetectAlarm>0</motionDetectAlarm>")) {
|
||||||
@@ -115,7 +110,6 @@ public class FoscamHandler extends ChannelDuplexHandler {
|
|||||||
ctx.close();
|
ctx.close();
|
||||||
ipCameraHandler.logger.debug("End of FOSCAM handler reached, so closing the channel to the camera now");
|
ipCameraHandler.logger.debug("End of FOSCAM handler reached, so closing the channel to the camera now");
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
ReferenceCountUtil.release(msg);
|
ReferenceCountUtil.release(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,15 +67,10 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
|||||||
if (msg == null || ctx == null) {
|
if (msg == null || ctx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String content = "";
|
|
||||||
int debounce = 3;
|
|
||||||
try {
|
try {
|
||||||
content = msg.toString();
|
int debounce = 3;
|
||||||
if (content.isEmpty()) {
|
String content = msg.toString();
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.trace("HTTP Result back from camera is \t:{}:", content);
|
logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
|
|
||||||
if (content.contains("--boundary")) {// Alarm checking goes in here//
|
if (content.contains("--boundary")) {// Alarm checking goes in here//
|
||||||
if (content.contains("<EventNotificationAlert version=\"")) {
|
if (content.contains("<EventNotificationAlert version=\"")) {
|
||||||
if (content.contains("hannelID>" + nvrChannel + "</")) {// some camera use c or <dynChannelID>
|
if (content.contains("hannelID>" + nvrChannel + "</")) {// some camera use c or <dynChannelID>
|
||||||
@@ -114,7 +109,8 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
|||||||
countDown();
|
countDown();
|
||||||
countDown();
|
countDown();
|
||||||
}
|
}
|
||||||
} else if (content.contains("<channelID>0</channelID>")) {// NVR uses channel 0 to say all channels
|
} else if (content.contains("<channelID>0</channelID>")) {// NVR uses channel 0 to say all
|
||||||
|
// channels
|
||||||
if (content.contains("<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
|
if (content.contains("<eventType>videoloss</eventType>\r\n<eventState>inactive</eventState>")) {
|
||||||
if (vmdCount > 1) {
|
if (vmdCount > 1) {
|
||||||
vmdCount = 1;
|
vmdCount = 1;
|
||||||
|
|||||||
@@ -58,13 +58,10 @@ public class InstarHandler extends ChannelDuplexHandler {
|
|||||||
if (msg == null || ctx == null) {
|
if (msg == null || ctx == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String content = "";
|
|
||||||
String value1 = "";
|
|
||||||
try {
|
try {
|
||||||
content = msg.toString();
|
String value1 = "";
|
||||||
if (content.isEmpty()) {
|
String content = msg.toString();
|
||||||
return;
|
ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
|
||||||
}
|
|
||||||
switch (requestUrl) {
|
switch (requestUrl) {
|
||||||
case "/param.cgi?cmd=getinfrared":
|
case "/param.cgi?cmd=getinfrared":
|
||||||
if (content.contains("var infraredstat=\"auto")) {
|
if (content.contains("var infraredstat=\"auto")) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class IpCameraDiscoveryService extends AbstractDiscoveryService {
|
|||||||
removeOlderResults(getTimestampOfLastScan());
|
removeOlderResults(getTimestampOfLastScan());
|
||||||
OnvifDiscovery onvifDiscovery = new OnvifDiscovery(this);
|
OnvifDiscovery onvifDiscovery = new OnvifDiscovery(this);
|
||||||
try {
|
try {
|
||||||
onvifDiscovery.discoverCameras(3702);// WS discovery
|
onvifDiscovery.discoverCameras();
|
||||||
} catch (UnknownHostException | InterruptedException e) {
|
} catch (UnknownHostException | InterruptedException e) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"IpCamera Discovery has an issue discovering the network settings to find cameras with. Try setting up the camera manually.");
|
"IpCamera Discovery has an issue discovering the network settings to find cameras with. Try setting up the camera manually.");
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
if (contentType.contains("multipart")) {
|
if (contentType.contains("multipart")) {
|
||||||
closeConnection = false;
|
closeConnection = false;
|
||||||
if (mjpegUri.contains(requestUrl)) {
|
if (mjpegUri.equals(requestUrl)) {
|
||||||
if (msg instanceof HttpMessage) {
|
if (msg instanceof HttpMessage) {
|
||||||
// very start of stream only
|
// very start of stream only
|
||||||
ReferenceCountUtil.retain(msg, 1);
|
ReferenceCountUtil.retain(msg, 1);
|
||||||
@@ -268,13 +268,13 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg instanceof HttpContent) {
|
if (msg instanceof HttpContent) {
|
||||||
if (mjpegUri.contains(requestUrl)) {
|
if (mjpegUri.equals(requestUrl)) {
|
||||||
// multiple MJPEG stream packets come back as this.
|
// multiple MJPEG stream packets come back as this.
|
||||||
ReferenceCountUtil.retain(msg, 1);
|
ReferenceCountUtil.retain(msg, 1);
|
||||||
streamToGroup(msg, mjpegChannelGroup, true);
|
streamToGroup(msg, mjpegChannelGroup, true);
|
||||||
} else {
|
} else {
|
||||||
HttpContent content = (HttpContent) msg;
|
HttpContent content = (HttpContent) msg;
|
||||||
// Found some cameras uses Content-Type: image/jpg instead of image/jpeg
|
// Found some cameras use Content-Type: image/jpg instead of image/jpeg
|
||||||
if (contentType.contains("image/jp")) {
|
if (contentType.contains("image/jp")) {
|
||||||
for (int i = 0; i < content.content().capacity(); i++) {
|
for (int i = 0; i < content.content().capacity(); i++) {
|
||||||
incomingJpeg[bytesAlreadyRecieved++] = content.content().getByte(i);
|
incomingJpeg[bytesAlreadyRecieved++] = content.content().getByte(i);
|
||||||
@@ -304,8 +304,8 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
super.channelRead(ctx, reply);
|
super.channelRead(ctx, reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// HIKVISION alertStream never has a LastHttpContent as it always stays open//
|
// Alarm Streams never have a LastHttpContent as they always stay open//
|
||||||
if (contentType.contains("multipart")) {
|
else if (contentType.contains("multipart")) {
|
||||||
if (bytesAlreadyRecieved != 0) {
|
if (bytesAlreadyRecieved != 0) {
|
||||||
reply = incomingMessage;
|
reply = incomingMessage;
|
||||||
incomingMessage = "";
|
incomingMessage = "";
|
||||||
@@ -316,13 +316,14 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
// Foscam needs this as will other cameras with chunks//
|
// Foscam needs this as will other cameras with chunks//
|
||||||
if (isChunked && bytesAlreadyRecieved != 0) {
|
if (isChunked && bytesAlreadyRecieved != 0) {
|
||||||
|
logger.debug("Reply is chunked.");
|
||||||
reply = incomingMessage;
|
reply = incomingMessage;
|
||||||
super.channelRead(ctx, reply);
|
super.channelRead(ctx, reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // msg is not HttpContent
|
} else { // msg is not HttpContent
|
||||||
// Foscam and Amcrest cameras need this
|
// Foscam cameras need this
|
||||||
if (!contentType.contains("image/jp") && bytesAlreadyRecieved != 0) {
|
if (!contentType.contains("image/jp") && bytesAlreadyRecieved != 0) {
|
||||||
reply = incomingMessage;
|
reply = incomingMessage;
|
||||||
logger.debug("Packet back from camera is {}", incomingMessage);
|
logger.debug("Packet back from camera is {}", incomingMessage);
|
||||||
@@ -982,7 +983,6 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String input = (cameraConfig.getAlarmInputUrl().isEmpty()) ? rtspUri : cameraConfig.getAlarmInputUrl();
|
String input = (cameraConfig.getAlarmInputUrl().isEmpty()) ? rtspUri : cameraConfig.getAlarmInputUrl();
|
||||||
String outputOptions = "-f null -";
|
|
||||||
String filterOptions = "";
|
String filterOptions = "";
|
||||||
if (!audioAlarmEnabled) {
|
if (!audioAlarmEnabled) {
|
||||||
filterOptions = "-an";
|
filterOptions = "-an";
|
||||||
@@ -991,16 +991,22 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
if (!motionAlarmEnabled && !ffmpegSnapshotGeneration) {
|
if (!motionAlarmEnabled && !ffmpegSnapshotGeneration) {
|
||||||
filterOptions = filterOptions.concat(" -vn");
|
filterOptions = filterOptions.concat(" -vn");
|
||||||
|
} else if (motionAlarmEnabled && !cameraConfig.getMotionOptions().isEmpty()) {
|
||||||
|
String usersMotionOptions = cameraConfig.getMotionOptions();
|
||||||
|
if (usersMotionOptions.startsWith("-")) {
|
||||||
|
// Need to put the users custom options first in the chain before the motion is detected
|
||||||
|
filterOptions += " " + usersMotionOptions + ",select='gte(scene," + motionThreshold
|
||||||
|
+ ")',metadata=print";
|
||||||
|
} else {
|
||||||
|
filterOptions = filterOptions + " " + usersMotionOptions + " -vf select='gte(scene,"
|
||||||
|
+ motionThreshold + ")',metadata=print";
|
||||||
|
}
|
||||||
} else if (motionAlarmEnabled) {
|
} else if (motionAlarmEnabled) {
|
||||||
filterOptions = filterOptions
|
filterOptions = filterOptions
|
||||||
.concat(" -vf select='gte(scene," + motionThreshold + ")',metadata=print");
|
.concat(" -vf select='gte(scene," + motionThreshold + ")',metadata=print");
|
||||||
}
|
}
|
||||||
if (!cameraConfig.getUser().isEmpty()) {
|
|
||||||
filterOptions += " ";// add space as the Framework does not allow spaces at start of config.
|
|
||||||
}
|
|
||||||
ffmpegRtspHelper = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, input,
|
ffmpegRtspHelper = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, input,
|
||||||
filterOptions + cameraConfig.getMotionOptions(), outputOptions, cameraConfig.getUser(),
|
filterOptions, "-f null -", cameraConfig.getUser(), cameraConfig.getPassword());
|
||||||
cameraConfig.getPassword());
|
|
||||||
localAlarms = ffmpegRtspHelper;
|
localAlarms = ffmpegRtspHelper;
|
||||||
if (localAlarms != null) {
|
if (localAlarms != null) {
|
||||||
localAlarms.startConverting();
|
localAlarms.startConverting();
|
||||||
@@ -1484,7 +1490,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
boolean streamIsStopped(String url) {
|
boolean streamIsStopped(String url) {
|
||||||
ChannelTracking channelTracking = channelTrackingMap.get(url);
|
ChannelTracking channelTracking = channelTrackingMap.get(url);
|
||||||
if (channelTracking != null) {
|
if (channelTracking != null) {
|
||||||
if (channelTracking.getChannel().isOpen()) {
|
if (channelTracking.getChannel().isActive()) {
|
||||||
return false; // stream is running.
|
return false; // stream is running.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1534,20 +1540,21 @@ public class IpCameraHandler extends BaseThingHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runs every 8 seconds due to mjpeg streams not staying open unless they update this often.
|
/**
|
||||||
|
* {@link pollCameraRunnable} Polls every 8 seconds, to check camera is still ONLINE and keep mjpeg and alarm
|
||||||
|
* streams open and more.
|
||||||
|
*
|
||||||
|
*/
|
||||||
void pollCameraRunnable() {
|
void pollCameraRunnable() {
|
||||||
// Snapshot should be first to keep consistent time between shots
|
// Snapshot should be first to keep consistent time between shots
|
||||||
if (!snapshotUri.isEmpty()) {
|
|
||||||
if (updateImageChannel) {
|
|
||||||
sendHttpGET(snapshotUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (streamingAutoFps) {
|
if (streamingAutoFps) {
|
||||||
updateAutoFps = true;
|
updateAutoFps = true;
|
||||||
if (!snapshotPolling && !ffmpegSnapshotGeneration) {
|
if (!snapshotPolling && !ffmpegSnapshotGeneration) {
|
||||||
// Dont need to poll if creating from RTSP stream with FFmpeg or we are polling at full rate already.
|
// Dont need to poll if creating from RTSP stream with FFmpeg or we are polling at full rate already.
|
||||||
sendHttpGET(snapshotUri);
|
sendHttpGET(snapshotUri);
|
||||||
}
|
}
|
||||||
|
} else if (!snapshotUri.isEmpty() && !snapshotPolling) {// we need to check camera is still online.
|
||||||
|
sendHttpGET(snapshotUri);
|
||||||
}
|
}
|
||||||
// NOTE: Use lowPriorityRequests if get request is not needed every poll.
|
// NOTE: Use lowPriorityRequests if get request is not needed every poll.
|
||||||
if (!lowPriorityRequests.isEmpty()) {
|
if (!lowPriorityRequests.isEmpty()) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.net.UnknownHostException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -42,19 +43,21 @@ import io.netty.bootstrap.Bootstrap;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelFactory;
|
import io.netty.channel.ChannelFactory;
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.group.ChannelGroup;
|
||||||
|
import io.netty.channel.group.DefaultChannelGroup;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.DatagramChannel;
|
import io.netty.channel.socket.DatagramChannel;
|
||||||
import io.netty.channel.socket.DatagramPacket;
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
import io.netty.channel.socket.InternetProtocolFamily;
|
import io.netty.channel.socket.InternetProtocolFamily;
|
||||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OnvifDiscovery} is responsible for finding cameras that are Onvif using UDP multicast.
|
* The {@link OnvifDiscovery} is responsible for finding cameras that are ONVIF using UDP multicast.
|
||||||
*
|
*
|
||||||
* @author Matthew Skinner - Initial contribution
|
* @author Matthew Skinner - Initial contribution
|
||||||
*/
|
*/
|
||||||
@@ -69,7 +72,8 @@ public class OnvifDiscovery {
|
|||||||
this.ipCameraDiscoveryService = ipCameraDiscoveryService;
|
this.ipCameraDiscoveryService = ipCameraDiscoveryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable NetworkInterface getLocalNIF() {
|
public @Nullable List<NetworkInterface> getLocalNICs() {
|
||||||
|
List<NetworkInterface> results = new ArrayList<>(2);
|
||||||
try {
|
try {
|
||||||
for (Enumeration<NetworkInterface> enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
|
for (Enumeration<NetworkInterface> enumNetworks = NetworkInterface.getNetworkInterfaces(); enumNetworks
|
||||||
.hasMoreElements();) {
|
.hasMoreElements();) {
|
||||||
@@ -79,13 +83,13 @@ public class OnvifDiscovery {
|
|||||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
InetAddress inetAddress = enumIpAddr.nextElement();
|
||||||
if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().length() < 18
|
if (!inetAddress.isLoopbackAddress() && inetAddress.getHostAddress().toString().length() < 18
|
||||||
&& inetAddress.isSiteLocalAddress()) {
|
&& inetAddress.isSiteLocalAddress()) {
|
||||||
return networkInterface;
|
results.add(networkInterface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SocketException ex) {
|
} catch (SocketException ex) {
|
||||||
}
|
}
|
||||||
return null;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
void searchReply(String url, String xml) {
|
void searchReply(String url, String xml) {
|
||||||
@@ -180,23 +184,21 @@ public class OnvifDiscovery {
|
|||||||
return brand;
|
return brand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void discoverCameras(int port) throws UnknownHostException, InterruptedException {
|
private DatagramPacket wsDiscovery() throws UnknownHostException {
|
||||||
String uuid = UUID.randomUUID().toString();
|
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"><e:Header><w:MessageID>uuid:"
|
||||||
String xml = "";
|
+ UUID.randomUUID()
|
||||||
|
+ "</w:MessageID><w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To><w:Action a:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action></e:Header><e:Body><d:Probe><d:Types xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dp0=\"http://www.onvif.org/ver10/network/wsdl\">dp0:NetworkVideoTransmitter</d:Types></d:Probe></e:Body></e:Envelope>";
|
||||||
if (port == 3702) {
|
|
||||||
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"><e:Header><w:MessageID>uuid:"
|
|
||||||
+ uuid
|
|
||||||
+ "</w:MessageID><w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To><w:Action a:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action></e:Header><e:Body><d:Probe><d:Types xmlns:dp0=\"http://www.onvif.org/ver10/network/wsdl\">dp0:NetworkVideoTransmitter</d:Types></d:Probe></e:Body></e:Envelope>";
|
|
||||||
}
|
|
||||||
ByteBuf discoveryProbeMessage = Unpooled.copiedBuffer(xml, 0, xml.length(), StandardCharsets.UTF_8);
|
ByteBuf discoveryProbeMessage = Unpooled.copiedBuffer(xml, 0, xml.length(), StandardCharsets.UTF_8);
|
||||||
InetSocketAddress localNetworkAddress = new InetSocketAddress(0);// Listen for replies on all connections.
|
return new DatagramPacket(discoveryProbeMessage,
|
||||||
InetSocketAddress multiCastAddress = new InetSocketAddress(InetAddress.getByName("239.255.255.250"), port);
|
new InetSocketAddress(InetAddress.getByName("239.255.255.250"), 3702), new InetSocketAddress(0));
|
||||||
DatagramPacket datagramPacket = new DatagramPacket(discoveryProbeMessage, multiCastAddress,
|
}
|
||||||
localNetworkAddress);
|
|
||||||
NetworkInterface networkInterface = getLocalNIF();
|
|
||||||
DatagramChannel datagramChannel;
|
|
||||||
|
|
||||||
|
public void discoverCameras() throws UnknownHostException, InterruptedException {
|
||||||
|
List<NetworkInterface> nics = getLocalNICs();
|
||||||
|
if (nics == null || nics.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NetworkInterface networkInterface = nics.get(0);
|
||||||
Bootstrap bootstrap = new Bootstrap().group(new NioEventLoopGroup())
|
Bootstrap bootstrap = new Bootstrap().group(new NioEventLoopGroup())
|
||||||
.channelFactory(new ChannelFactory<NioDatagramChannel>() {
|
.channelFactory(new ChannelFactory<NioDatagramChannel>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -213,26 +215,21 @@ public class OnvifDiscovery {
|
|||||||
}).option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_REUSEADDR, true)
|
}).option(ChannelOption.SO_BROADCAST, true).option(ChannelOption.SO_REUSEADDR, true)
|
||||||
.option(ChannelOption.IP_MULTICAST_LOOP_DISABLED, false).option(ChannelOption.SO_RCVBUF, 2048)
|
.option(ChannelOption.IP_MULTICAST_LOOP_DISABLED, false).option(ChannelOption.SO_RCVBUF, 2048)
|
||||||
.option(ChannelOption.IP_MULTICAST_TTL, 255).option(ChannelOption.IP_MULTICAST_IF, networkInterface);
|
.option(ChannelOption.IP_MULTICAST_TTL, 255).option(ChannelOption.IP_MULTICAST_IF, networkInterface);
|
||||||
|
ChannelGroup openChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||||
datagramChannel = (DatagramChannel) bootstrap.bind(localNetworkAddress).sync().channel();
|
for (NetworkInterface nic : nics) {
|
||||||
datagramChannel.joinGroup(multiCastAddress, networkInterface).sync();
|
DatagramChannel datagramChannel = (DatagramChannel) bootstrap.option(ChannelOption.IP_MULTICAST_IF, nic)
|
||||||
ChannelFuture chFuture;
|
.bind(new InetSocketAddress(0)).sync().channel();
|
||||||
if (port == 1900) {
|
datagramChannel
|
||||||
String ssdp = "M-SEARCH * HTTP/1.1\n" + "HOST: 239.255.255.250:1900\n" + "MAN: \"ssdp:discover\"\n"
|
.joinGroup(new InetSocketAddress(InetAddress.getByName("239.255.255.250"), 3702), networkInterface)
|
||||||
+ "MX: 1\n" + "ST: urn:dial-multiscreen-org:service:dial:1\n"
|
.sync();
|
||||||
+ "USER-AGENT: Microsoft Edge/83.0.478.61 Windows\n" + "\n" + "";
|
openChannels.add(datagramChannel);
|
||||||
ByteBuf ssdpProbeMessage = Unpooled.copiedBuffer(ssdp, 0, ssdp.length(), StandardCharsets.UTF_8);
|
}
|
||||||
datagramPacket = new DatagramPacket(ssdpProbeMessage, multiCastAddress, localNetworkAddress);
|
if (!openChannels.isEmpty()) {
|
||||||
chFuture = datagramChannel.writeAndFlush(datagramPacket);
|
openChannels.writeAndFlush(wsDiscovery());
|
||||||
} else {
|
TimeUnit.SECONDS.sleep(6);
|
||||||
chFuture = datagramChannel.writeAndFlush(datagramPacket);
|
openChannels.close();
|
||||||
|
processCameraReplys();
|
||||||
|
bootstrap.config().group().shutdownGracefully();
|
||||||
}
|
}
|
||||||
chFuture.awaitUninterruptibly(2000);
|
|
||||||
chFuture = datagramChannel.closeFuture();
|
|
||||||
TimeUnit.SECONDS.sleep(5);
|
|
||||||
datagramChannel.close();
|
|
||||||
chFuture.awaitUninterruptibly(6000);
|
|
||||||
processCameraReplys();
|
|
||||||
bootstrap.config().group().shutdownGracefully();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2665,7 +2665,7 @@
|
|||||||
<category>Light</category>
|
<category>Light</category>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="enablePrivacyMode">
|
<channel-type id="enablePrivacyMode" advanced="true">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Switch</item-type>
|
||||||
<label>Enable Privacy Mode</label>
|
<label>Enable Privacy Mode</label>
|
||||||
<description>Turn the Privacy Mode on and off.</description>
|
<description>Turn the Privacy Mode on and off.</description>
|
||||||
|
|||||||
Reference in New Issue
Block a user