[ipcamera] Add Reolink API support (#14728)

Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
Matthew Skinner 2023-05-13 20:31:39 +10:00 committed by GitHub
parent cd4879a315
commit 01add04f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 901 additions and 20 deletions

View File

@ -35,6 +35,7 @@ Some cameras allow the key frame to be created every second or a different amoun
These cameras do not have the ability to create H.264 streams and hence can not be used with HLS, however all other features should work.
Due to many custom firmwares available, you may need to ask the firmware developer what the URLs are for snapshots and MJPEG streams if they have changed the defaults from what the Arduino IDE sample code uses.
Another limitation is that they can only provide a single stream at a time, so you need to setup the `ffmpegInput` to use the ipcamera.mjpeg feed from the openHAB server and change `ffmpegInputOptions` to "-f mjpeg" so FFmpeg knows the input is MJPEG format and not H.264.
Example:
@ -46,7 +47,8 @@ Thing ipcamera:generic:Esp32Cam
snapshotUrl="http://192.168.1.181/capture",
mjpegUrl="http://192.168.1.181:81/stream",
ffmpegInputOptions="-f mjpeg",
ffmpegOutput="/tmp/Esp32Camera/", ffmpegInput="http://192.168.1.181:81/stream"
ffmpegOutput="/tmp/Esp32Camera/",
ffmpegInput="http://192.168.1.3:8080/ipcamera/{cameraUID}/ipcamera.mjpeg"
]
```
@ -124,6 +126,11 @@ Thing ipcamera:hikvision:West "West Camera"
- For MJPEG to work, you need to set the first sub-stream to be MJPEG format for the default settings to work, otherwise you can override the default with mjpegUrl with a valid URL for MJPEG streams.
- Be sure to update to the latest firmware for your camera as Instar have made a lot of improvements recently, including adding MQTT features (MQTT is not needed for this binding to work).
### Reolink
- NVR's made by Reolink have ONVIF disabled by default and may require a screen connected to the hardware to enable ONVIF or newer firmwares may be able to do this via their app or web UI.
- This binding will use the Reolink API for polling the alarms if the `nvrChannel` is 1 or higher and does not need ONVIF to be enabled. To use ONVIF event methods for the alarms, you can set `nvrChannel` to 0.
## Discovery
The discovery feature of openHAB can be used to find and setup any ONVIF cameras.
@ -209,6 +216,7 @@ The channels are kept consistent as much as possible from brand to brand to make
|-|-|-|
| `activateAlarmOutput` | Switch | Toggles a cameras relay output 1. |
| `activateAlarmOutput2` | Switch | Toggles a cameras relay output 2. |
| `animalAlarm` | Switch | Toggles when an animal is in view. |
| `audioAlarm` | Switch (read only) | When the camera detects noise above a threshold this switch will move to ON. |
| `autoLED` | Switch | When ON this sets a cameras IR LED to automatically turn on or off. |
| `carAlarm` | Switch | When a car is detected the switch will turn ON. |
@ -217,10 +225,12 @@ The channels are kept consistent as much as possible from brand to brand to make
| `enableAudioAlarm` | Switch | Allows the audio alarm to be turned ON or OFF. |
| `enableExternalAlarmInput` | Switch | Hikvision and Instar allow the Alarm input terminals to be disabled by this control. |
| `enableFieldDetectionAlarm` | Switch | Allows the field detection alarm to be turned ON or OFF. Some cameras will call this the Intrusion Alarm. |
| `enableFTP` | Switch | Turn the cameras internal FTP recordings ON or OFF. |
| `enableLED` | Switch | Turn the IR LED ON or OFF. Some cameras have 3 states the LED can be in, so see the `autoLED` channel. |
| `enableLineCrossingAlarm` | Switch | Turns the line crossing alarm for API cameras, ON and OFF. |
| `enableMotionAlarm` | Switch | Turns the motion alarm ON and OFF for API cameras. This will not effect FFmpeg based alarms which have their own control. |
| `enablePirAlarm` | Switch | Turn PIR sensor ON or OFF. |
| `enableRecordings` | Switch | Turn the cameras internal recordings ON or OFF. |
| `externalAlarmInput` | Switch (read only) | Reflects the status of the alarm input terminals on some cameras. |
| `externalAlarmInput2` | Switch (read only) | Reflects the status of the alarm input 2 terminals on some cameras. |
| `externalLight` | Switch | Some cameras have a dedicated relay output for turning lights on and off with. |

View File

@ -27,6 +27,7 @@ public class CameraConfig {
private int onvifPort;
private String username = "";
private String password = "";
public boolean useToken = true;
private int onvifMediaProfile;
private int pollTime;
private String ffmpegInput = "";

View File

@ -107,7 +107,6 @@ public class FoscamHandler extends ChannelDuplexHandler {
if (content.contains("</CGI_Result>")) {
ctx.close();
ipCameraHandler.logger.debug("End of FOSCAM handler reached, so closing the channel to the camera now");
}
} finally {
ReferenceCountUtil.release(msg);

View File

@ -33,6 +33,7 @@ public class IpCameraBindingConstants {
public static final String AMCREST_HANDLER = "amcrestHandler";
public static final String COMMON_HANDLER = "commonHandler";
public static final String INSTAR_HANDLER = "instarHandler";
public static final String REOLINK_HANDLER = "reolinkHandler";
public static enum FFmpegFormat {
HLS,
@ -66,10 +67,12 @@ public class IpCameraBindingConstants {
public static final ThingTypeUID THING_TYPE_DAHUA = new ThingTypeUID(BINDING_ID, DAHUA_THING);
public static final String DOORBIRD_THING = "doorbird";
public static final ThingTypeUID THING_TYPE_DOORBIRD = new ThingTypeUID(BINDING_ID, DOORBIRD_THING);
public static final String REOLINK_THING = "reolink";
public static final ThingTypeUID THING_TYPE_REOLINK = new ThingTypeUID(BINDING_ID, REOLINK_THING);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<ThingTypeUID>(
Arrays.asList(THING_TYPE_ONVIF, THING_TYPE_GENERIC, THING_TYPE_AMCREST, THING_TYPE_DAHUA, THING_TYPE_INSTAR,
THING_TYPE_FOSCAM, THING_TYPE_DOORBIRD, THING_TYPE_HIKVISION));
THING_TYPE_FOSCAM, THING_TYPE_DOORBIRD, THING_TYPE_HIKVISION, THING_TYPE_REOLINK));
public static final Set<ThingTypeUID> GROUP_SUPPORTED_THING_TYPES = new HashSet<ThingTypeUID>(
Arrays.asList(THING_TYPE_GROUP));
@ -139,4 +142,6 @@ public class IpCameraBindingConstants {
public static final String CHANNEL_CAR_ALARM = "carAlarm";
public static final String CHANNEL_HUMAN_ALARM = "humanAlarm";
public static final String CHANNEL_ANIMAL_ALARM = "animalAlarm";
public static final String CHANNEL_ENABLE_FTP = "enableFTP";
public static final String CHANNEL_ENABLE_RECORDINGS = "enableRecordings";
}

View File

@ -0,0 +1,280 @@
/**
* Copyright (c) 2010-2023 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 static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ipcamera.internal.ReolinkState.GetAiStateResponse;
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import com.google.gson.Gson;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
/**
* The {@link ReolinkHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class ReolinkHandler extends ChannelDuplexHandler {
protected final Gson gson = new Gson();
private IpCameraHandler ipCameraHandler;
private String requestUrl = "Empty";
public ReolinkHandler(IpCameraHandler thingHandler) {
ipCameraHandler = thingHandler;
}
public void setURL(String url) {
requestUrl = url;
}
// This handles the incoming http replies back from the camera.
@Override
public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object msg) throws Exception {
if (msg == null || ctx == null) {
return;
}
try {
String content = msg.toString();
ipCameraHandler.logger.trace("HTTP Result from {} contains \t:{}:", requestUrl, content);
int afterCommand = requestUrl.indexOf("&");
String cutDownURL;
if (afterCommand < 0) {
cutDownURL = requestUrl;
} else {
cutDownURL = requestUrl.substring(0, afterCommand);
}
switch (cutDownURL) {// Use a cutdown URL as we can not use variables in a switch()
case "/api.cgi?cmd=Login":
ipCameraHandler.reolinkAuth = "&token=" + Helper.searchString(content, "\"name\" : \"");
if (ipCameraHandler.reolinkAuth.length() > 7) {
ipCameraHandler.logger.debug("Your Reolink camera gave a login:{}",
ipCameraHandler.reolinkAuth);
ipCameraHandler.snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel="
+ ipCameraHandler.cameraConfig.getNvrChannel() + "&rs=openHAB"
+ ipCameraHandler.reolinkAuth;
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAbility" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\""
+ ipCameraHandler.cameraConfig.getUser() + "\" }}}]");
} else {
ipCameraHandler.logger.info("Your Reolink camera gave a bad login response:{}", content);
}
break;
case "/api.cgi?cmd=GetAbility": // Used to check what channels the camera supports
List<org.openhab.core.thing.Channel> removeChannels = new ArrayList<>();
org.openhab.core.thing.Channel channel;
if (content.contains("\"supportFtpEnable\": { \"permit\": 0")) {
ipCameraHandler.logger.debug("Camera has no Enable FTP support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_FTP);
if (channel != null) {
removeChannels.add(channel);
}
}
if (content.contains("\"supportRecordEnable\": { \"permit\": 0")) {
ipCameraHandler.logger.debug("Camera has no enable recording support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_RECORDINGS);
if (channel != null) {
removeChannels.add(channel);
}
}
if (content.contains("\"floodLight\": { \"permit\": 0")) {
ipCameraHandler.logger.debug("Camera has no Flood light support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_LED);
if (channel != null) {
removeChannels.add(channel);
}
}
ipCameraHandler.removeChannels(removeChannels);
break;
case "/api.cgi?cmd=GetAiState":
ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content));
GetAiStateResponse[] aiResponse = gson.fromJson(content, GetAiStateResponse[].class);
if (aiResponse == null) {
ipCameraHandler.logger.debug("The GetAiStateResponse could not be parsed");
return;
}
if (aiResponse[0].value.dog_cat != null) {
if (aiResponse[0].value.dog_cat.alarm_state == 1) {
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
}
}
if (aiResponse[0].value.face.alarm_state == 1) {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
}
if (aiResponse[0].value.people.alarm_state == 1) {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
}
if (aiResponse[0].value.vehicle.alarm_state == 1) {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.OFF);
}
break;
case "/api.cgi?cmd=GetAudioAlarmV20":
if (content.contains("\"enable\" : 1")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
}
break;
case "/api.cgi?cmd=GetIrLights":
if (content.contains("\"state\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetMdState":
if (content.contains("\"state\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
}
break;
}
} finally {
ReferenceCountUtil.release(msg);
}
}
// This handles the commands that come from the openHAB event bus.
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_ENABLE_MOTION_ALARM:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetMdState" + ipCameraHandler.reolinkAuth);
break;
case CHANNEL_ENABLE_AUDIO_ALARM:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetAudioAlarmV20\", \"action\":1, \"param\":{ \"channel\": 0}}]");
break;
case CHANNEL_AUTO_LED:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetIrLights" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetIrLights\"}]");
break;
}
return;
} // end of "REFRESH"
switch (channelUID.getId()) {
case CHANNEL_ACTIVATE_ALARM_OUTPUT: // cameras built in siren
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 1, \"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 0, \"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
}
break;
case CHANNEL_AUTO_LED:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Auto\"}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
}
break;
case CHANNEL_ENABLE_AUDIO_ALARM:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 1,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 0,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
}
break;
case CHANNEL_ENABLE_FTP:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 1}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
}
break;
case CHANNEL_ENABLE_LED:
if (OnOffType.OFF.equals(command) || PercentType.ZERO.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 0,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
} else if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
} else if (command instanceof PercentType) {
int value = ((PercentType) command).toBigDecimal().intValue();
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
+ "}}}]");
}
case CHANNEL_ENABLE_MOTION_ALARM:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
}
break;
case CHANNEL_ENABLE_RECORDINGS:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 1}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
}
break;
}
}
// If a camera does not need to poll a request as often as snapshots, it can be
// added here. Binding steps through the list.
public List<String> getLowPriorityRequests() {
return List.of();
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2023 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;
/**
* The {@link ReolinkState} class holds the state and GSON parsed replies for a single Reolink Camera.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class ReolinkState {
public class GetAiStateResponse {
public class Value {
public class Alarm {
public int alarm_state = 0;
public int support = 0;
}
public int channel = 0;
public Alarm dog_cat = new Alarm();
public Alarm face = new Alarm();
public Alarm people = new Alarm();
public Alarm vehicle = new Alarm();
}
public String cmd = "";
public int code = 0;
public Value value = new Value();
}
}

View File

@ -23,6 +23,7 @@ import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
@ -57,6 +58,7 @@ 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.ReolinkHandler;
import org.openhab.binding.ipcamera.internal.onvif.OnvifConnection;
import org.openhab.binding.ipcamera.internal.servlet.CameraServlet;
import org.openhab.core.OpenHAB;
@ -72,9 +74,11 @@ import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -146,10 +150,11 @@ public class IpCameraHandler extends BaseThingHandler {
private @Nullable ScheduledFuture<?> cameraConnectionJob = null;
private @Nullable ScheduledFuture<?> pollCameraJob = null;
private @Nullable ScheduledFuture<?> snapshotJob = null;
private @Nullable ScheduledFuture<?> authenticationJob = null;
private @Nullable Bootstrap mainBootstrap;
private EventLoopGroup mainEventLoopGroup = new NioEventLoopGroup(1);
private FullHttpRequest putRequestWithBody = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("PUT"),
"");
private FullHttpRequest putRequestWithBody = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "");
private FullHttpRequest postRequestWithBody = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "");
private String gifFilename = "ipcamera";
private String gifHistory = "";
private String mp4History = "";
@ -168,6 +173,7 @@ public class IpCameraHandler extends BaseThingHandler {
// basicAuth MUST remain private as it holds the cameraConfig.getPassword()
private String basicAuth = "";
public String reolinkAuth = "&token=null";
public boolean useBasicAuth = false;
public boolean useDigestAuth = false;
public boolean newInstarApi = false;
@ -325,9 +331,7 @@ public class IpCameraHandler extends BaseThingHandler {
}
// Foscam needs this as will other cameras with chunks//
if (isChunked && bytesAlreadyRecieved != 0) {
logger.debug("Reply is chunked.");
reply = incomingMessage;
super.channelRead(ctx, reply);
}
}
}
@ -335,7 +339,7 @@ public class IpCameraHandler extends BaseThingHandler {
// Foscam cameras need this
if (!contentType.contains("image/jp") && bytesAlreadyRecieved != 0) {
reply = incomingMessage;
logger.debug("Packet back from camera is {}", incomingMessage);
logger.trace("Packet back from camera is {}", incomingMessage);
super.channelRead(ctx, reply);
}
}
@ -472,6 +476,24 @@ public class IpCameraHandler extends BaseThingHandler {
sendHttpRequest("PUT", httpRequestURL, null);
}
public void sendHttpPOST(String httpPostURL, String content) {
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, httpPostURL);
request.headers().set("Host", cameraConfig.getIp());
request.headers().add("Content-Type", "application/json");
request.headers().add("User-Agent",
"openHAB/" + FrameworkUtil.getBundle(this.getClass()).getVersion().toString());
request.headers().add("Accept", "*/*");
ByteBuf bbuf = Unpooled.copiedBuffer(content, StandardCharsets.UTF_8);
request.headers().set("Content-Length", bbuf.readableBytes());
request.content().clear().writeBytes(bbuf);
postRequestWithBody = request; // use Global so the authhandler can use it when resent with DIGEST.
sendHttpRequest("POST", httpPostURL, null);
}
public void sendHttpPOST(String httpRequestURL) {
sendHttpRequest("POST", httpRequestURL, null);
}
public void sendHttpGET(String httpRequestURL) {
sendHttpRequest("GET", httpRequestURL, null);
}
@ -566,6 +588,9 @@ public class IpCameraHandler extends BaseThingHandler {
case INSTAR_THING:
socketChannel.pipeline().addLast(INSTAR_HANDLER, new InstarHandler(getHandle()));
break;
case REOLINK_THING:
socketChannel.pipeline().addLast(REOLINK_HANDLER, new ReolinkHandler(getHandle()));
break;
default:
socketChannel.pipeline().addLast(new HttpOnlyHandler(getHandle()));
break;
@ -575,12 +600,14 @@ public class IpCameraHandler extends BaseThingHandler {
}
FullHttpRequest request;
if (!"PUT".equals(httpMethod) || (useDigestAuth && digestString == null)) {
if ("GET".equals(httpMethod) || (useDigestAuth && digestString == null)) {
request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod(httpMethod), httpRequestURL);
request.headers().set("Host", cameraConfig.getIp() + ":" + port);
request.headers().set("Connection", HttpHeaderValues.CLOSE);
} else {
} else if ("PUT".equals(httpMethod)) {
request = putRequestWithBody;
} else {
request = postRequestWithBody;
}
if (!basicAuth.isEmpty()) {
@ -630,6 +657,10 @@ public class IpCameraHandler extends BaseThingHandler {
InstarHandler instarHandler = (InstarHandler) ch.pipeline().get(INSTAR_HANDLER);
instarHandler.setURL(httpRequestURL);
break;
case REOLINK_THING:
ReolinkHandler reolinkHandler = (ReolinkHandler) ch.pipeline().get(REOLINK_HANDLER);
reolinkHandler.setURL(httpRequestURL);
break;
}
ch.writeAndFlush(request);
} else { // an error occured
@ -1025,6 +1056,12 @@ public class IpCameraHandler extends BaseThingHandler {
setChannelState(CHANNEL_RECORDING_GIF, DecimalType.valueOf(new String("" + seconds)));
}
private void getReolinkToken() {
sendHttpPOST("/api.cgi?cmd=Login",
"[{\"cmd\":\"Login\", \"param\":{ \"User\":{ \"Version\": \"0\", \"userName\":\""
+ cameraConfig.getUser() + "\", \"password\":\"" + cameraConfig.getPassword() + "\"}}}]");
}
public String returnValueFromString(String rawString, String searchedString) {
String result = "";
int index = rawString.indexOf(searchedString);
@ -1063,6 +1100,14 @@ public class IpCameraHandler extends BaseThingHandler {
}
}
public void removeChannels(List<org.openhab.core.thing.Channel> removeChannels) {
if (!removeChannels.isEmpty()) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withoutChannels(removeChannels);
updateThing(thingBuilder.build());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
@ -1287,6 +1332,10 @@ public class IpCameraHandler extends BaseThingHandler {
lowPriorityRequests = instarHandler.getLowPriorityRequests();
}
break;
case REOLINK_THING:
ReolinkHandler reolinkHandler = new ReolinkHandler(getHandle());
reolinkHandler.handleCommand(channelUID, command);
break;
default:
HttpOnlyHandler defaultHandler = new HttpOnlyHandler(getHandle());
defaultHandler.handleCommand(channelUID, command);
@ -1538,6 +1587,21 @@ public class IpCameraHandler extends BaseThingHandler {
sendHttpGET("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion");
sendHttpGET("/cgi-bin/eventManager.cgi?action=getEventIndexes&code=AudioMutation");
break;
case REOLINK_THING:
if (cameraConfig.getNvrChannel() > 0) {
sendHttpGET("/api.cgi?cmd=GetAiState&channel=" + cameraConfig.getNvrChannel() + "&user="
+ cameraConfig.getUser() + "&password=" + cameraConfig.getPassword());
sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + "&user="
+ cameraConfig.getUser() + "&password=" + cameraConfig.getPassword());
} else {
if (!snapshotPolling) {
checkCameraConnection();
}
if (!onvifCamera.isConnected()) {
onvifCamera.connect(true);
}
}
break;
case DAHUA_THING:
if (!snapshotPolling) {
checkCameraConnection();
@ -1663,6 +1727,30 @@ public class IpCameraHandler extends BaseThingHandler {
+ getThing().getUID().getId()
+ "/instar&-as_ssl=0&-as_mode=1&-as_activequery=1&-as_auth=0&-as_query1=0&-as_query2=0&-as_query3=0&-as_query4=0&-as_query5=0");
break;
case REOLINK_THING:
if (cameraConfig.useToken) {
authenticationJob = threadPool.scheduleWithFixedDelay(this::getReolinkToken, 0, 45,
TimeUnit.MINUTES);
} else {
reolinkAuth = "&user=" + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword();
}
if (snapshotUri.isEmpty()) {
if (cameraConfig.getNvrChannel() < 1) {
snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=openHAB" + reolinkAuth;
} else {
snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=" + (cameraConfig.getNvrChannel() - 1)
+ "&rs=openHAB" + reolinkAuth;
}
}
if (rtspUri.isEmpty()) {
if (cameraConfig.getNvrChannel() < 1) {
rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_01_main";
} else {
rtspUri = "rtsp://" + cameraConfig.getIp() + ":554/h264Preview_0" + cameraConfig.getNvrChannel()
+ "_main";
}
}
break;
}
// for poll times 9 seconds and above don't display a warning about the Image channel.
if (9000 > cameraConfig.getPollTime() && cameraConfig.getUpdateImageWhen().contains("1")) {
@ -1681,11 +1769,23 @@ public class IpCameraHandler extends BaseThingHandler {
cameraConfig.getUser(), cameraConfig.getPassword());
onvifCamera.setSelectedMediaProfile(cameraConfig.getOnvifMediaProfile());
// Only use ONVIF events if it is not an API camera.
onvifCamera.connect(thing.getThingTypeUID().getId().equals(ONVIF_THING));
onvifCamera.connect(supportsOnvifEvents());
}
cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, 4, 8, TimeUnit.SECONDS);
}
private boolean supportsOnvifEvents() {
switch (thing.getThingTypeUID().getId()) {
case ONVIF_THING:
return true;
case REOLINK_THING:
if (cameraConfig.getNvrChannel() < 1) {
return true;
}
}
return false;
}
private void keepMjpegRunning() {
CameraServlet localServlet = servlet;
if (localServlet != null && !localServlet.openStreams.isEmpty()) {
@ -1708,17 +1808,22 @@ public class IpCameraHandler extends BaseThingHandler {
Future<?> localFuture = pollCameraJob;
if (localFuture != null) {
localFuture.cancel(true);
localFuture = null;
pollCameraJob = null;
}
localFuture = authenticationJob;
if (localFuture != null) {
localFuture.cancel(true);
authenticationJob = null;
}
localFuture = snapshotJob;
if (localFuture != null) {
localFuture.cancel(true);
localFuture = null;
snapshotJob = null;
}
localFuture = cameraConnectionJob;
if (localFuture != null) {
localFuture.cancel(true);
localFuture = null;
cameraConnectionJob = null;
}
Ffmpeg localFfmpeg = ffmpegHLS;
if (localFfmpeg != null) {
@ -1762,7 +1867,7 @@ public class IpCameraHandler extends BaseThingHandler {
CameraServlet localServlet = servlet;
if (localServlet != null) {
localServlet.dispose();
localServlet = null;
servlet = null;
}
threadPool.shutdown();
// inform all group handlers that this camera has gone offline

View File

@ -316,13 +316,18 @@ public class OnvifConnection {
} finally {
connecting.unlock();
}
sendOnvifRequest(requestBuilder(RequestType.GetCapabilities, deviceXAddr));
parseDateAndTime(message);
logger.debug("Openhabs UTC dateTime is:{}", getUTCdateTime());
} else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
parseXAddr(message);
sendOnvifRequest(requestBuilder(RequestType.GetProfiles, mediaXAddr));
} else if (message.contains("GetProfilesResponse")) {// 3rd to be sent.
connecting.lock();
try {
isConnected = true;
} finally {
connecting.unlock();
}
parseProfiles(message);
sendOnvifRequest(requestBuilder(RequestType.GetSnapshotUri, mediaXAddr));
sendOnvifRequest(requestBuilder(RequestType.GetStreamUri, mediaXAddr));
@ -472,7 +477,21 @@ public class OnvifConnection {
ptzXAddr = Helper.fetchXML(message, "<tt:PTZ", "tt:XAddr");
if (ptzXAddr.isEmpty()) {
ptzDevice = false;
logger.trace("Camera must not support PTZ, it failed to give a <tt:PTZ><tt:XAddr>:{}", message);
logger.debug("Camera has no ONVIF PTZ support.");
List<org.openhab.core.thing.Channel> removeChannels = new ArrayList<>();
org.openhab.core.thing.Channel channel = ipCameraHandler.getThing().getChannel(CHANNEL_PAN);
if (channel != null) {
removeChannels.add(channel);
}
channel = ipCameraHandler.getThing().getChannel(CHANNEL_TILT);
if (channel != null) {
removeChannels.add(channel);
}
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ZOOM);
if (channel != null) {
removeChannels.add(channel);
}
ipCameraHandler.removeChannels(removeChannels);
} else {
logger.debug("ptzXAddr:{}", ptzXAddr);
}
@ -601,11 +620,13 @@ public class OnvifConnection {
public void eventRecieved(String eventMessage) {
String topic = Helper.fetchXML(eventMessage, "Topic", "tns1:");
if (topic.isEmpty()) {
sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
return;
}
String dataName = Helper.fetchXML(eventMessage, "tt:Data", "Name=\"");
String dataValue = Helper.fetchXML(eventMessage, "tt:Data", "Value=\"");
if (!topic.isEmpty()) {
logger.debug("Onvif Event Topic:{}, Data:{}, Value:{}", topic, dataName, dataValue);
}
logger.debug("Onvif Event Topic:{}, Data:{}, Value:{}", topic, dataName, dataValue);
switch (topic) {
case "RuleEngine/CellMotionDetector/Motion":
if ("true".equals(dataValue)) {
@ -692,7 +713,43 @@ public class OnvifConnection {
ipCameraHandler.changeAlarmState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/Visitor":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_DOORBELL, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/VehicleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_CAR_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/DogCatDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/FaceDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
}
break;
case "RuleEngine/MyRuleDetector/PeopleDetect":
if ("true".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else if ("false".equals(dataValue)) {
ipCameraHandler.changeAlarmState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
}
break;
default:
logger.debug("Please report this camera has an un-implemented ONVIF event. Topic:{}", topic);
}
sendOnvifRequest(requestBuilder(RequestType.Renew, subscriptionXAddr));
}
@ -856,6 +913,7 @@ public class OnvifConnection {
threadPool = Executors.newScheduledThreadPool(2);
sendOnvifRequest(requestBuilder(RequestType.GetSystemDateAndTime, deviceXAddr));
usingEvents = useEvents;
sendOnvifRequest(requestBuilder(RequestType.GetCapabilities, deviceXAddr));
}
} finally {
connecting.unlock();

View File

@ -153,6 +153,8 @@ public class OnvifDiscovery {
return "dahua";
} else if (response.toLowerCase().contains("dh-sd")) {
return "dahua";
} else if (response.toLowerCase().contains("reolink")) {
return "reolink";
}
return "onvif";
}

View File

@ -23,6 +23,8 @@ thing-type.ipcamera.instar.label = Instar Camera with API
thing-type.ipcamera.instar.description = Use for all current INSTAR HD Cameras, as they support an API as well as ONVIF.
thing-type.ipcamera.onvif.label = ONVIF IP Camera
thing-type.ipcamera.onvif.description = Use when the binding does not list your brand of ONVIF camera.
thing-type.ipcamera.reolink.label = Reolink Camera with API
thing-type.ipcamera.reolink.description = Use for all Reolink cameras, as they support an API as well as ONVIF.
# thing types config
@ -548,6 +550,72 @@ thing-type.config.ipcamera.onvif.updateImageWhen.option.5 = During Audio Alarm (
thing-type.config.ipcamera.onvif.updateImageWhen.option.45 = During Motion and Audio Alarms (45)
thing-type.config.ipcamera.onvif.username.label = Username
thing-type.config.ipcamera.onvif.username.description = Enter the User name used to connect to your camera. Leave blank if your camera does not use login details.
thing-type.config.ipcamera.reolink.alarmInputUrl.label = Alarm Input URL
thing-type.config.ipcamera.reolink.alarmInputUrl.description = Leave blank to use the ffmpegInput as the source for detecting motion with FFmpeg, or enter any HTTP or RTSP URL. TIP: Using a low res source can save CPU usage.
thing-type.config.ipcamera.reolink.ffmpegInput.label = FFmpeg Input
thing-type.config.ipcamera.reolink.ffmpegInput.description = Leave this blank to use the auto detected RTSP address, or enter a URL for any type of stream that FFmpeg can use as an input.
thing-type.config.ipcamera.reolink.ffmpegInputOptions.label = FFmpeg Input Options
thing-type.config.ipcamera.reolink.ffmpegInputOptions.description = This gives you direct access to specify FFmpeg options before the -i.
thing-type.config.ipcamera.reolink.ffmpegLocation.label = FFmpeg Install Location
thing-type.config.ipcamera.reolink.ffmpegLocation.description = The full path including the filename for where you have installed FFmpeg. For windows use this format, c:\ffmpeg\bin\ffmpeg.exe
thing-type.config.ipcamera.reolink.ffmpegOutput.label = FFmpeg Output Folder
thing-type.config.ipcamera.reolink.ffmpegOutput.description = Leave this blank and the binding will use the openHAB userdata folder. Alternatively, a unique path for each camera that ends with a slash and has write permissions can be entered.
thing-type.config.ipcamera.reolink.gifOutOptions.label = GIF Out Options
thing-type.config.ipcamera.reolink.gifOutOptions.description = This gives you direct access to specify your own FFmpeg options to be used for animated GIF files.
thing-type.config.ipcamera.reolink.gifPreroll.label = GIF Preroll
thing-type.config.ipcamera.reolink.gifPreroll.description = Store this many snapshots from BEFORE you trigger a GIF creation.
thing-type.config.ipcamera.reolink.group.FFmpeg Setup.label = FFmpeg Settings
thing-type.config.ipcamera.reolink.group.FFmpeg Setup.description = Settings that setup or effect the video stream.
thing-type.config.ipcamera.reolink.group.Image ch Settings.label = Image channel settings
thing-type.config.ipcamera.reolink.group.Image ch Settings.description = Settings for the image channel features which is not recommended to be used. See readme for more info.
thing-type.config.ipcamera.reolink.group.Settings.label = Main Settings
thing-type.config.ipcamera.reolink.group.Settings.description = Settings required to connect to the camera.
thing-type.config.ipcamera.reolink.hlsOutOptions.label = HLS Out Options
thing-type.config.ipcamera.reolink.hlsOutOptions.description = This gives you direct access to specify your own FFmpeg options to be used.
thing-type.config.ipcamera.reolink.ipAddress.label = IP Address
thing-type.config.ipcamera.reolink.ipAddress.description = Use this format 192.168.1.2 and do not include the port number.
thing-type.config.ipcamera.reolink.ipWhitelist.label = IP Whitelist
thing-type.config.ipcamera.reolink.ipWhitelist.description = Enter any IP's inside (brackets) that you wish to allow to access the video stream. 'DISABLE' will allow all devices on your network unrestricted access.
thing-type.config.ipcamera.reolink.mjpegOptions.label = MJPEG Options
thing-type.config.ipcamera.reolink.mjpegOptions.description = This gives you direct access to specify your own FFmpeg options to be used for MJPEG streams.
thing-type.config.ipcamera.reolink.mjpegUrl.label = MJPEG URL
thing-type.config.ipcamera.reolink.mjpegUrl.description = Leave this blank to use the auto detected URL, or enter a full HTTP address to where a MJPEG stream can be watched if entered into any browser.
thing-type.config.ipcamera.reolink.motionOptions.label = Motion Options
thing-type.config.ipcamera.reolink.motionOptions.description = This gives you direct access to specify your own FFmpeg options to be used for detecting motion.
thing-type.config.ipcamera.reolink.mp4OutOptions.label = MP4 Out Options
thing-type.config.ipcamera.reolink.mp4OutOptions.description = This gives you direct access to specify your own FFmpeg options to be used for recording MP4 files.
thing-type.config.ipcamera.reolink.nvrChannel.label = NVR Input Channel
thing-type.config.ipcamera.reolink.nvrChannel.description = Set this to 0 if it is a stand alone camera, or to the input channel number of your NVR that the camera is connected to.
thing-type.config.ipcamera.reolink.onvifMediaProfile.label = ONVIF Media Profile
thing-type.config.ipcamera.reolink.onvifMediaProfile.description = Cameras can supply more than one stream at different resolutions and formats. 0 selects the main-stream and 1 or above are the sub-streams. Sometimes you need to turn on sub-streams in the cameras setup before they can be used.
thing-type.config.ipcamera.reolink.onvifPort.label = ONVIF Port
thing-type.config.ipcamera.reolink.onvifPort.description = The port your camera uses for ONVIF connections. This is needed for PTZ movement, alarm events and auto discovery of RTSP and snapshot URLs.
thing-type.config.ipcamera.reolink.password.label = Password
thing-type.config.ipcamera.reolink.password.description = Enter the password for your camera. Leave blank if your camera does not use one.
thing-type.config.ipcamera.reolink.pollTime.label = Poll Time
thing-type.config.ipcamera.reolink.pollTime.description = Most features are made on demand and not polled, but some features require a regular snapshot to work. Default is "1000" which is 1 second.
thing-type.config.ipcamera.reolink.port.label = Port for HTTP
thing-type.config.ipcamera.reolink.port.description = This port will be used for HTTP calls for fetching the snapshot and alarm states.
thing-type.config.ipcamera.reolink.ptzContinuous.label = Use Continuous PTZ
thing-type.config.ipcamera.reolink.ptzContinuous.description = Select if you want Relative (false) or Continuous (true) movements.
thing-type.config.ipcamera.reolink.snapshotOptions.label = Snapshot Options
thing-type.config.ipcamera.reolink.snapshotOptions.description = Specify your own FFmpeg options to be used when creating snapshots from RTSP.
thing-type.config.ipcamera.reolink.snapshotUrl.label = Snapshot URL
thing-type.config.ipcamera.reolink.snapshotUrl.description = Leave blank to use the autodetected URL for snapshots, or enter a HTTP URL to where a snapshot can be seen if entered into any browser.
thing-type.config.ipcamera.reolink.updateImageWhen.label = Update Image Channel When:
thing-type.config.ipcamera.reolink.updateImageWhen.description = The Image channel can be set to update in a number of ways. Recommend you set this to never updates as per the readme.
thing-type.config.ipcamera.reolink.updateImageWhen.option.0 = Image channel never updates (0)
thing-type.config.ipcamera.reolink.updateImageWhen.option.1 = Image channel follows pollImage (1)
thing-type.config.ipcamera.reolink.updateImageWhen.option.2 = Start Motion Alarm (2)
thing-type.config.ipcamera.reolink.updateImageWhen.option.3 = Start Audio Alarm (3)
thing-type.config.ipcamera.reolink.updateImageWhen.option.23 = Start of Motion and Audio Alarms (23)
thing-type.config.ipcamera.reolink.updateImageWhen.option.4 = During Motion Alarm (4)
thing-type.config.ipcamera.reolink.updateImageWhen.option.5 = During Audio Alarm (5)
thing-type.config.ipcamera.reolink.updateImageWhen.option.45 = During Motion and Audio Alarms (45)
thing-type.config.ipcamera.reolink.useToken.label = Use API Token
thing-type.config.ipcamera.reolink.useToken.description = True if you want to use a Token, or false to use the user/password in each URL sent.
thing-type.config.ipcamera.reolink.username.label = Username
thing-type.config.ipcamera.reolink.username.description = Enter the User name used to connect to your camera. Leave blank if your camera does not use login details.
# channel types
@ -555,6 +623,8 @@ channel-type.ipcamera.activateAlarmOutput.label = Alarm Output 1 ON/OFF
channel-type.ipcamera.activateAlarmOutput.description = You can use the cameras output to trigger a device like a burglar alarm.
channel-type.ipcamera.activateAlarmOutput2.label = Alarm Output 2 ON/OFF
channel-type.ipcamera.activateAlarmOutput2.description = You can use the cameras output 2 to trigger a device like a burglar alarm.
channel-type.ipcamera.animalAlarm.label = Animal Alarm
channel-type.ipcamera.animalAlarm.description = An animal has triggered the object detection.
channel-type.ipcamera.audioAlarm.label = Audio Alarm
channel-type.ipcamera.audioAlarm.description = Audio has triggered an Alarm.
channel-type.ipcamera.autoLED.label = Auto LED
@ -569,6 +639,8 @@ channel-type.ipcamera.enableAudioAlarm.label = Enable Audio Alarm
channel-type.ipcamera.enableAudioAlarm.description = By using this feature you can stop the camera from sending e-mails when you are having a party.
channel-type.ipcamera.enableExternalAlarmInput.label = Enable Alarm Input 1
channel-type.ipcamera.enableExternalAlarmInput.description = Turn the External Alarm Input feature on and off.
channel-type.ipcamera.enableFTP.label = Enable FTP
channel-type.ipcamera.enableFTP.description = Turn the FTP features of the camera on and off
channel-type.ipcamera.enableFieldDetectionAlarm.label = Enable Field Alarm
channel-type.ipcamera.enableFieldDetectionAlarm.description = By using this feature you can stop the camera from sending e-mails when you are actually home.
channel-type.ipcamera.enableLED.label = LED Controls
@ -581,6 +653,8 @@ channel-type.ipcamera.enablePirAlarm.label = Enable PIR Alarm
channel-type.ipcamera.enablePirAlarm.description = Enable/Disable the PIR Alarm.
channel-type.ipcamera.enablePrivacyMode.label = Enable Privacy Mode
channel-type.ipcamera.enablePrivacyMode.description = Turn the Privacy Mode on and off.
channel-type.ipcamera.enableRecordings.label = Enable Recordings
channel-type.ipcamera.enableRecordings.description = Enable/Disable the cameras internal recordings
channel-type.ipcamera.externalAlarmInput.label = Alarm Input 1
channel-type.ipcamera.externalAlarmInput.description = Some cameras have alarm input wires which can be used to connect to door bells or external PIR sensors.
channel-type.ipcamera.externalAlarmInput2.label = Alarm Input 2

View File

@ -351,6 +351,9 @@
<channel id="storageAlarm" typeId="storageAlarm"/>
<channel id="sceneChangeAlarm" typeId="sceneChangeAlarm"/>
<channel id="tooBrightAlarm" typeId="tooBrightAlarm"/>
<channel id="humanAlarm" typeId="humanAlarm"/>
<channel id="animalAlarm" typeId="animalAlarm"/>
<channel id="carAlarm" typeId="carAlarm"/>
<channel id="tooBlurryAlarm" typeId="tooBlurryAlarm"/>
<channel id="pan" typeId="pan"/>
<channel id="tilt" typeId="tilt"/>
@ -2257,7 +2260,297 @@
</parameter>
</config-description>
</thing-type>
<thing-type id="reolink">
<label>Reolink Camera with API</label>
<description>Use for all Reolink cameras, as they support an API as well as ONVIF.</description>
<channels>
<channel id="startStream" typeId="startStream"/>
<channel id="pollImage" typeId="pollImage"/>
<channel id="image" typeId="image"/>
<channel id="recordingGif" typeId="recordingGif"/>
<channel id="gifHistory" typeId="gifHistory"/>
<channel id="gifHistoryLength" typeId="gifHistoryLength"/>
<channel id="recordingMp4" typeId="recordingMp4"/>
<channel id="mp4History" typeId="mp4History"/>
<channel id="mp4HistoryLength" typeId="mp4HistoryLength"/>
<channel id="lastMotionType" typeId="lastMotionType"/>
<channel id="lastEventData" typeId="lastEventData"/>
<channel id="ffmpegMotionControl" typeId="ffmpegMotionControl"/>
<channel id="ffmpegMotionAlarm" typeId="ffmpegMotionAlarm"/>
<channel id="enableMotionAlarm" typeId="enableMotionAlarm"/>
<channel id="motionAlarm" typeId="motionAlarm"/>
<channel id="cellMotionAlarm" typeId="cellMotionAlarm"/>
<channel id="externalMotion" typeId="externalMotion"/>
<channel id="enableAudioAlarm" typeId="enableAudioAlarm"/>
<channel id="thresholdAudioAlarm" typeId="thresholdAudioAlarm"/>
<channel id="audioAlarm" typeId="audioAlarm"/>
<channel id="pan" typeId="pan"/>
<channel id="tilt" typeId="tilt"/>
<channel id="zoom" typeId="zoom"/>
<channel id="gotoPreset" typeId="gotoPreset"/>
<channel id="mjpegUrl" typeId="mjpegUrl"/>
<channel id="rtspUrl" typeId="rtspUrl"/>
<channel id="imageUrl" typeId="imageUrl"/>
<channel id="hlsUrl" typeId="hlsUrl"/>
<channel id="carAlarm" typeId="carAlarm"/>
<channel id="humanAlarm" typeId="humanAlarm"/>
<channel id="animalAlarm" typeId="animalAlarm"/>
<channel id="faceDetected" typeId="faceDetected"/>
<channel id="autoLED" typeId="autoLED"/>
<channel id="enableLED" typeId="enableLED"/>
<channel id="textOverlay" typeId="textOverlay"/>
<channel id="activateAlarmOutput" typeId="activateAlarmOutput"/>
<channel id="doorBell" typeId="doorBell"/>
<channel id="enableRecordings" typeId="enableRecordings"/>
<channel id="enableFTP" typeId="enableFTP"/>
</channels>
<config-description>
<parameter-group name="Settings">
<label>Main Settings</label>
<description>Settings required to connect to the camera.</description>
<advanced>false</advanced>
</parameter-group>
<parameter-group name="FFmpeg Setup">
<label>FFmpeg Settings</label>
<description>Settings that setup or effect the video stream.</description>
<advanced>false</advanced>
</parameter-group>
<parameter-group name="Image ch Settings">
<label>Image channel settings</label>
<description>Settings for the image channel features which is not recommended to be used. See readme for more info.</description>
<advanced>true</advanced>
</parameter-group>
<parameter name="mjpegUrl" type="text" required="false" groupName="Settings">
<context>url</context>
<label>MJPEG URL</label>
<description>Leave this blank to use the auto detected URL, or enter a full HTTP address to where a MJPEG stream can
be watched if entered into any browser.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="ffmpegInput" type="text" required="false" groupName="FFmpeg Setup">
<context>url</context>
<label>FFmpeg Input</label>
<description>Leave this blank to use the auto detected RTSP address, or enter a URL for any type of stream that
FFmpeg can use as an input.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="ffmpegInputOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>FFmpeg Input Options</label>
<description>This gives you direct access to specify FFmpeg options before the -i.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="ffmpegLocation" type="text" required="false" groupName="FFmpeg Setup">
<label>FFmpeg Install Location</label>
<description>The full path including the filename for where you have installed FFmpeg. For windows use this format,
c:\ffmpeg\bin\ffmpeg.exe
</description>
<default>/usr/bin/ffmpeg</default>
</parameter>
<parameter name="ffmpegOutput" type="text" required="false" groupName="FFmpeg Setup">
<label>FFmpeg Output Folder</label>
<description>Leave this blank and the binding will use the openHAB userdata folder. Alternatively, a unique path for
each camera that ends with a slash and has write permissions can be entered.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="hlsOutOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>HLS Out Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used.
</description>
<default>-strict -2 -f lavfi -i aevalsrc=0 -acodec aac -vcodec copy -hls_flags delete_segments -hls_time 2
-hls_list_size 4</default>
<advanced>true</advanced>
</parameter>
<parameter name="gifOutOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>GIF Out Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used for animated GIF files.
</description>
<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</default>
<advanced>true</advanced>
</parameter>
<parameter name="mp4OutOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>MP4 Out Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used for recording MP4 files.
</description>
<default>-c:v copy -c:a copy</default>
<advanced>true</advanced>
</parameter>
<parameter name="mjpegOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>MJPEG Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used for MJPEG streams.
</description>
<default>-q:v 5 -r 2 -vf scale=640:-2 -update 1</default>
<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>
<description>Leave blank to use the ffmpegInput as the source for detecting motion with FFmpeg, or enter any HTTP
or
RTSP URL. TIP: Using a low res source can save CPU usage.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="motionOptions" type="text" required="false" groupName="FFmpeg Setup">
<label>Motion Options</label>
<description>This gives you direct access to specify your own FFmpeg options to be used for detecting motion.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="gifPreroll" type="integer" required="true" min="0" max="30" groupName="Settings">
<label>GIF Preroll</label>
<description>Store this many snapshots from BEFORE you trigger a GIF creation.
</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="updateImageWhen" type="text" groupName="Image ch Settings" multiple="false">
<label>Update Image Channel When:</label>
<description>The Image channel can be set to update in a number of ways. Recommend you set this to never updates as
per the readme.
</description>
<default>0</default>
<advanced>true</advanced>
<options>
<option value="0">Image channel never updates (0)</option>
<option value="1">Image channel follows pollImage (1)</option>
<option value="2">Start Motion Alarm (2)</option>
<option value="3">Start Audio Alarm (3)</option>
<option value="23">Start of Motion and Audio Alarms (23)</option>
<option value="4">During Motion Alarm (4)</option>
<option value="5">During Audio Alarm (5)</option>
<option value="45">During Motion and Audio Alarms (45)</option>
</options>
</parameter>
<parameter name="ipAddress" type="text" required="true" groupName="Settings">
<context>network-address</context>
<label>IP Address</label>
<description>Use this format 192.168.1.2 and do not include the port number.
</description>
</parameter>
<parameter name="nvrChannel" type="integer" required="true" min="0" max="255" groupName="Settings">
<label>NVR Input Channel</label>
<description>Set this to 0 if it is a stand alone camera, or to the input channel number of your NVR that the
camera
is connected to.
</description>
<default>0</default>
</parameter>
<parameter name="port" type="integer" required="true" min="1" max="65535" groupName="Settings">
<label>Port for HTTP</label>
<description>This port will be used for HTTP calls for fetching the snapshot and alarm states.
</description>
<default>80</default>
<advanced>true</advanced>
</parameter>
<parameter name="snapshotUrl" type="text" required="false" groupName="Settings">
<context>url</context>
<label>Snapshot URL</label>
<description>Leave blank to use the autodetected URL for snapshots, or enter a HTTP URL to where a snapshot can be
seen if entered into any browser.
</description>
<advanced>true</advanced>
</parameter>
<parameter name="onvifPort" type="integer" required="true" groupName="Settings">
<label>ONVIF Port</label>
<description>The port your camera uses for ONVIF connections. This is needed for PTZ movement, alarm events and auto
discovery of RTSP and snapshot URLs.
</description>
<default>8000</default>
<advanced>true</advanced>
</parameter>
<parameter name="username" type="text" required="false" groupName="Settings">
<label>Username</label>
<description>Enter the User name used to connect to your camera. Leave blank if your camera does not use login
details.
</description>
</parameter>
<parameter name="password" type="text" required="false" groupName="Settings">
<context>password</context>
<label>Password</label>
<description>Enter the password for your camera. Leave blank if your camera does not use one.
</description>
</parameter>
<parameter name="useToken" type="boolean" groupName="Settings">
<label>Use API Token</label>
<description>True if you want to use a Token, or false to use the user/password in each URL sent.
</description>
<default>true</default>
</parameter>
<parameter name="ipWhitelist" type="text" required="false" groupName="Settings">
<label>IP Whitelist</label>
<description>Enter any IP's inside (brackets) that you wish to allow to access the video stream. 'DISABLE' will
allow all devices on your network unrestricted access.
</description>
<default>DISABLE</default>
<advanced>true</advanced>
</parameter>
<parameter name="ptzContinuous" type="boolean" groupName="Settings">
<label>Use Continuous PTZ</label>
<description>Select if you want Relative (false) or Continuous (true) movements.
</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="onvifMediaProfile" type="integer" required="true" min="0" max="5" groupName="Settings">
<label>ONVIF Media Profile</label>
<description>Cameras can supply more than one stream at different resolutions and formats. 0 selects the main-stream
and 1 or above are the sub-streams. Sometimes you need to turn on sub-streams in the cameras setup before they can
be used.
</description>
<default>0</default>
</parameter>
<parameter name="pollTime" type="integer" required="true" min="1000" groupName="Settings" unit="ms">
<label>Poll Time</label>
<description>Most features are made on demand and not polled, but some features require a regular snapshot to work.
Default is "1000" which is 1 second.
</description>
<default>1000</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="image" advanced="true">
<item-type>Image</item-type>
<label>Image</label>
@ -2661,6 +2954,18 @@
<description>Turn the Privacy Mode on and off.</description>
</channel-type>
<channel-type id="enableRecordings" advanced="true">
<item-type>Switch</item-type>
<label>Enable Recordings</label>
<description>Enable/Disable the cameras internal recordings</description>
</channel-type>
<channel-type id="enableFTP" advanced="true">
<item-type>Switch</item-type>
<label>Enable FTP</label>
<description>Turn the FTP features of the camera on and off</description>
</channel-type>
<channel-type id="autoLED" advanced="true">
<item-type>Switch</item-type>
<label>Auto LED</label>