]> git.basschouten.com Git - openhab-addons.git/commitdiff
[ipcamera] Improve ONVIF preset naming (#8948)
authorMatthew Skinner <matt@pcmus.com>
Sat, 21 Nov 2020 06:27:49 +0000 (17:27 +1100)
committerGitHub <noreply@github.com>
Sat, 21 Nov 2020 06:27:49 +0000 (22:27 -0800)
* 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>
18 files changed:
bundles/org.openhab.binding.ipcamera/README.md
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/AmcrestHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/CameraConfig.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DahuaHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/DoorBirdHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/Ffmpeg.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/FoscamHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/HikvisionHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/InstarHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraActions.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraBindingConstants.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDynamicStateDescriptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraHandlerFactory.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/StreamServerHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraGroupHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/handler/IpCameraHandler.java
bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/onvif/OnvifConnection.java
bundles/org.openhab.binding.ipcamera/src/main/resources/OH-INF/thing/thing-types.xml

index ff25ddce0e4efdf87f8400f23b7cd9dc26bd2602..cfdbe24d79a7409317f59e7271006f00bd0728ee 100644 (file)
@@ -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)"` |
index 687453ec5528c63c6c0987bf13df74effe4f1433..3cedaff611b2ec8cd5cab72bd2366057791102cc 100644 (file)
@@ -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;
         }
     }
 
index 313ed8cae410184510f0af7bc33b043c07e49e20..b3c2722a653c57cc809a6b1b415279a926fd7c3b 100644 (file)
@@ -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;
     }
index a5918be9b165336b4c22009d479c6e64be5d43e1..35c418bb948ffd4eee8c12fc457f781113655050 100644 (file)
@@ -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;
         }
     }
 
index 0208037f825e61e83eb610854dcc43116a54a8a2..777f001240fb7b525ba4895375626e515ef6b337 100644 (file)
@@ -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;
         }
     }
 
index 6f61ee15afc1d3a2dab33045b092c658fdf03f95..6e353933a16fb7f101f50bc77c4eb2a29fc959e3 100644 (file)
@@ -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) {
index 6766a7fbfec419a6ad3dde18a9e0f9b604f5791c..dd0dc570496a0265d3e93f3a2c845f9822b0dc49 100644 (file)
@@ -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;
         }
     }
 
index 8521f97e49c5c45834f3109d4cbf96e40b7c6f67..74580916f34716416ae9202bb342d7bc6b9a0b2f 100644 (file)
@@ -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;
         }
     }
 
index 80a7b8bbd92b9eec16471394f39465187261ac10..86a1d583b1d90324494f9089a10af73c498aa0e5 100644 (file)
@@ -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;
         }
     }
 
index 1ea92adaf7c141b219aa31bcf311f3a6f76d1252..d042d93bcc74b002bcb049d33db89bfa60ace0ab 100644 (file)
@@ -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);
         }
     }
 
index 676c8c1b00ae7f407171b1b6401938b857974e42..ee272259e0d2eba0a5c22cd96e82984071b14cdc 100644 (file)
@@ -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";
diff --git a/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDynamicStateDescriptionProvider.java b/bundles/org.openhab.binding.ipcamera/src/main/java/org/openhab/binding/ipcamera/internal/IpCameraDynamicStateDescriptionProvider.java
new file mode 100644 (file)
index 0000000..3f259c9
--- /dev/null
@@ -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;
+    }
+}
index 73f7378e4aa85599d168ddf414b86c688b065d38..bbd032acd6d1a758b74e97c90e18be92f4159c3d 100644 (file)
@@ -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);
         }
index 194827f8ec796cba58c321ef8d0821f97ff236b5..65ecd9e1f8db2541c13a300034c2ad54625b2e1c 100644 (file)
@@ -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");
index 3420c65f30c32b2962667d1fd08d76fa9fa5f73d..6fc3f2e1eb5f030b0d5fedd0c4158a6756da9738 100644 (file)
@@ -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();
     }
index 2e16e0419e259663af489ace0ddbd69070f1b99c..4994cfb3e725b04a0ea270eb757e8bc8b2d8a4c4 100644 (file)
@@ -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();
     }
index 402725db50199e1d7e8a0f44b9e5deec405ce764..fdad134c1d817b567ddd74518a0750d1579dfe44 100644 (file)
@@ -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()) {
index c88b0ad280484ca144eb204979f9e24a848f48b4..d7ed31478c222895ede01d9e245fb6fea414f3df 100644 (file)
                                <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>
                                <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>
                                <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>
                                <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>
                                <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>
                                <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>
                                <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>
                                <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>