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.List;
18 import java.util.regex.Pattern;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
23 import org.openhab.core.library.types.DecimalType;
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;
30 import org.openhab.core.types.UnDefType;
32 import io.netty.channel.ChannelDuplexHandler;
33 import io.netty.channel.ChannelHandlerContext;
34 import io.netty.util.ReferenceCountUtil;
37 * The {@link DahuaHandler} is responsible for handling commands, which are
38 * sent to one of the channels.
40 * @author Matthew Skinner - Initial contribution
44 public class DahuaHandler extends ChannelDuplexHandler {
45 private IpCameraHandler ipCameraHandler;
46 private int nvrChannel;
47 private Pattern boundaryPattern;
49 public DahuaHandler(IpCameraHandler handler, int nvrChannel) {
50 ipCameraHandler = handler;
51 this.nvrChannel = nvrChannel;
52 boundaryPattern = Pattern.compile("^-- ?myboundary$", Pattern.MULTILINE);
55 private void processEvent(String content) {
56 int startIndex = content.indexOf("Code=") + 5;// skip Code=
57 int endIndex = content.indexOf(";", startIndex + 1);
58 if (startIndex == -1 || endIndex == -1) {
59 ipCameraHandler.logger.debug("Code= not found in Dahua event. Content was:{}", content);
62 String code = content.substring(startIndex, endIndex);
63 startIndex = endIndex + 8;// skip ;action=
64 endIndex = content.indexOf(";", startIndex);
65 if (startIndex == -1 || endIndex == -1) {
66 ipCameraHandler.logger.debug(";action= not found in Dahua event. Content was:{}", content);
69 String action = content.substring(startIndex, endIndex);
70 startIndex = content.indexOf(";data=", startIndex);
72 endIndex = content.lastIndexOf("}");
74 String data = content.substring(startIndex + 6, endIndex + 1);
75 ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(data));
80 if ("Start".equals(action)) {
81 ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
82 } else if ("Stop".equals(action)) {
83 ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
86 case "TakenAwayDetection":
87 if ("Start".equals(action)) {
88 ipCameraHandler.motionDetected(CHANNEL_ITEM_TAKEN);
89 } else if ("Stop".equals(action)) {
90 ipCameraHandler.noMotionDetected(CHANNEL_ITEM_TAKEN);
94 if ("Start".equals(action)) {
95 ipCameraHandler.motionDetected(CHANNEL_ITEM_LEFT);
96 } else if ("Stop".equals(action)) {
97 ipCameraHandler.noMotionDetected(CHANNEL_ITEM_LEFT);
100 case "SmartMotionVehicle":
101 if ("Start".equals(action)) {
102 ipCameraHandler.motionDetected(CHANNEL_CAR_ALARM);
103 } else if ("Stop".equals(action)) {
104 ipCameraHandler.noMotionDetected(CHANNEL_CAR_ALARM);
107 case "SmartMotionHuman":
108 if ("Start".equals(action)) {
109 ipCameraHandler.motionDetected(CHANNEL_HUMAN_ALARM);
110 } else if ("Stop".equals(action)) {
111 ipCameraHandler.noMotionDetected(CHANNEL_HUMAN_ALARM);
114 case "CrossLineDetection":
115 if ("Start".equals(action)) {
116 ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
117 } else if ("Stop".equals(action)) {
118 ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
122 case "AudioMutation":
123 if ("Start".equals(action)) {
124 ipCameraHandler.audioDetected();
125 } else if ("Stop".equals(action)) {
126 ipCameraHandler.noAudioDetected();
129 case "FaceDetection":
130 if ("Start".equals(action)) {
131 ipCameraHandler.motionDetected(CHANNEL_FACE_DETECTED);
132 } else if ("Stop".equals(action)) {
133 ipCameraHandler.noMotionDetected(CHANNEL_FACE_DETECTED);
136 case "ParkingDetection":
137 if ("Start".equals(action)) {
138 ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.ON);
139 } else if ("Stop".equals(action)) {
140 ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.OFF);
143 case "CrossRegionDetection":
144 if ("Start".equals(action)) {
145 ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
146 } else if ("Stop".equals(action)) {
147 ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
152 if ("Start".equals(action)) {
153 ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
154 } else if ("Stop".equals(action)) {
155 ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
158 case "VideoAbnormalDetection":
159 if ("Start".equals(action)) {
160 ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
161 } else if ("Stop".equals(action)) {
162 ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
166 if ("Start".equals(action)) {
167 ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
168 } else if ("Stop".equals(action)) {
169 ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
173 if ("Start".equals(action)) {
174 if (content.contains("index=0")) {
175 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.ON);
177 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT2, OnOffType.ON);
179 } else if ("Stop".equals(action)) {
180 if (content.contains("index=0")) {
181 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
183 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT2, OnOffType.OFF);
188 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.ON);
190 case "LensMaskClose":
191 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
193 // Skip these so they are not logged.
196 case "NTPAdjustTime":
197 case "StorageChange":
200 case "VideoMotionInfo":
201 case "RtspSessionDisconnect":
202 case "LeFunctionStatusSync":
206 ipCameraHandler.logger.debug("Unrecognised Dahua event, Code={}, action={}", code, action);
210 private void processSettings(String content) {
211 // determine if the motion detection is turned on or off.
212 if (content.contains("table.MotionDetect[0].Enable=true")) {
213 ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
214 } else if (content.contains("table.MotionDetect[" + nvrChannel + "].Enable=false")) {
215 ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
218 // determine if the audio alarm is turned on or off.
219 if (content.contains("table.AudioDetect[0].MutationDetect=true")) {
220 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
221 } else if (content.contains("table.AudioDetect[0].MutationDetect=false")) {
222 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
225 // Handle AudioMutationThreshold alarm
226 if (content.contains("table.AudioDetect[0].MutationThreold=")) {
227 String value = ipCameraHandler.returnValueFromString(content, "table.AudioDetect[0].MutationThreold=");
228 ipCameraHandler.setChannelState(CHANNEL_THRESHOLD_AUDIO_ALARM, PercentType.valueOf(value));
231 // CrossLineDetection alarm on/off
232 if (content.contains("table.VideoAnalyseRule[0][1].Enable=true")) {
233 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.ON);
234 } else if (content.contains("table.VideoAnalyseRule[0][1].Enable=false")) {
235 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.OFF);
237 // Privacy Mode on/off
238 if (content.contains("table.LeLensMask[0].Enable=true")) {
239 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.ON);
240 } else if (content.contains("table.LeLensMask[0].Enable=false")) {
241 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
245 // This handles the incoming http replies back from the camera.
247 public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object msg) throws Exception {
248 if (msg == null || ctx == null) {
252 String content = msg.toString();
253 ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
254 String[] events = boundaryPattern.split(content);
255 if (events.length > 1) {
256 for (int i = 1; i < events.length; i++) {
257 processEvent(events[i]);
260 processSettings(content);
263 ReferenceCountUtil.release(msg);
267 // This handles the commands that come from the openHAB event bus.
268 public void handleCommand(ChannelUID channelUID, Command command) {
269 if (command instanceof RefreshType) {
270 switch (channelUID.getId()) {
271 case CHANNEL_ENABLE_AUDIO_ALARM:
272 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=AudioDetect[0]");
274 case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
275 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=VideoAnalyseRule");
277 case CHANNEL_ENABLE_MOTION_ALARM:
278 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=MotionDetect[0]");
280 case CHANNEL_ENABLE_PRIVACY_MODE:
281 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=LeLensMask[0]");
285 } // end of "REFRESH"
286 switch (channelUID.getId()) {
287 case CHANNEL_TEXT_OVERLAY:
288 String text = Helper.encodeSpecialChars(command.toString());
289 if (text.isEmpty()) {
290 ipCameraHandler.sendHttpGET(
291 "/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].EncodeBlend=false");
293 ipCameraHandler.sendHttpGET(
294 "/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].Text="
298 case CHANNEL_ENABLE_LED:
299 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
300 if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) {
301 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Off");
302 } else if (OnOffType.ON.equals(command)) {
304 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual");
306 ipCameraHandler.sendHttpGET(
307 "/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual&Lighting[0][0].MiddleLight[0].Light="
308 + command.toString());
311 case CHANNEL_AUTO_LED:
312 if (OnOffType.ON.equals(command)) {
313 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LED, UnDefType.UNDEF);
314 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Auto");
317 case CHANNEL_THRESHOLD_AUDIO_ALARM:
318 int threshold = Math.round(Float.valueOf(command.toString()));
320 if (threshold == 0) {
321 ipCameraHandler.sendHttpGET(
322 "/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=1");
324 ipCameraHandler.sendHttpGET(
325 "/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=" + threshold);
328 case CHANNEL_ENABLE_AUDIO_ALARM:
329 if (OnOffType.ON.equals(command)) {
330 ipCameraHandler.sendHttpGET(
331 "/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1");
333 ipCameraHandler.sendHttpGET(
334 "/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=false");
337 case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
338 if (OnOffType.ON.equals(command)) {
339 ipCameraHandler.sendHttpGET(
340 "/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=true");
342 ipCameraHandler.sendHttpGET(
343 "/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=false");
346 case CHANNEL_ENABLE_MOTION_ALARM:
347 if (OnOffType.ON.equals(command)) {
348 ipCameraHandler.sendHttpGET(
349 "/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=true&MotionDetect[0].EventHandler.Dejitter=1");
352 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=false");
355 case CHANNEL_ACTIVATE_ALARM_OUTPUT:
356 if (OnOffType.ON.equals(command)) {
357 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[0].Mode=1");
359 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[0].Mode=0");
362 case CHANNEL_ACTIVATE_ALARM_OUTPUT2:
363 if (OnOffType.ON.equals(command)) {
364 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=1");
366 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=0");
369 case CHANNEL_ENABLE_PRIVACY_MODE:
370 if (OnOffType.OFF.equals(command)) {
372 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=false");
373 } else if (OnOffType.ON.equals(command)) {
375 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=true");
381 // If a camera does not need to poll a request as often as snapshots, it can be
382 // added here. Binding steps through the list.
383 public List<String> getLowPriorityRequests() {