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.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 nvrChannelAdjusted;
47 private Pattern boundaryPattern;
49 public DahuaHandler(IpCameraHandler handler, int nvrChannel) {
50 ipCameraHandler = handler;
51 // Most of the API is the NVR channel -1, but some of it is not, like streams and snapshot URLS.
52 nvrChannelAdjusted = nvrChannel - 1;
53 boundaryPattern = Pattern.compile("^-- ?myboundary$", Pattern.MULTILINE);
56 private void processEvent(String content) {
57 int startIndex = content.indexOf("Code=") + 5;// skip Code=
58 int endIndex = content.indexOf(";", startIndex + 1);
59 if (startIndex == -1 || endIndex == -1) {
60 ipCameraHandler.logger.debug("Code= not found in Dahua event. Content was:{}", content);
63 String code = content.substring(startIndex, endIndex);
64 startIndex = endIndex + 8;// skip ;action=
65 endIndex = content.indexOf(";", startIndex);
66 if (startIndex == -1 || endIndex == -1) {
67 ipCameraHandler.logger.debug(";action= not found in Dahua event. Content was:{}", content);
70 String action = content.substring(startIndex, endIndex);
71 startIndex = content.indexOf(";data=", startIndex);
73 endIndex = content.lastIndexOf("}");
75 String data = content.substring(startIndex + 6, endIndex + 1);
76 ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(data));
81 if ("Start".equals(action)) {
82 ipCameraHandler.motionDetected(CHANNEL_MOTION_ALARM);
83 } else if ("Stop".equals(action)) {
84 ipCameraHandler.noMotionDetected(CHANNEL_MOTION_ALARM);
87 case "TakenAwayDetection":
88 if ("Start".equals(action)) {
89 ipCameraHandler.motionDetected(CHANNEL_ITEM_TAKEN);
90 } else if ("Stop".equals(action)) {
91 ipCameraHandler.noMotionDetected(CHANNEL_ITEM_TAKEN);
95 if ("Start".equals(action)) {
96 ipCameraHandler.motionDetected(CHANNEL_ITEM_LEFT);
97 } else if ("Stop".equals(action)) {
98 ipCameraHandler.noMotionDetected(CHANNEL_ITEM_LEFT);
101 case "SmartMotionVehicle":
102 if ("Start".equals(action)) {
103 ipCameraHandler.motionDetected(CHANNEL_CAR_ALARM);
104 } else if ("Stop".equals(action)) {
105 ipCameraHandler.noMotionDetected(CHANNEL_CAR_ALARM);
108 case "SmartMotionHuman":
109 if ("Start".equals(action)) {
110 ipCameraHandler.motionDetected(CHANNEL_HUMAN_ALARM);
111 } else if ("Stop".equals(action)) {
112 ipCameraHandler.noMotionDetected(CHANNEL_HUMAN_ALARM);
115 case "CrossLineDetection":
116 if ("Start".equals(action)) {
117 ipCameraHandler.motionDetected(CHANNEL_LINE_CROSSING_ALARM);
118 } else if ("Stop".equals(action)) {
119 ipCameraHandler.noMotionDetected(CHANNEL_LINE_CROSSING_ALARM);
123 case "AudioMutation":
124 if ("Start".equals(action)) {
125 ipCameraHandler.audioDetected();
126 } else if ("Stop".equals(action)) {
127 ipCameraHandler.noAudioDetected();
130 case "FaceDetection":
131 if ("Start".equals(action)) {
132 ipCameraHandler.motionDetected(CHANNEL_FACE_DETECTED);
133 } else if ("Stop".equals(action)) {
134 ipCameraHandler.noMotionDetected(CHANNEL_FACE_DETECTED);
137 case "ParkingDetection":
138 if ("Start".equals(action)) {
139 ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.ON);
140 } else if ("Stop".equals(action)) {
141 ipCameraHandler.setChannelState(CHANNEL_PARKING_ALARM, OnOffType.OFF);
144 case "CrossRegionDetection":
145 if ("Start".equals(action)) {
146 ipCameraHandler.motionDetected(CHANNEL_FIELD_DETECTION_ALARM);
147 } else if ("Stop".equals(action)) {
148 ipCameraHandler.noMotionDetected(CHANNEL_FIELD_DETECTION_ALARM);
153 if ("Start".equals(action)) {
154 ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.ON);
155 } else if ("Stop".equals(action)) {
156 ipCameraHandler.setChannelState(CHANNEL_TOO_DARK_ALARM, OnOffType.OFF);
159 case "VideoAbnormalDetection":
160 if ("Start".equals(action)) {
161 ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.ON);
162 } else if ("Stop".equals(action)) {
163 ipCameraHandler.setChannelState(CHANNEL_SCENE_CHANGE_ALARM, OnOffType.OFF);
167 if ("Start".equals(action)) {
168 ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.ON);
169 } else if ("Stop".equals(action)) {
170 ipCameraHandler.setChannelState(CHANNEL_TOO_BLURRY_ALARM, OnOffType.OFF);
174 if ("Start".equals(action)) {
175 if (content.contains("index=0")) {
176 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.ON);
178 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT2, OnOffType.ON);
180 } else if ("Stop".equals(action)) {
181 if (content.contains("index=0")) {
182 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT, OnOffType.OFF);
184 ipCameraHandler.setChannelState(CHANNEL_EXTERNAL_ALARM_INPUT2, OnOffType.OFF);
189 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.ON);
191 case "LensMaskClose":
192 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
194 // Skip these so they are not logged.
197 case "NTPAdjustTime":
198 case "StorageChange":
201 case "VideoMotionInfo":
202 case "RtspSessionDisconnect":
203 case "LeFunctionStatusSync":
207 ipCameraHandler.logger.debug("Unrecognised Dahua event, Code={}, action={}", code, action);
211 private void processSettings(String content) {
212 // determine if the motion detection is turned on or off.
213 if (content.contains("table.MotionDetect[" + nvrChannelAdjusted + "].Enable=true")) {
214 ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
215 } else if (content.contains("table.MotionDetect[" + nvrChannelAdjusted + "].Enable=false")) {
216 ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
219 // determine if the audio alarm is turned on or off.
220 if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationDetect=true")) {
221 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
222 } else if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationDetect=false")) {
223 ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
226 // Handle AudioMutationThreshold alarm
227 if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=")) {
228 String value = ipCameraHandler.returnValueFromString(content,
229 "table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=");
230 ipCameraHandler.setChannelState(CHANNEL_THRESHOLD_AUDIO_ALARM, PercentType.valueOf(value));
233 // CrossLineDetection alarm on/off
234 if (content.contains("table.VideoAnalyseRule[" + nvrChannelAdjusted + "][1].Enable=true")) {
235 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.ON);
236 } else if (content.contains("table.VideoAnalyseRule[" + nvrChannelAdjusted + "][1].Enable=false")) {
237 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.OFF);
239 // Privacy Mode on/off
240 if (content.contains("table.LeLensMask[" + nvrChannelAdjusted + "].Enable=true")) {
241 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.ON);
242 } else if (content.contains("table.LeLensMask[" + nvrChannelAdjusted + "].Enable=false")) {
243 ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
247 // This handles the incoming http replies back from the camera.
249 public void channelRead(@Nullable ChannelHandlerContext ctx, @Nullable Object msg) throws Exception {
250 if (msg == null || ctx == null) {
254 String content = msg.toString();
255 ipCameraHandler.logger.trace("HTTP Result back from camera is \t:{}:", content);
256 String[] events = boundaryPattern.split(content);
257 if (events.length > 1) {
258 for (int i = 1; i < events.length; i++) {
259 processEvent(events[i]);
262 processSettings(content);
265 ReferenceCountUtil.release(msg);
269 // This handles the commands that come from the openHAB event bus.
270 public void handleCommand(ChannelUID channelUID, Command command) {
271 if (command instanceof RefreshType) {
272 switch (channelUID.getId()) {
273 case CHANNEL_ENABLE_AUDIO_ALARM:
274 ipCameraHandler.sendHttpGET(
275 "/cgi-bin/configManager.cgi?action=getConfig&name=AudioDetect[" + nvrChannelAdjusted + "]");
277 case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
278 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=VideoAnalyseRule["
279 + nvrChannelAdjusted + "]");
281 case CHANNEL_ENABLE_MOTION_ALARM:
282 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=MotionDetect["
283 + nvrChannelAdjusted + "]");
285 case CHANNEL_ENABLE_PRIVACY_MODE:
286 ipCameraHandler.sendHttpGET(
287 "/cgi-bin/configManager.cgi?action=getConfig&name=LeLensMask[" + nvrChannelAdjusted + "]");
289 case CHANNEL_AUTO_LED:
290 case CHANNEL_ENABLE_LED:
291 ipCameraHandler.sendHttpGET(
292 "/cgi-bin/configManager.cgi?action=getConfig&name=Light[" + nvrChannelAdjusted + "]");
294 case CHANNEL_AUTO_WHITE_LED:
295 case CHANNEL_WHITE_LED:
296 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=Lighting_V2["
297 + nvrChannelAdjusted + "][0][1].Mode");
301 } // end of "REFRESH"
302 switch (channelUID.getId()) {
303 case CHANNEL_TEXT_OVERLAY:
304 String text = Helper.encodeSpecialChars(command.toString());
305 if (text.isEmpty()) {
306 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoWidget["
307 + nvrChannelAdjusted + "].CustomTitle[1].EncodeBlend=false");
310 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[" + nvrChannelAdjusted
311 + "].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].Text=" + text);
314 case CHANNEL_WHITE_LED:
315 if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) {
316 // IR to auto and white light off.
317 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
319 .sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2[" + nvrChannelAdjusted
320 + "][0][1].Mode=Off&Lighting_V2[" + nvrChannelAdjusted + "][0][0].Mode=Auto");
321 } else if (OnOffType.ON.equals(command)) {
322 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2["
323 + nvrChannelAdjusted + "][0][1].Mode=Manual");
324 } else if (command instanceof PercentType percentCommand) {
325 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2["
326 + nvrChannelAdjusted + "][0][1].Mode=Manual&Lighting_V2[" + nvrChannelAdjusted
327 + "][0][1].NearLight[0].Light=" + command.toString());
330 case CHANNEL_AUTO_WHITE_LED:
331 if (OnOffType.ON.equals(command)) {
332 // we do not know the state anymore as it now will turns on and off via motion
333 ipCameraHandler.setChannelState(CHANNEL_WHITE_LED, UnDefType.UNDEF);
334 ipCameraHandler.sendHttpGET(
335 "/cgi-bin/configManager.cgi?action=setConfig&AlarmLighting[" + nvrChannelAdjusted
336 + "][0].Enable=true&Alarm[2].EventHandler.LightingLink.LightDuration=300");
338 ipCameraHandler.sendHttpGET(
339 "/cgi-bin/configManager.cgi?action=setConfig&AlarmLighting[" + nvrChannelAdjusted
340 + "][0].Enable=false&Alarm[2].EventHandler.LightingLink.LightDuration=0");
343 case CHANNEL_ENABLE_LED:
344 ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
345 if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) {
346 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
347 + nvrChannelAdjusted + "][0].Mode=Off");
348 } else if (OnOffType.ON.equals(command)) {
349 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
350 + nvrChannelAdjusted + "][0].Mode=Manual");
352 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
353 + nvrChannelAdjusted + "][0].Mode=Manual&Lighting[" + nvrChannelAdjusted
354 + "][0].MiddleLight[0].Light=" + command.toString());
357 case CHANNEL_AUTO_LED:
358 if (OnOffType.ON.equals(command)) {
359 ipCameraHandler.setChannelState(CHANNEL_ENABLE_LED, UnDefType.UNDEF);
360 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
361 + nvrChannelAdjusted + "][0].Mode=Auto");
364 case CHANNEL_THRESHOLD_AUDIO_ALARM:
365 int threshold = Math.round(Float.valueOf(command.toString()));
367 if (threshold == 0) {
368 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
369 + nvrChannelAdjusted + "].MutationThreold=1");
371 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
372 + nvrChannelAdjusted + "].MutationThreold=" + threshold);
375 case CHANNEL_ENABLE_AUDIO_ALARM:
376 if (OnOffType.ON.equals(command)) {
377 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
378 + nvrChannelAdjusted + "].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1");
380 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
381 + nvrChannelAdjusted + "].MutationDetect=false");
384 case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
385 if (OnOffType.ON.equals(command)) {
386 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
387 + nvrChannelAdjusted + "][1].Enable=true");
389 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
390 + nvrChannelAdjusted + "][1].Enable=false");
393 case CHANNEL_ENABLE_MOTION_ALARM:
394 if (OnOffType.ON.equals(command)) {
395 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
396 + nvrChannelAdjusted + "].Enable=true&MotionDetect[0].EventHandler.Dejitter=1");
398 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
399 + nvrChannelAdjusted + "].Enable=false");
402 case CHANNEL_ACTIVATE_ALARM_OUTPUT:
403 if (OnOffType.ON.equals(command)) {
404 ipCameraHandler.sendHttpGET(
405 "/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[" + nvrChannelAdjusted + "].Mode=1");
407 ipCameraHandler.sendHttpGET(
408 "/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[" + nvrChannelAdjusted + "].Mode=0");
411 case CHANNEL_ACTIVATE_ALARM_OUTPUT2:
412 if (OnOffType.ON.equals(command)) {
413 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=1");
415 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[1].Mode=0");
418 case CHANNEL_ENABLE_PRIVACY_MODE:
419 if (OnOffType.OFF.equals(command)) {
420 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
421 + nvrChannelAdjusted + "].Enable=false");
422 } else if (OnOffType.ON.equals(command)) {
423 ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
424 + nvrChannelAdjusted + "].Enable=true");
430 // If a camera does not need to poll a request as often as snapshots, it can be
431 // added here. Binding steps through the list.
432 public List<String> getLowPriorityRequests() {