[ipcamera] Improve ONVIF preset naming (#8948)
* Refactor to prevent endless loop. * Allow `-rtsp_transport tcp` to be over-ridden. * Display actual preset names * Allow IP to not match due to Hostname given in setup. * Fix index off by 1 * Bug fixes for HLS * Compatibility fix for GotoPreset * Improve default snapshot quality and allow FFmpeg arguments to be changed. Signed-off-by: Matthew Skinner <matt@pcmus.com> Co-authored-by: Connor Petty <mistercpp2000@gmail.com>
This commit is contained in:
parent
ae7d5715ee
commit
8b3c633b8d
bundles/org.openhab.binding.ipcamera
README.md
src/main
java/org/openhab/binding/ipcamera/internal
AmcrestHandler.javaCameraConfig.javaDahuaHandler.javaDoorBirdHandler.javaFfmpeg.javaFoscamHandler.javaHikvisionHandler.javaInstarHandler.javaIpCameraActions.javaIpCameraBindingConstants.javaIpCameraDynamicStateDescriptionProvider.javaIpCameraHandlerFactory.javaStreamServerHandler.java
handler
onvif
resources/OH-INF/thing
@ -196,6 +196,7 @@ If you do not specify any of these, the binding will use the default which shoul
|
||||
| `hlsOutOptions`| This gives you direct access to specify your own FFmpeg options to be used. Default: `-strict -2 -f lavfi -i aevalsrc=0 -acodec aac -vcodec copy -hls_flags delete_segments -hls_time 2 -hls_list_size 4` |
|
||||
| `gifOutOptions`| This gives you direct access to specify your own FFmpeg options to be used for animated GIF files. Default: `-r 2 -filter_complex scale=-2:360:flags=lanczos,setpts=0.5*PTS,split[o1][o2];[o1]palettegen[p];[o2]fifo[o3];[o3][p]paletteuse` |
|
||||
| `mjpegOptions` | Allows you to change the settings for creating a MJPEG stream from RTSP using FFmpeg. Possible reasons to change this would be to rotate or re-scale the picture from the camera, change the JPG compression for better quality or the FPS rate. |
|
||||
| `snapshotOptions` | Specify your own FFmpeg options to be used when creating snapshots from RTSP. Default: `-an -vsync vfr -q:v 2 -update 1` |
|
||||
| `motionOptions` | This gives access to the FFmpeg parameters for detecting motion alarms from a RTSP stream. One possible use for this is to use the CROP feature to ignore any trees that move in the wind or a timecode stamp. Crop will not remove the trees from your picture, it only ignores the movement of the tree. |
|
||||
| `gifPreroll`| Store this many snapshots from BEFORE you trigger a GIF creation. Default: `0` will not use snapshots and will instead use a realtime stream from the ffmpegInput URL |
|
||||
| `ipWhitelist`| Enter any IPs inside brackets that you wish to allow to access the video stream. `DISABLE` the default value will turn this feature off. Example: `ipWhitelist="(127.0.0.1)(192.168.0.99)"` |
|
||||
|
@ -19,7 +19,6 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -206,19 +205,6 @@ public class AmcrestHandler extends ChannelDuplexHandler {
|
||||
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=0");
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
|
||||
ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ public class CameraConfig {
|
||||
private String gifOutOptions = "";
|
||||
private String mp4OutOptions = "";
|
||||
private String mjpegOptions = "";
|
||||
private String snapshotOptions = "";
|
||||
private String motionOptions = "";
|
||||
private boolean ptzContinuous;
|
||||
private int gifPreroll;
|
||||
@ -61,6 +62,10 @@ public class CameraConfig {
|
||||
return mjpegOptions;
|
||||
}
|
||||
|
||||
public String getSnapshotOptions() {
|
||||
return snapshotOptions;
|
||||
}
|
||||
|
||||
public String getMotionOptions() {
|
||||
return motionOptions;
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -252,18 +251,6 @@ public class DahuaHandler extends ChannelDuplexHandler {
|
||||
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=0");
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString()) / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,7 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
@ -99,19 +97,6 @@ public class DoorBirdHandler extends ChannelDuplexHandler {
|
||||
ipCameraHandler.sendHttpGET("/bha-api/light-on.cgi");
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
|
||||
ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
29
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java
29
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java
@ -75,23 +75,20 @@ public class Ffmpeg {
|
||||
commandArrayList.add(0, ffmpegLocation);
|
||||
}
|
||||
|
||||
public void setKeepAlive(int seconds) {
|
||||
if (seconds == -1) {
|
||||
keepAlive = -1;
|
||||
} else {// We now poll every 8 seconds due to mjpeg stream requirement.
|
||||
keepAlive = 8; // 64 seconds approx.
|
||||
public void setKeepAlive(int numberOfEightSeconds) {
|
||||
// We poll every 8 seconds due to mjpeg stream requirement.
|
||||
if (keepAlive == -1 && numberOfEightSeconds > 1) {
|
||||
return;// When set to -1 this will not auto turn off stream.
|
||||
}
|
||||
keepAlive = numberOfEightSeconds;
|
||||
}
|
||||
|
||||
public void checkKeepAlive() {
|
||||
if (keepAlive <= -1) {
|
||||
return;
|
||||
} else if (keepAlive == 0) {
|
||||
} else if (--keepAlive == 0) {
|
||||
stopConverting();
|
||||
} else {
|
||||
keepAlive--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private class IpCameraFfmpegThread extends Thread {
|
||||
@ -119,8 +116,9 @@ public class Ffmpeg {
|
||||
public void run() {
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(commandArrayList.toArray(new String[commandArrayList.size()]));
|
||||
if (process != null) {
|
||||
InputStream errorStream = process.getErrorStream();
|
||||
Process localProcess = process;
|
||||
if (localProcess != null) {
|
||||
InputStream errorStream = localProcess.getErrorStream();
|
||||
InputStreamReader errorStreamReader = new InputStreamReader(errorStream);
|
||||
BufferedReader bufferedReader = new BufferedReader(errorStreamReader);
|
||||
String line = null;
|
||||
@ -189,10 +187,11 @@ public class Ffmpeg {
|
||||
|
||||
public void stopConverting() {
|
||||
if (ipCameraFfmpegThread.isAlive()) {
|
||||
logger.debug("Stopping ffmpeg {} now", format);
|
||||
running = false;
|
||||
if (process != null) {
|
||||
process.destroyForcibly();
|
||||
logger.debug("Stopping ffmpeg {} now when keepalive is:{}", format, keepAlive);
|
||||
Process localProcess = process;
|
||||
if (localProcess != null) {
|
||||
localProcess.destroyForcibly();
|
||||
running = false;
|
||||
}
|
||||
if (format.equals(FFmpegFormat.HLS)) {
|
||||
if (keepAlive == -1) {
|
||||
|
@ -19,7 +19,6 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
@ -215,19 +214,6 @@ public class FoscamHandler extends ChannelDuplexHandler {
|
||||
+ username + "&pwd=" + password);
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
|
||||
ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,7 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -441,19 +439,6 @@ public class HikvisionHandler extends ChannelDuplexHandler {
|
||||
"<IOPortData version=\"1.0\" xmlns=\"http://www.hikvision.com/ver10/XMLSchema\">\r\n <outputState>low</outputState>\r\n</IOPortData>\r\n");
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
|
||||
ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,7 @@ import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
@ -200,19 +198,6 @@ public class InstarHandler extends ChannelDuplexHandler {
|
||||
ipCameraHandler.sendHttpGET("/param.cgi?cmd=setioattr&-io_enable=0");
|
||||
}
|
||||
return;
|
||||
case CHANNEL_FFMPEG_MOTION_CONTROL:
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
ipCameraHandler.motionAlarmEnabled = false;
|
||||
ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
} else {
|
||||
ipCameraHandler.motionAlarmEnabled = true;
|
||||
ipCameraHandler.motionThreshold = Double.valueOf(command.toString());
|
||||
ipCameraHandler.motionThreshold = ipCameraHandler.motionThreshold / 10000;
|
||||
}
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,15 +45,14 @@ public class IpCameraActions implements ThingActions {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@RuleAction(label = "record an MP4", description = "Record MP4 to a set filename if given, or if filename is null to ipcamera.mp4")
|
||||
@RuleAction(label = "record a MP4", description = "Record MP4 to a set filename if given, or if filename is null to ipcamera.mp4")
|
||||
public void recordMP4(
|
||||
@ActionInput(name = "filename", label = "Filename", description = "Name that the recording will have once created, don't include the .mp4.") @Nullable String filename,
|
||||
@ActionInput(name = "secondsToRecord", label = "Seconds to Record", description = "Enter a number of how many seconds to record.") int secondsToRecord) {
|
||||
logger.debug("Recording {}.mp4 for {} seconds.", filename, secondsToRecord);
|
||||
if (filename == null && handler != null) {
|
||||
handler.recordMp4("ipcamera", secondsToRecord);
|
||||
} else if (handler != null && filename != null) {
|
||||
handler.recordMp4(filename, secondsToRecord);
|
||||
IpCameraHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
localHandler.recordMp4(filename != null ? filename : "ipcamera", secondsToRecord);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,10 +65,9 @@ public class IpCameraActions implements ThingActions {
|
||||
@ActionInput(name = "filename", label = "Filename", description = "Name that the recording will have once created, don't include the .mp4.") @Nullable String filename,
|
||||
@ActionInput(name = "secondsToRecord", label = "Seconds to Record", description = "Enter a number of how many seconds to record.") int secondsToRecord) {
|
||||
logger.debug("Recording {}.gif for {} seconds.", filename, secondsToRecord);
|
||||
if (filename == null && handler != null) {
|
||||
handler.recordGif("ipcamera", secondsToRecord);
|
||||
} else if (handler != null && filename != null) {
|
||||
handler.recordGif(filename, secondsToRecord);
|
||||
IpCameraHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
localHandler.recordGif(filename != null ? filename : "ipcamera", secondsToRecord);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,37 +71,7 @@ public class IpCameraBindingConstants {
|
||||
|
||||
// List of all Thing Config items
|
||||
public static final String CONFIG_IPADDRESS = "ipAddress";
|
||||
public static final String CONFIG_PORT = "port";
|
||||
public static final String CONFIG_ONVIF_PORT = "onvifPort";
|
||||
public static final String CONFIG_SERVER_PORT = "serverPort";
|
||||
public static final String CONFIG_USERNAME = "username";
|
||||
public static final String CONFIG_PASSWORD = "password";
|
||||
public static final String CONFIG_ONVIF_PROFILE_NUMBER = "onvifMediaProfile";
|
||||
public static final String CONFIG_POLL_TIME = "pollTime";
|
||||
public static final String CONFIG_FFMPEG_INPUT = "ffmpegInput";
|
||||
public static final String CONFIG_SNAPSHOT_URL_OVERRIDE = "snapshotUrl";
|
||||
public static final String CONFIG_MJPEG_URL = "mjpegUrl";
|
||||
public static final String CONFIG_FFMPEG_MOTION_INPUT = "alarmInputUrl";
|
||||
public static final String CONFIG_MOTION_URL_OVERRIDE = "customMotionAlarmUrl";
|
||||
public static final String CONFIG_AUDIO_URL_OVERRIDE = "customAudioAlarmUrl";
|
||||
public static final String CONFIG_IMAGE_UPDATE_WHEN = "updateImageWhen";
|
||||
public static final String CONFIG_NVR_CHANNEL = "nvrChannel";
|
||||
public static final String CONFIG_IP_WHITELIST = "ipWhitelist";
|
||||
public static final String CONFIG_FFMPEG_LOCATION = "ffmpegLocation";
|
||||
public static final String CONFIG_FFMPEG_OUTPUT = "ffmpegOutput";
|
||||
public static final String CONFIG_FFMPEG_HLS_OUT_ARGUMENTS = "hlsOutOptions";
|
||||
public static final String CONFIG_FFMPEG_GIF_OUT_ARGUMENTS = "gifOutOptions";
|
||||
public static final String CONFIG_FFMPEG_MP4_OUT_ARGUMENTS = "mp4OutOptions";
|
||||
public static final String CONFIG_FFMPEG_MJPEG_ARGUMENTS = "mjpegOptions";
|
||||
public static final String CONFIG_FFMPEG_MOTION_ARGUMENTS = "motionOptions";
|
||||
public static final String CONFIG_PTZ_CONTINUOUS = "ptzContinuous";
|
||||
public static final String CONFIG_GIF_PREROLL = "gifPreroll";
|
||||
// group thing configs
|
||||
public static final String CONFIG_FIRST_CAM = "firstCamera";
|
||||
public static final String CONFIG_SECOND_CAM = "secondCamera";
|
||||
public static final String CONFIG_THIRD_CAM = "thirdCamera";
|
||||
public static final String CONFIG_FORTH_CAM = "forthCamera";
|
||||
public static final String CONFIG_MOTION_CHANGES_ORDER = "motionChangesOrder";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_POLL_IMAGE = "pollImage";
|
||||
|
37
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDynamicStateDescriptionProvider.java
Normal file
37
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDynamicStateDescriptionProvider.java
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.ipcamera.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link IpCameraDynamicStateDescriptionProvider} Allows the dynamic updating of the ONVIF
|
||||
* preset names and tokens that can change at any time.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, IpCameraDynamicStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class IpCameraDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
@Activate
|
||||
public IpCameraDynamicStateDescriptionProvider(
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -40,10 +40,13 @@ import org.osgi.service.component.annotations.Reference;
|
||||
public class IpCameraHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final @Nullable String openhabIpAddress;
|
||||
private final GroupTracker groupTracker = new GroupTracker();
|
||||
private final IpCameraDynamicStateDescriptionProvider stateDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public IpCameraHandlerFactory(final @Reference NetworkAddressService networkAddressService) {
|
||||
public IpCameraHandlerFactory(final @Reference NetworkAddressService networkAddressService,
|
||||
final @Reference IpCameraDynamicStateDescriptionProvider stateDescriptionProvider) {
|
||||
openhabIpAddress = networkAddressService.getPrimaryIpv4HostAddress();
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -59,7 +62,7 @@ public class IpCameraHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new IpCameraHandler(thing, openhabIpAddress, groupTracker);
|
||||
return new IpCameraHandler(thing, openhabIpAddress, groupTracker, stateDescriptionProvider);
|
||||
} else if (GROUP_SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
return new IpCameraGroupHandler(thing, openhabIpAddress, groupTracker);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -96,20 +97,19 @@ public class StreamServerHandler extends ChannelInboundHandlerAdapter {
|
||||
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(httpRequest.uri());
|
||||
switch (queryStringDecoder.path()) {
|
||||
case "/ipcamera.m3u8":
|
||||
if (ipCameraHandler.ffmpegHLS != null) {
|
||||
if (!ipCameraHandler.ffmpegHLS.getIsAlive()) {
|
||||
if (ipCameraHandler.ffmpegHLS != null) {
|
||||
ipCameraHandler.ffmpegHLS.startConverting();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ffmpeg localFfmpeg = ipCameraHandler.ffmpegHLS;
|
||||
if (localFfmpeg == null) {
|
||||
ipCameraHandler.setupFfmpegFormat(FFmpegFormat.HLS);
|
||||
} else if (!localFfmpeg.getIsAlive()) {
|
||||
localFfmpeg.startConverting();
|
||||
} else {
|
||||
localFfmpeg.setKeepAlive(8);
|
||||
sendFile(ctx, httpRequest.uri(), "application/x-mpegurl");
|
||||
return;
|
||||
}
|
||||
if (ipCameraHandler.ffmpegHLS != null) {
|
||||
ipCameraHandler.ffmpegHLS.setKeepAlive(8);
|
||||
}
|
||||
// Allow files to be created, or you get old m3u8 from the last time this ran.
|
||||
TimeUnit.MILLISECONDS.sleep(4500);
|
||||
sendFile(ctx, httpRequest.uri(), "application/x-mpegurl");
|
||||
ctx.close();
|
||||
return;
|
||||
case "/ipcamera.mpd":
|
||||
sendFile(ctx, httpRequest.uri(), "application/dash+xml");
|
||||
|
@ -23,6 +23,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -361,9 +362,9 @@ public class IpCameraGroupHandler extends BaseThingHandler {
|
||||
public void dispose() {
|
||||
startStreamServer(false);
|
||||
groupTracker.listOfGroupHandlers.remove(this);
|
||||
if (pollCameraGroupJob != null) {
|
||||
pollCameraGroupJob.cancel(true);
|
||||
pollCameraGroupJob = null;
|
||||
Future<?> future = pollCameraGroupJob;
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
cameraOrder.clear();
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -53,6 +54,7 @@ import org.openhab.binding.ipcamera.internal.HttpOnlyHandler;
|
||||
import org.openhab.binding.ipcamera.internal.InstarHandler;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraActions;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
|
||||
import org.openhab.binding.ipcamera.internal.IpCameraDynamicStateDescriptionProvider;
|
||||
import org.openhab.binding.ipcamera.internal.MyNettyAuthHandler;
|
||||
import org.openhab.binding.ipcamera.internal.StreamServerHandler;
|
||||
import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection;
|
||||
@ -125,9 +127,10 @@ import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
@NonNullByDefault
|
||||
public class IpCameraHandler extends BaseThingHandler {
|
||||
public final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
public final IpCameraDynamicStateDescriptionProvider stateDescriptionProvider;
|
||||
private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4);
|
||||
private GroupTracker groupTracker;
|
||||
public CameraConfig cameraConfig;
|
||||
public CameraConfig cameraConfig = new CameraConfig();
|
||||
|
||||
// ChannelGroup is thread safe
|
||||
public final ChannelGroup mjpegChannelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
@ -391,9 +394,10 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public IpCameraHandler(Thing thing, @Nullable String ipAddress, GroupTracker groupTracker) {
|
||||
public IpCameraHandler(Thing thing, @Nullable String ipAddress, GroupTracker groupTracker,
|
||||
IpCameraDynamicStateDescriptionProvider stateDescriptionProvider) {
|
||||
super(thing);
|
||||
cameraConfig = getConfigAs(CameraConfig.class);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
if (ipAddress != null) {
|
||||
hostIp = ipAddress;
|
||||
} else {
|
||||
@ -759,16 +763,13 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
mjpegChannelGroup.remove(ctx.channel());
|
||||
if (mjpegChannelGroup.isEmpty()) {
|
||||
logger.debug("All ipcamera.mjpeg streams have stopped.");
|
||||
if (mjpegUri.equals("ffmpeg")) {
|
||||
if (ffmpegMjpeg != null) {
|
||||
ffmpegMjpeg.stopConverting();
|
||||
if (mjpegUri.equals("ffmpeg") || mjpegUri.isEmpty()) {
|
||||
Ffmpeg localMjpeg = ffmpegMjpeg;
|
||||
if (localMjpeg != null) {
|
||||
localMjpeg.stopConverting();
|
||||
}
|
||||
} else if (!mjpegUri.isEmpty()) {
|
||||
closeChannel(getTinyUrl(mjpegUri));
|
||||
} else {
|
||||
if (ffmpegMjpeg != null) {
|
||||
ffmpegMjpeg.stopConverting();
|
||||
}
|
||||
closeChannel(getTinyUrl(mjpegUri));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -887,8 +888,6 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
if (rtspUri.toLowerCase().contains("rtsp")) {
|
||||
if (inputOptions.isEmpty()) {
|
||||
inputOptions = "-rtsp_transport tcp";
|
||||
} else {
|
||||
inputOptions = inputOptions + " -rtsp_transport tcp";
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,8 +908,9 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
cameraConfig.getPassword());
|
||||
}
|
||||
}
|
||||
if (ffmpegHLS != null) {
|
||||
ffmpegHLS.startConverting();
|
||||
Ffmpeg localHLS = ffmpegHLS;
|
||||
if (localHLS != null) {
|
||||
localHLS.startConverting();
|
||||
}
|
||||
break;
|
||||
case GIF:
|
||||
@ -934,8 +934,9 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
if (cameraConfig.getGifPreroll() > 0) {
|
||||
storeSnapshots();
|
||||
}
|
||||
if (ffmpegGIF != null) {
|
||||
ffmpegGIF.startConverting();
|
||||
Ffmpeg localGIF = ffmpegGIF;
|
||||
if (localGIF != null) {
|
||||
localGIF.startConverting();
|
||||
if (gifHistory.isEmpty()) {
|
||||
gifHistory = gifFilename;
|
||||
} else if (!gifFilename.equals("ipcamera")) {
|
||||
@ -957,21 +958,25 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
ffmpegRecord = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
|
||||
cameraConfig.getMp4OutOptions(), cameraConfig.getFfmpegOutput() + mp4Filename + ".mp4",
|
||||
cameraConfig.getUser(), cameraConfig.getPassword());
|
||||
ffmpegRecord.startConverting();
|
||||
if (mp4History.isEmpty()) {
|
||||
mp4History = mp4Filename;
|
||||
} else if (!mp4Filename.equals("ipcamera")) {
|
||||
mp4History = mp4Filename + "," + mp4History;
|
||||
if (mp4HistoryLength > 49) {
|
||||
int endIndex = mp4History.lastIndexOf(",");
|
||||
mp4History = mp4History.substring(0, endIndex);
|
||||
Ffmpeg localRecord = ffmpegRecord;
|
||||
if (localRecord != null) {
|
||||
localRecord.startConverting();
|
||||
if (mp4History.isEmpty()) {
|
||||
mp4History = mp4Filename;
|
||||
} else if (!mp4Filename.equals("ipcamera")) {
|
||||
mp4History = mp4Filename + "," + mp4History;
|
||||
if (mp4HistoryLength > 49) {
|
||||
int endIndex = mp4History.lastIndexOf(",");
|
||||
mp4History = mp4History.substring(0, endIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
setChannelState(CHANNEL_MP4_HISTORY, new StringType(mp4History));
|
||||
break;
|
||||
case RTSP_ALARMS:
|
||||
if (ffmpegRtspHelper != null) {
|
||||
ffmpegRtspHelper.stopConverting();
|
||||
Ffmpeg localAlarms = ffmpegRtspHelper;
|
||||
if (localAlarms != null) {
|
||||
localAlarms.stopConverting();
|
||||
if (!audioAlarmEnabled && !motionAlarmEnabled) {
|
||||
return;
|
||||
}
|
||||
@ -996,40 +1001,45 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
ffmpegRtspHelper = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, input,
|
||||
filterOptions + cameraConfig.getMotionOptions(), outputOptions, cameraConfig.getUser(),
|
||||
cameraConfig.getPassword());
|
||||
ffmpegRtspHelper.startConverting();
|
||||
localAlarms = ffmpegRtspHelper;
|
||||
if (localAlarms != null) {
|
||||
localAlarms.startConverting();
|
||||
}
|
||||
break;
|
||||
case MJPEG:
|
||||
if (ffmpegMjpeg == null) {
|
||||
if (inputOptions.isEmpty()) {
|
||||
inputOptions = "-hide_banner -loglevel warning";
|
||||
} else {
|
||||
inputOptions = inputOptions + " -hide_banner -loglevel warning";
|
||||
inputOptions += " -hide_banner -loglevel warning";
|
||||
}
|
||||
ffmpegMjpeg = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
|
||||
cameraConfig.getMjpegOptions(),
|
||||
"http://127.0.0.1:" + cameraConfig.getServerPort() + "/ipcamera.jpg",
|
||||
cameraConfig.getUser(), cameraConfig.getPassword());
|
||||
}
|
||||
if (ffmpegMjpeg != null) {
|
||||
ffmpegMjpeg.startConverting();
|
||||
Ffmpeg localMjpeg = ffmpegMjpeg;
|
||||
if (localMjpeg != null) {
|
||||
localMjpeg.startConverting();
|
||||
}
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
// if mjpeg stream you can use ffmpeg -i input.h264 -codec:v copy -bsf:v mjpeg2jpeg output%03d.jpg
|
||||
// if mjpeg stream you can use 'ffmpeg -i input -codec:v copy -bsf:v mjpeg2jpeg output.jpg'
|
||||
if (ffmpegSnapshot == null) {
|
||||
if (inputOptions.isEmpty()) {
|
||||
// iFrames only
|
||||
inputOptions = "-threads 1 -skip_frame nokey -hide_banner -loglevel warning";
|
||||
} else {
|
||||
inputOptions = inputOptions + " -threads 1 -skip_frame nokey -hide_banner -loglevel warning";
|
||||
inputOptions += " -threads 1 -skip_frame nokey -hide_banner -loglevel warning";
|
||||
}
|
||||
ffmpegSnapshot = new Ffmpeg(this, format, cameraConfig.getFfmpegLocation(), inputOptions, rtspUri,
|
||||
"-an -vsync vfr -update 1",
|
||||
cameraConfig.getSnapshotOptions(),
|
||||
"http://127.0.0.1:" + cameraConfig.getServerPort() + "/snapshot.jpg",
|
||||
cameraConfig.getUser(), cameraConfig.getPassword());
|
||||
}
|
||||
if (ffmpegSnapshot != null) {
|
||||
ffmpegSnapshot.startConverting();
|
||||
Ffmpeg localSnaps = ffmpegSnapshot;
|
||||
if (localSnaps != null) {
|
||||
localSnaps.startConverting();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1185,7 +1195,7 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
motionAlarmEnabled = true;
|
||||
} else if (OnOffType.OFF.equals(command) || DecimalType.ZERO.equals(command)) {
|
||||
motionAlarmEnabled = false;
|
||||
noMotionDetected(CHANNEL_MOTION_ALARM);
|
||||
noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
|
||||
} else {
|
||||
motionAlarmEnabled = true;
|
||||
motionThreshold = Double.valueOf(command.toString());
|
||||
@ -1194,14 +1204,22 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
setupFfmpegFormat(FFmpegFormat.RTSP_ALARMS);
|
||||
return;
|
||||
case CHANNEL_START_STREAM:
|
||||
Ffmpeg localHLS;
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
setupFfmpegFormat(FFmpegFormat.HLS);
|
||||
if (ffmpegHLS != null) {
|
||||
ffmpegHLS.setKeepAlive(-1);// will keep running till manually stopped.
|
||||
localHLS = ffmpegHLS;
|
||||
if (localHLS == null) {
|
||||
setupFfmpegFormat(FFmpegFormat.HLS);
|
||||
localHLS = ffmpegHLS;
|
||||
}
|
||||
if (localHLS != null) {
|
||||
localHLS.setKeepAlive(-1);// Now will run till manually stopped.
|
||||
localHLS.startConverting();
|
||||
}
|
||||
} else {
|
||||
if (ffmpegHLS != null) {
|
||||
ffmpegHLS.setKeepAlive(1);
|
||||
localHLS = ffmpegHLS;
|
||||
if (localHLS != null) {
|
||||
// Still runs but will be able to auto stop when the HLS stream is no longer used.
|
||||
localHLS.setKeepAlive(1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -1228,8 +1246,9 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
sendHttpGET(snapshotUri);// Allows this to change Image FPS on demand
|
||||
}
|
||||
} else {
|
||||
if (ffmpegSnapshot != null) {
|
||||
ffmpegSnapshot.stopConverting();
|
||||
Ffmpeg localSnaps = ffmpegSnapshot;
|
||||
if (localSnaps != null) {
|
||||
localSnaps.stopConverting();
|
||||
ffmpegSnapshotGeneration = false;
|
||||
}
|
||||
updateImageChannel = false;
|
||||
@ -1376,8 +1395,9 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
groupTracker.listOfOnlineCameraHandlers.add(this);
|
||||
groupTracker.listOfOnlineCameraUID.add(getThing().getUID().getId());
|
||||
if (cameraConnectionJob != null) {
|
||||
cameraConnectionJob.cancel(false);
|
||||
Future<?> localFuture = cameraConnectionJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(false);
|
||||
}
|
||||
|
||||
if (cameraConfig.getGifPreroll() > 0 || cameraConfig.getUpdateImageWhen().contains("1")) {
|
||||
@ -1482,16 +1502,19 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
public void stopSnapshotPolling() {
|
||||
Future<?> localFuture;
|
||||
if (!streamingSnapshotMjpeg && cameraConfig.getGifPreroll() == 0
|
||||
&& !cameraConfig.getUpdateImageWhen().contains("1")) {
|
||||
snapshotPolling = false;
|
||||
if (snapshotJob != null) {
|
||||
snapshotJob.cancel(true);
|
||||
localFuture = snapshotJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
}
|
||||
} else if (cameraConfig.getUpdateImageWhen().contains("4")) { // only during Motion Alarms
|
||||
snapshotPolling = false;
|
||||
if (snapshotJob != null) {
|
||||
snapshotJob.cancel(true);
|
||||
localFuture = snapshotJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1575,8 +1598,9 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ffmpegHLS != null) {
|
||||
ffmpegHLS.checkKeepAlive();
|
||||
Ffmpeg localHLS = ffmpegHLS;
|
||||
if (localHLS != null) {
|
||||
localHLS.checkKeepAlive();
|
||||
}
|
||||
if (openChannels.size() > 18) {
|
||||
logger.debug("There are {} open Channels being tracked.", openChannels.size());
|
||||
@ -1681,17 +1705,17 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
isOnline = false;
|
||||
snapshotPolling = false;
|
||||
onvifCamera.disconnect();
|
||||
if (pollCameraJob != null) {
|
||||
pollCameraJob.cancel(true);
|
||||
pollCameraJob = null;
|
||||
Future<?> localFuture = pollCameraJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
}
|
||||
if (snapshotJob != null) {
|
||||
snapshotJob.cancel(true);
|
||||
snapshotJob = null;
|
||||
localFuture = snapshotJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
}
|
||||
if (cameraConnectionJob != null) {
|
||||
cameraConnectionJob.cancel(true);
|
||||
cameraConnectionJob = null;
|
||||
localFuture = cameraConnectionJob;
|
||||
if (localFuture != null) {
|
||||
localFuture.cancel(true);
|
||||
}
|
||||
threadPool.shutdown();
|
||||
threadPool = Executors.newScheduledThreadPool(4);
|
||||
@ -1707,29 +1731,29 @@ public class IpCameraHandler extends BaseThingHandler {
|
||||
stopStreamServer();
|
||||
openChannels.close();
|
||||
|
||||
if (ffmpegHLS != null) {
|
||||
ffmpegHLS.stopConverting();
|
||||
ffmpegHLS = null;
|
||||
Ffmpeg localFfmpeg = ffmpegHLS;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
if (ffmpegRecord != null) {
|
||||
ffmpegRecord.stopConverting();
|
||||
ffmpegRecord = null;
|
||||
localFfmpeg = ffmpegRecord;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
if (ffmpegGIF != null) {
|
||||
ffmpegGIF.stopConverting();
|
||||
ffmpegGIF = null;
|
||||
localFfmpeg = ffmpegGIF;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
if (ffmpegRtspHelper != null) {
|
||||
ffmpegRtspHelper.stopConverting();
|
||||
ffmpegRtspHelper = null;
|
||||
localFfmpeg = ffmpegRtspHelper;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
if (ffmpegMjpeg != null) {
|
||||
ffmpegMjpeg.stopConverting();
|
||||
ffmpegMjpeg = null;
|
||||
localFfmpeg = ffmpegMjpeg;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
if (ffmpegSnapshot != null) {
|
||||
ffmpegSnapshot.stopConverting();
|
||||
ffmpegSnapshot = null;
|
||||
localFfmpeg = ffmpegSnapshot;
|
||||
if (localFfmpeg != null) {
|
||||
localFfmpeg.stopConverting();
|
||||
}
|
||||
channelTrackingMap.clear();
|
||||
}
|
||||
|
@ -21,9 +21,11 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -33,6 +35,8 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ipcamera.internal.Helper;
|
||||
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -145,8 +149,9 @@ public class OnvifConnection {
|
||||
private String ptzNodeToken = "000";
|
||||
private String ptzConfigToken = "000";
|
||||
private int presetTokenIndex = 0;
|
||||
private LinkedList<String> presetTokens = new LinkedList<>();
|
||||
private LinkedList<String> mediaProfileTokens = new LinkedList<>();
|
||||
private List<String> presetTokens = new LinkedList<>();
|
||||
private List<String> presetNames = new LinkedList<>();
|
||||
private List<String> mediaProfileTokens = new LinkedList<>();
|
||||
private boolean ptzDevice = true;
|
||||
|
||||
public OnvifConnection(IpCameraHandler ipCameraHandler, String ipAddress, String user, String password) {
|
||||
@ -277,8 +282,7 @@ public class OnvifConnection {
|
||||
case GotoPreset:
|
||||
return "<GotoPreset xmlns=\"http://www.onvif.org/ver20/ptz/wsdl\"><ProfileToken>"
|
||||
+ mediaProfileTokens.get(mediaProfileIndex) + "</ProfileToken><PresetToken>"
|
||||
+ presetTokens.get(presetTokenIndex)
|
||||
+ "</PresetToken><Speed><PanTilt x=\"0.0\" y=\"0.0\" space=\"\"></PanTilt><Zoom x=\"0.0\" space=\"\"></Zoom></Speed></GotoPreset>";
|
||||
+ presetTokens.get(presetTokenIndex) + "</PresetToken></GotoPreset>";
|
||||
case GetPresets:
|
||||
return "<GetPresets xmlns=\"http://www.onvif.org/ver20/ptz/wsdl\"><ProfileToken>"
|
||||
+ mediaProfileTokens.get(mediaProfileIndex) + "</ProfileToken></GetPresets>";
|
||||
@ -326,7 +330,7 @@ public class OnvifConnection {
|
||||
} else if (message.contains("GetStatusResponse")) {
|
||||
processPTZLocation(message);
|
||||
} else if (message.contains("GetPresetsResponse")) {
|
||||
presetTokens = listOfResults(message, "<tptz:Preset", "token=\"");
|
||||
parsePresets(message);
|
||||
} else if (message.contains("GetConfigurationsResponse")) {
|
||||
sendPTZRequest(RequestType.GetPresets);
|
||||
ptzConfigToken = Helper.fetchXML(message, "PTZConfiguration", "token=\"");
|
||||
@ -357,14 +361,16 @@ public class OnvifConnection {
|
||||
HttpRequest requestBuilder(RequestType requestType, String xAddr) {
|
||||
logger.trace("Sending ONVIF request:{}", requestType);
|
||||
String security = "";
|
||||
String extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
||||
String extraEnvelope = "";
|
||||
String headerTo = "";
|
||||
String getXmlCache = getXml(requestType);
|
||||
if (requestType.equals(RequestType.CreatePullPointSubscription) || requestType.equals(RequestType.PullMessages)
|
||||
|| requestType.equals(RequestType.Renew) || requestType.equals(RequestType.Unsubscribe)) {
|
||||
headerTo = "<a:To s:mustUnderstand=\"1\">http://" + ipAddress + xAddr + "</a:To>";
|
||||
extraEnvelope = " xmlns:a=\"http://www.w3.org/2005/08/addressing\"";
|
||||
}
|
||||
if (!password.isEmpty()) {
|
||||
String headers;
|
||||
if (!password.isEmpty() && !requestType.equals(RequestType.GetSystemDateAndTime)) {
|
||||
String nonce = createNonce();
|
||||
String dateTime = getUTCdateTime();
|
||||
String digest = createDigest(nonce, dateTime);
|
||||
@ -376,17 +382,15 @@ public class OnvifConnection {
|
||||
+ encodeBase64(nonce)
|
||||
+ "</Nonce><Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
|
||||
+ dateTime + "</Created></UsernameToken></Security>";
|
||||
}
|
||||
String headers = "<s:Header>" + security + headerTo + "</s:Header>";
|
||||
|
||||
if (requestType.equals(RequestType.GetSystemDateAndTime)) {
|
||||
extraEnvelope = "";
|
||||
headers = "<s:Header>" + security + headerTo + "</s:Header>";
|
||||
} else {// GetSystemDateAndTime must not be password protected as per spec.
|
||||
headers = "";
|
||||
}
|
||||
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"), xAddr);
|
||||
request.headers().add("Content-Type", "application/soap+xml");
|
||||
request.headers().add("charset", "utf-8");
|
||||
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
||||
request.headers().add("Content-Type",
|
||||
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
|
||||
request.headers().add("Charset", "utf-8");
|
||||
if (onvifPort != 80) {
|
||||
request.headers().set("Host", ipAddress + ":" + onvifPort);
|
||||
} else {
|
||||
@ -398,7 +402,6 @@ public class OnvifConnection {
|
||||
+ headers
|
||||
+ "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
|
||||
+ getXmlCache + "</s:Body></s:Envelope>";
|
||||
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
|
||||
request.headers().add("SOAPAction", "\"" + actionString + "/" + requestType + "\"");
|
||||
ByteBuf bbuf = Unpooled.copiedBuffer(fullXml, StandardCharsets.UTF_8);
|
||||
request.headers().set("Content-Length", bbuf.readableBytes());
|
||||
@ -408,15 +411,14 @@ public class OnvifConnection {
|
||||
|
||||
/**
|
||||
* The {@link removeIPfromUrl} Will throw away all text before the cameras IP, also removes the IP and the PORT
|
||||
* leaving just the
|
||||
* URL.
|
||||
* leaving just the URL.
|
||||
*
|
||||
* @author Matthew Skinner - Initial contribution
|
||||
*/
|
||||
String removeIPfromUrl(String url) {
|
||||
int index = url.indexOf(ipAddress);
|
||||
int index = url.indexOf("//");
|
||||
if (index != -1) {// now remove the :port
|
||||
index = url.indexOf("/", index + ipAddress.length());
|
||||
index = url.indexOf("/", index + 2);
|
||||
}
|
||||
if (index == -1) {
|
||||
logger.debug("We hit an issue parsing url:{}", url);
|
||||
@ -456,11 +458,10 @@ public class OnvifConnection {
|
||||
String minute = Helper.fetchXML(message, "UTCDateTime", "Minute>");
|
||||
String hour = Helper.fetchXML(message, "UTCDateTime", "Hour>");
|
||||
String second = Helper.fetchXML(message, "UTCDateTime", "Second>");
|
||||
logger.debug("Cameras UTC time is : {}:{}:{}", hour, minute, second);
|
||||
String day = Helper.fetchXML(message, "UTCDateTime", "Day>");
|
||||
String month = Helper.fetchXML(message, "UTCDateTime", "Month>");
|
||||
String year = Helper.fetchXML(message, "UTCDateTime", "Year>");
|
||||
logger.debug("Cameras UTC date is : {}-{}-{}", year, month, day);
|
||||
logger.debug("Cameras UTC dateTime is:{}-{}-{}T{}:{}:{}", year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
private String getUTCdateTime() {
|
||||
@ -718,8 +719,8 @@ public class OnvifConnection {
|
||||
this.mediaProfileIndex = mediaProfileIndex;
|
||||
}
|
||||
|
||||
LinkedList<String> listOfResults(String message, String heading, String key) {
|
||||
LinkedList<String> results = new LinkedList<String>();
|
||||
List<String> listOfResults(String message, String heading, String key) {
|
||||
List<String> results = new LinkedList<>();
|
||||
String temp = "";
|
||||
for (int startLookingFromIndex = 0; startLookingFromIndex != -1;) {
|
||||
startLookingFromIndex = message.indexOf(heading, startLookingFromIndex);
|
||||
@ -728,13 +729,31 @@ public class OnvifConnection {
|
||||
if (!temp.isEmpty()) {
|
||||
logger.trace("String was found:{}", temp);
|
||||
results.add(temp);
|
||||
++startLookingFromIndex;
|
||||
} else {
|
||||
return results;// key string must not exist so stop looking.
|
||||
}
|
||||
startLookingFromIndex += temp.length();
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void parsePresets(String message) {
|
||||
List<StateOption> presets = new ArrayList<>();
|
||||
int counter = 1;// Presets start at 1 not 0. HOME may be added to index 0.
|
||||
presetTokens = listOfResults(message, "<tptz:Preset", "token=\"");
|
||||
presetNames = listOfResults(message, "<tptz:Preset", "<tt:Name>");
|
||||
if (presetTokens.size() != presetNames.size()) {
|
||||
logger.warn("Camera did not report the same number of Tokens and Names for PTZ presets");
|
||||
return;
|
||||
}
|
||||
for (String value : presetNames) {
|
||||
presets.add(new StateOption(Integer.toString(counter++), value));
|
||||
}
|
||||
ipCameraHandler.stateDescriptionProvider
|
||||
.setStateOptions(new ChannelUID(ipCameraHandler.getThing().getUID(), CHANNEL_GOTO_PRESET), presets);
|
||||
}
|
||||
|
||||
void parseProfiles(String message) {
|
||||
mediaProfileTokens = listOfResults(message, "<trt:Profiles", "token=\"");
|
||||
if (mediaProfileIndex >= mediaProfileTokens.size()) {
|
||||
|
@ -241,6 +241,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -479,6 +487,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -722,6 +738,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -1006,6 +1030,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -1272,6 +1304,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -1535,6 +1575,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -1823,6 +1871,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
@ -2098,6 +2154,14 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="snapshotOptions" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<label>Snapshot Options</label>
|
||||
<description>Specify your own FFmpeg options to be used when creating snapshots from RTSP.
|
||||
</description>
|
||||
<default>-an -vsync vfr -q:v 2 -update 1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="alarmInputUrl" type="text" required="false" groupName="FFmpeg Setup">
|
||||
<context>url</context>
|
||||
<label>Alarm Input URL</label>
|
||||
|
Loading…
x
Reference in New Issue
Block a user