[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
11 changed files with 901 additions and 20 deletions

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>