2 * Copyright (c) 2010-2024 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.alarm_state == 1) {
124 ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
126 ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
128 if (aiResponse[0].value.face.alarm_state == 1) {
129 ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
131 ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
133 if (aiResponse[0].value.people.alarm_state == 1) {
134 ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
136 ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
138 if (aiResponse[0].value.vehicle.alarm_state == 1) {
139 ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.ON);
141 ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.OFF);
144 case "/api.cgi?cmd=GetAudioAlarmV20":
145 if (content.contains("\"enable\" : 1")) {
146 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
148 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
151 case "/api.cgi?cmd=GetIrLights":
152 if (content.contains("\"state\" : 0")) {
153 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
155 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
158 case "/api.cgi?cmd=GetMdState":
159 if (content.contains("\"state\" : 0")) {
160 ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
162 ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
167 ReferenceCountUtil.release(msg);
171 // This handles the commands that come from the openHAB event bus.
172 public void handleCommand(ChannelUID channelUID, Command command) {
173 if (command instanceof RefreshType) {
174 switch (channelUID.getId()) {
175 case CHANNEL_ENABLE_MOTION_ALARM:
176 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetMdState" + ipCameraHandler.reolinkAuth);
178 case CHANNEL_ENABLE_AUDIO_ALARM:
179 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
180 "[{ \"cmd\":\"GetAudioAlarmV20\", \"action\":1, \"param\":{ \"channel\": 0}}]");
182 case CHANNEL_AUTO_LED:
183 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetIrLights" + ipCameraHandler.reolinkAuth,
184 "[{ \"cmd\":\"GetIrLights\"}]");
188 } // end of "REFRESH"
189 switch (channelUID.getId()) {
190 case CHANNEL_ACTIVATE_ALARM_OUTPUT: // cameras built in siren
191 if (OnOffType.ON.equals(command)) {
192 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
193 "[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 1, \"channel\": "
194 + ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
196 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=AudioAlarmPlay" + ipCameraHandler.reolinkAuth,
197 "[{\"cmd\": \"AudioAlarmPlay\", \"param\": {\"alarm_mode\": \"manul\", \"manual_switch\": 0, \"channel\": "
198 + ipCameraHandler.cameraConfig.getNvrChannel() + " }}]");
201 case CHANNEL_AUTO_LED:
202 if (OnOffType.ON.equals(command)) {
203 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
204 "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
205 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Auto\"}}}]");
207 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
208 "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
209 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
212 case CHANNEL_ENABLE_AUDIO_ALARM:
213 if (OnOffType.ON.equals(command)) {
214 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
215 "[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 1,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
217 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth,
218 "[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 0,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]");
221 case CHANNEL_ENABLE_FTP:
222 if (OnOffType.ON.equals(command)) {
223 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
224 "[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
225 + ipCameraHandler.cameraConfig.getNvrChannel()
226 + ",\"schedule\" : {\"enable\" : 1}}}}]");
228 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
229 "[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
230 + ipCameraHandler.cameraConfig.getNvrChannel()
231 + ",\"schedule\" : {\"enable\" : 0}}}}]");
234 case CHANNEL_ENABLE_LED:
235 if (OnOffType.OFF.equals(command) || PercentType.ZERO.equals(command)) {
236 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
237 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 0,\"channel\": "
238 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
239 } else if (OnOffType.ON.equals(command)) {
240 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
241 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
242 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]");
243 } else if (command instanceof PercentType percentCommand) {
244 int value = percentCommand.toBigDecimal().intValue();
245 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
246 "[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
247 + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
250 case CHANNEL_ENABLE_MOTION_ALARM:
251 if (OnOffType.ON.equals(command)) {
252 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
254 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetMdAlarm" + ipCameraHandler.reolinkAuth);
257 case CHANNEL_ENABLE_RECORDINGS:
258 if (OnOffType.ON.equals(command)) {
259 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
260 "[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
261 + ipCameraHandler.cameraConfig.getNvrChannel()
262 + ",\"schedule\" : {\"enable\" : 1}}}}]");
264 ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
265 "[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
266 + ipCameraHandler.cameraConfig.getNvrChannel()
267 + ",\"schedule\" : {\"enable\" : 0}}}}]");
273 // If a camera does not need to poll a request as often as snapshots, it can be
274 // added here. Binding steps through the list.
275 public List<String> getLowPriorityRequests() {