2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.ipcamera.internal;
15 import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.List;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.ipcamera.internal.ReolinkState.GetAiStateResponse;
23 import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
24 import org.openhab.core.library.types.OnOffType;
25 import org.openhab.core.library.types.PercentType;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.RefreshType;
31 import com.google.gson.Gson;
33 import io.netty.channel.ChannelDuplexHandler;
34 import io.netty.channel.ChannelHandlerContext;
35 import io.netty.util.ReferenceCountUtil;
38 * The {@link ReolinkHandler} is responsible for handling commands, which are
39 * sent to one of the channels.
41 * @author Matthew Skinner - Initial contribution
45 public class ReolinkHandler extends ChannelDuplexHandler {
46 protected final Gson gson = new Gson();
47 private IpCameraHandler ipCameraHandler;
48 private String requestUrl = "Empty";
50 public ReolinkHandler(IpCameraHandler thingHandler) {
51 ipCameraHandler = thingHandler;
54 public void setURL(String url) {
58 // This handles the incoming http replies back from the camera.
60 public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object msg) throws Exception {
61 if (msg == null || ctx == null) {
65 String content = msg.toString();
66 ipCameraHandler.logger.trace("HTTP Result from {} contains \t:{}:", requestUrl, content);
67 int afterCommand = requestUrl.indexOf("&");
69 if (afterCommand < 0) {
70 cutDownURL = requestUrl;
72 cutDownURL = requestUrl.substring(0, afterCommand);
74 switch (cutDownURL) {// Use a cutdown URL as we can not use variables in a switch()
75 case "/api.cgi?cmd=Login":
76 ipCameraHandler.reolinkAuth = "&token=" + Helper.searchString(content, "\"name\" : \"");
77 if (ipCameraHandler.reolinkAuth.length() > 7) {
78 ipCameraHandler.logger.debug("Your Reolink camera gave a login:{}",
79 ipCameraHandler.reolinkAuth);
80 ipCameraHandler.snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel="
81 + ipCameraHandler.cameraConfig.getNvrChannel() + "&rs=openHAB"
82 + ipCameraHandler.reolinkAuth;
83 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAbility" + ipCameraHandler.reolinkAuth,
84 "[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\""
85 + ipCameraHandler.cameraConfig.getUser() + "\" }}}]");
87 ipCameraHandler.logger.info("Your Reolink camera gave a bad login response:{}", content);
90 case "/api.cgi?cmd=GetAbility": // Used to check what channels the camera supports
91 List<org.openhab.core.thing.Channel> removeChannels = new ArrayList<>();
92 org.openhab.core.thing.Channel channel;
93 if (content.contains("\"supportFtpEnable\": { \"permit\": 0")) {
94 ipCameraHandler.logger.debug("Camera has no Enable FTP support.");
95 channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_FTP);
96 if (channel != null) {
97 removeChannels.add(channel);
100 if (content.contains("\"supportRecordEnable\": { \"permit\": 0")) {
101 ipCameraHandler.logger.debug("Camera has no enable recording support.");
102 channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_RECORDINGS);
103 if (channel != null) {
104 removeChannels.add(channel);
107 if (content.contains("\"floodLight\": { \"permit\": 0")) {
108 ipCameraHandler.logger.debug("Camera has no Flood light support.");
109 channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_LED);
110 if (channel != null) {
111 removeChannels.add(channel);
114 ipCameraHandler.removeChannels(removeChannels);
116 case "/api.cgi?cmd=GetAiState":
117 ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content));
118 GetAiStateResponse[] aiResponse = gson.fromJson(content, GetAiStateResponse[].class);
119 if (aiResponse == null) {
120 ipCameraHandler.logger.debug("The GetAiStateResponse could not be parsed");
123 if (aiResponse[0].value.dog_cat != null) {
124 if (aiResponse[0].value.dog_cat.alarm_state == 1) {
125 ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
127 ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
130 if (aiResponse[0].value.face.alarm_state == 1) {
131 ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
133 ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
135 if (aiResponse[0].value.people.alarm_state == 1) {
136 ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
138 ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
140 if (aiResponse[0].value.vehicle.alarm_state == 1) {
141 ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.ON);
143 ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.OFF);
146 case "/api.cgi?cmd=GetAudioAlarmV20":
147 if (content.contains("\"enable\" : 1")) {
148 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
150 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
153 case "/api.cgi?cmd=GetIrLights":
154 if (content.contains("\"state\" : 0")) {
155 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
157 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
160 case "/api.cgi?cmd=GetMdState":
161 if (content.contains("\"state\" : 0")) {
162 ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
164 ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
169 ReferenceCountUtil.release(msg);
173 // This handles the commands that come from the openHAB event bus.
174 public void handleCommand(ChannelUID channelUID, Command command) {
175 if (command instanceof RefreshType) {
176 switch (channelUID.getId()) {
177 case CHANNEL_ENABLE_MOTION_ALARM:
178 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetMdState" + ipCameraHandler.reolinkAuth);
180 case CHANNEL_ENABLE_AUDIO_ALARM:
181 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
182 "[{ \"cmd\":\"GetAudioAlarmV20\", \"action\":1, \"param\":{ \"channel\": 0}}]");
184 case CHANNEL_AUTO_LED:
185 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetIrLights" + ipCameraHandler.reolinkAuth,
186 "[{ \"cmd\":\"GetIrLights\"}]");
190 } // end of "REFRESH"
191 switch (channelUID.getId()) {
192 case CHANNEL_ACTIVATE_ALARM_OUTPUT: // cameras built in siren
193 if (OnOffType.ON.equals(command)) {
194 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
195 "[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 1, \"channel\": "
196 + ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
198 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
199 "[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 0, \"channel\": "
200 + ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
203 case CHANNEL_AUTO_LED:
204 if (OnOffType.ON.equals(command)) {
205 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
206 "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
207 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Auto\"}}}]");
209 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
210 "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
211 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
214 case CHANNEL_ENABLE_AUDIO_ALARM:
215 if (OnOffType.ON.equals(command)) {
216 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
217 "[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 1,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
219 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
220 "[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 0,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
223 case CHANNEL_ENABLE_FTP:
224 if (OnOffType.ON.equals(command)) {
225 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
226 "[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
227 + ipCameraHandler.cameraConfig.getNvrChannel()
228 + ",\"schedule\" : {\"enable\" : 1}}}}]");
230 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
231 "[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
232 + ipCameraHandler.cameraConfig.getNvrChannel()
233 + ",\"schedule\" : {\"enable\" : 0}}}}]");
236 case CHANNEL_ENABLE_LED:
237 if (OnOffType.OFF.equals(command) || PercentType.ZERO.equals(command)) {
238 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
239 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 0,\"channel\": "
240 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
241 } else if (OnOffType.ON.equals(command)) {
242 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
243 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
244 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
245 } else if (command instanceof PercentType) {
246 int value = ((PercentType) command).toBigDecimal().intValue();
247 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
248 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
249 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
252 case CHANNEL_ENABLE_MOTION_ALARM:
253 if (OnOffType.ON.equals(command)) {
254 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
256 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
259 case CHANNEL_ENABLE_RECORDINGS:
260 if (OnOffType.ON.equals(command)) {
261 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
262 "[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
263 + ipCameraHandler.cameraConfig.getNvrChannel()
264 + ",\"schedule\" : {\"enable\" : 1}}}}]");
266 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
267 "[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
268 + ipCameraHandler.cameraConfig.getNvrChannel()
269 + ",\"schedule\" : {\"enable\" : 0}}}}]");
275 // If a camera does not need to poll a request as often as snapshots, it can be
276 // added here. Binding steps through the list.
277 public List<String> getLowPriorityRequests() {