[ipcamera] Add white LED controls for Dahua and also Email and Push for Reolink with v20 command support (#16144)

* New reolink channels
* extra channel for Dahua.
* Reolink NPE fix
* Fix LED modes and auto.
* Handle NVR channels for new channels
* add nvr channels to Dahua.

Signed-off-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
Matthew Skinner 2024-01-27 01:07:13 +11:00 committed by GitHub
parent 2cd961f442
commit 5a29d7eb74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 669 additions and 266 deletions

View File

@ -213,68 +213,72 @@ If you do not specify any of these, the binding will use the default which shoul
Each camera brand will have different channels depending on how much of the support for an API has been added.
The channels are kept consistent as much as possible from brand to brand to make upgrading to a different camera easier.
| Channel | Type | Description |
|-|-|-|
| `activateAlarmOutput` | Switch | Toggles a cameras relay output 1. |
| `activateAlarmOutput2` | Switch | Toggles a cameras relay output 2. |
| `animalAlarm` | Switch | Toggles when an animal is in view. |
| `audioAlarm` | Switch (read only) | When the camera detects noise above a threshold this switch will move to ON. |
| `autoLED` | Switch | When ON this sets a cameras IR LED to automatically turn on or off. |
| `carAlarm` | Switch | When a car is detected the switch will turn ON. |
| `cellMotionAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `doorBell` | Switch (read only) | Doorbird only, will reflect the status of the doorbell button. |
| `enableAudioAlarm` | Switch | Allows the audio alarm to be turned ON or OFF. |
| `enableExternalAlarmInput` | Switch | Hikvision and Instar allow the Alarm input terminals to be disabled by this control. |
| `enableFieldDetectionAlarm` | Switch | Allows the field detection alarm to be turned ON or OFF. Some cameras will call this the Intrusion Alarm. |
| `enableFTP` | Switch | Turn the cameras internal FTP recordings ON or OFF. |
| `enableLED` | Switch | Turn the IR LED ON or OFF. Some cameras have 3 states the LED can be in, so see the `autoLED` channel. |
| `enableLineCrossingAlarm` | Switch | Turns the line crossing alarm for API cameras, ON and OFF. |
| `enableMotionAlarm` | Switch | Turns the motion alarm ON and OFF for API cameras. This will not effect FFmpeg based alarms which have their own control. |
| `enablePirAlarm` | Switch | Turn PIR sensor ON or OFF. |
| `enableRecordings` | Switch | Turn the cameras internal recordings ON or OFF. |
| `externalAlarmInput` | Switch (read only) | Reflects the status of the alarm input terminals on some cameras. |
| `externalAlarmInput2` | Switch (read only) | Reflects the status of the alarm input 2 terminals on some cameras. |
| `externalLight` | Switch | Some cameras have a dedicated relay output for turning lights on and off with. |
| `externalMotion` | Switch | Can be used to inform the camera if it has motion in its view area. Handy if you own a PIR or any other kind of external sensor. If you use the autofps.mjpeg feature, this could increase the frame rate when a door that was closed is opened. Note: It will not be passed onto your camera and will not trigger any recordings. |
| `faceDetected` | Switch (read only) | When a camera detects a face (API cameras only) this switch will move to ON. |
| `fieldDetectionAlarm` | Switch (read only) | Reflects the cameras status for the field or intrusion alarm. |
| `ffmpegMotionAlarm` | Switch (read only) | The status of the FFmpeg based motion alarm. |
| `ffmpegMotionControl` | Dimmer | This control allows FFmpeg to detect movement from a RTSP or HTTP source and inform openHAB. The channel that will move is called `ffmpegMotionAlarm`. |
| `gifHistory` | String | The 50 most recent filenames the binding has used unless reset. |
| `gifHistoryLength` | Number | How many filenames are in the `gifHistory`. |
| `gotoPreset` | String | ONVIF cameras that can move only. Will cause the camera to move to a preset location. |
| `hlsUrl` | String | The URL for the ipcamera.m3u8 file. |
| `humanAlarm` | Switch | When a camera detects a human this switch will turn ON. |
| `imageUrl` | String | The URL for the ipcamera.jpg file. |
| `itemLeft` | Switch (read only) | Will turn ON if an API camera detects an item has been left behind. |
| `itemTaken` | Switch (read only) | Will turn ON if an API camera detects an item has been stolen. |
| `lastMotionType` | String | Cameras with multiple alarm types will update this with which alarm last detected motion, i.e. a lineCrossing, faceDetection or item stolen alarm. You can also use this to create a timestamp of when the last motion was detected by creating a rule when this channel changes. |
| `lastEventData` | String | Detailed information about the last smart alarm that can contain information like which Line number was crossed and in which direction. The channel `lastMotionType` will hold the name of the alarm that this data belongs to. |
| `lineCrossingAlarm` | Switch (read only) | Will turn on if the API camera detects motion has crossed a line. |
| `mjpegUrl` | String | The URL for the ipcamera.mjpeg stream. |
| `motionAlarm` | Switch (read only) | The status of the 'video motion' events in ONVIF and API cameras. Also see `cellMotionAlarm` as these can give different results. |
| `mp4History` | String | The 50 most recent filenames the binding has used unless reset. |
| `mp4HistoryLength` | Number | How many filenames are in the `mp4History`. Setting this to 0 will clear the history. |
| `pan` | Dimmer | Works with ONVIF cameras that can be moved. |
| `parkingAlarm` | Switch (read only) | When an API camera detects a car, this will turn ON. |
| `pirAlarm` | Switch (read only) | When a camera with PIR ability detects motion, this turns ON. |
| `privacyMode` | Switch | Enable or disable the Privacy Mode of newer Amcrest/Dahua cameras. The camera will move the lens way down and stop the stream. |
| `recordingGif` | Number (read only) | How many seconds recording to GIF for. 0 when file ready. |
| `recordingMp4` | Number (read only) | How many seconds recording to MP4 for. 0 when file ready. |
| `rtspUrl` | String | The URL for the cameras auto detected RTSP stream. |
| `sceneChangeAlarm` | Switch (read only) | When an API camera detects the camera has moved, this turns ON. |
| `startStream` | Switch | Starts the HLS files being created, if it not manually moved it will indicate if the files are being created on demand. |
| `storageAlarm` | Switch (read only) | When an ONVIF cameras storage is full and/or removed, this turns ON. |
| `tamperAlarm` | Switch (read only) | When an ONVIF cameras tamper switch is tripped, this turns ON. |
| `textOverlay` | String | Dahua, Instar and Hikvision can overlay any text you enter here over the video stream. |
| `thresholdAudioAlarm` | Dimmer | This channel can be linked to a Switch and a Slider. The value of the slider is the value in dB that is detected as noise/alarm down from digital full scale. Higher values are more sensitive and will trigger the alarm with quieter / less noise. |
| `tilt` | Dimmer | Works with ONVIF cameras that can be moved. |
| `triggerExternalAlarmInput` | Switch | Hikvision cameras can change if the alarm input terminal is ON when high or low. This can be used to manually cause an alarm input event to occur. |
| `tooBlurryAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `tooBrightAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `tooDarkAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `pollImage` | Switch | This control can be used to manually start and stop using your CPU to create snapshots from a RTSP source. If you have a snapshot URL setup in the binding, only then can this control can be used to update the Image channel. |
| `zoom` | Dimmer | Works with ONVIF cameras that can be moved. |
| Channel | Type | Read/Write | Description |
|-------------------------------|---------|------------|------------------------------------------------------------------------------------------|
| `activateAlarmOutput` | Switch | RW |Toggles a cameras relay output 1. |
| `activateAlarmOutput2` | Switch | RW | Toggles a cameras relay output 2. |
| `animalAlarm` | Switch | RW | Toggles when an animal is in view. |
| `audioAlarm` | Switch | R | When the camera detects noise above a threshold this switch will move to ON. |
| `autoLED` | Switch | RW |When ON this sets a cameras IR LED to automatically turn on or off. |
| `autoWhiteLED` | Switch | RW |When ON this sets a cameras visible white LED to automatically turn on or off. |
| `carAlarm` | Switch | RW | When a car is detected the switch will turn ON. |
| `cellMotionAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `doorBell` | Switch | R | Doorbird only, will reflect the status of the doorbell button. |
| `enableAudioAlarm` | Switch | RW |Allows the audio alarm to be turned ON or OFF. |
| `enableEmail` | Switch | RW |Allows the email features to be turned ON or OFF. |
| `enableExternalAlarmInput` | Switch | RW |Hikvision and Instar allow the Alarm input terminals to be disabled by this control. |
| `enableFieldDetectionAlarm`| Switch | RW |Allows the field detection alarm to be turned ON or OFF. Some cameras will call this the Intrusion Alarm. |
| `enableFTP` | Switch | RW |Turn the cameras internal FTP recordings ON or OFF. |
| `enableLED` | Switch | RW |Turn the IR LED ON or OFF. Some cameras have 3 states the LED can be in, so see the `autoLED` channel. |
| `enableLineCrossingAlarm` | Switch | RW |Turns the line crossing alarm for API cameras, ON and OFF. |
| `enableMotionAlarm` | Switch | RW |Turns the motion alarm ON and OFF for API cameras. This will not effect FFmpeg based alarms which have their own control. |
| `enablePirAlarm` | Switch | RW |Turn PIR sensor ON or OFF. |
| `enablePush` | Switch | RW | Allows the push notification features to be turned ON or OFF. |
| `enableRecordings` | Switch | RW |Turn the cameras internal recordings ON or OFF. |
| `externalAlarmInput` | Switch | R | Reflects the status of the alarm input terminals on some cameras. |
| `externalAlarmInput2` | Switch | R | | Reflects the status of the alarm input 2 terminals on some cameras. |
| `externalLight` | Switch | RW |Some cameras have a dedicated relay output for turning lights on and off with. |
| `externalMotion` | Switch | RW |Can be used to inform the camera if it has motion in its view area. Handy if you own a PIR or any other kind of external sensor. If you use the autofps.mjpeg feature, this could increase the frame rate when a door that was closed is opened. Note: It will not be passed onto your camera and will not trigger any recordings. |
| `faceDetected` | Switch | R | When a camera detects a face (API cameras only) this switch will move to ON. |
| `fieldDetectionAlarm` | Switch | R | Reflects the cameras status for the field or intrusion alarm. |
| `ffmpegMotionAlarm` | Switch | R | The status of the FFmpeg based motion alarm. |
| `ffmpegMotionControl` | Dimmer | RW | This control allows FFmpeg to detect movement from a RTSP or HTTP source and inform openHAB. The channel that will move is called `ffmpegMotionAlarm`. |
| `gifHistory` | String | RW |The 50 most recent filenames the binding has used unless reset. |
| `gifHistoryLength` | Number | RW |How many filenames are in the `gifHistory`. |
| `gotoPreset` | String | RW |ONVIF cameras that can move only. Will cause the camera to move to a preset location. |
| `hlsUrl` | String | RW |The URL for the ipcamera.m3u8 file. |
| `humanAlarm` | Switch | RW |When a camera detects a human this switch will turn ON. |
| `imageUrl` | String | RW |The URL for the ipcamera.jpg file. |
| `itemLeft` | Switch | R | | Will turn ON if an API camera detects an item has been left behind. |
| `itemTaken` | Switch | R | Will turn ON if an API camera detects an item has been stolen. |
| `lastMotionType` | String | RW |Cameras with multiple alarm types will update this with which alarm last detected motion, i.e. a lineCrossing, faceDetection or item stolen alarm. You can also use this to create a timestamp of when the last motion was detected by creating a rule when this channel changes. |
| `lastEventData` | String | RW | Detailed information about the last smart alarm that can contain information like which Line number was crossed and in which direction. The channel `lastMotionType` will hold the name of the alarm that this data belongs to. |
| `lineCrossingAlarm` | Switch | R | Will turn on if the API camera detects motion has crossed a line. |
| `mjpegUrl` | String | RW | The URL for the ipcamera.mjpeg stream. |
| `motionAlarm` | Switch | R | The status of the 'video motion' events in ONVIF and API cameras. Also see `cellMotionAlarm` as these can give different results. |
| `mp4History` | String | RW | The 50 most recent filenames the binding has used unless reset. |
| `mp4HistoryLength` | Number | RW | How many filenames are in the `mp4History`. Setting this to 0 will clear the history. |
| `pan` | Dimmer | RW | Works with ONVIF cameras that can be moved. |
| `parkingAlarm` | Switch | R | When an API camera detects a car, this will turn ON. |
| `pirAlarm` | Switch | R | When a camera with PIR ability detects motion, this turns ON. |
| `privacyMode` | Switch | RW | Enable or disable the Privacy Mode of newer Amcrest/Dahua cameras. The camera will move the lens way down and stop the stream. |
| `recordingGif` | Number | R | How many seconds recording to GIF for. 0 when file ready. |
| `recordingMp4` | Number | R | How many seconds recording to MP4 for. 0 when file ready. |
| `rtspUrl` | String | RW | The URL for the cameras auto detected RTSP stream. |
| `sceneChangeAlarm` | Switch | R | When an API camera detects the camera has moved, this turns ON. |
| `startStream` | Switch | RW | Starts the HLS files being created, if it not manually moved it will indicate if the files are being created on demand. |
| `storageAlarm` | Switch | R | When an ONVIF cameras storage is full and/or removed, this turns ON. |
| `tamperAlarm` | Switch | R | When an ONVIF cameras tamper switch is tripped, this turns ON. |
| `textOverlay` | String | RW | Dahua, Instar and Hikvision can overlay any text you enter here over the video stream. |
| `thresholdAudioAlarm` | Dimmer | RW |This channel can be linked to a Switch and a Slider. The value of the slider is the value in dB that is detected as noise/alarm down from digital full scale. Higher values are more sensitive and will trigger the alarm with quieter / less noise. |
| `tilt` | Dimmer | RW |Works with ONVIF cameras that can be moved. |
| `triggerExternalAlarmInput` | Switch | RW | Hikvision cameras can change if the alarm input terminal is ON when high or low. This can be used to manually cause an alarm input event to occur. |
| `tooBlurryAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `tooBrightAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `tooDarkAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `pollImage` | Switch | RW | This control can be used to manually start and stop using your CPU to create snapshots from a RTSP source. If you have a snapshot URL setup in the binding, only then can this control can be used to update the Image channel. |
| `whiteLED` | Dimmer | RW | Turn the visible white LED ON or OFF and if supported dim from 0-100%. |
| `zoom` | Dimmer | RW | Works with ONVIF cameras that can be moved. |
## Moving PTZ Cameras

View File

@ -43,12 +43,13 @@ import io.netty.util.ReferenceCountUtil;
@NonNullByDefault
public class DahuaHandler extends ChannelDuplexHandler {
private IpCameraHandler ipCameraHandler;
private int nvrChannel;
private int nvrChannelAdjusted;
private Pattern boundaryPattern;
public DahuaHandler(IpCameraHandler handler, int nvrChannel) {
ipCameraHandler = handler;
this.nvrChannel = nvrChannel;
// Most of the API is the NVR channel -1, but some of it is not, like streams and snapshot URLS.
nvrChannelAdjusted = nvrChannel - 1;
boundaryPattern = Pattern.compile("^-- ?myboundary$", Pattern.MULTILINE);
}
@ -209,35 +210,36 @@ public class DahuaHandler extends ChannelDuplexHandler {
private void processSettings(String content) {
// determine if the motion detection is turned on or off.
if (content.contains("table.MotionDetect[0].Enable=true")) {
if (content.contains("table.MotionDetect[" + nvrChannelAdjusted + "].Enable=true")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
} else if (content.contains("table.MotionDetect[" + nvrChannel + "].Enable=false")) {
} else if (content.contains("table.MotionDetect[" + nvrChannelAdjusted + "].Enable=false")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
}
// determine if the audio alarm is turned on or off.
if (content.contains("table.AudioDetect[0].MutationDetect=true")) {
if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationDetect=true")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
} else if (content.contains("table.AudioDetect[0].MutationDetect=false")) {
} else if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationDetect=false")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
}
// Handle AudioMutationThreshold alarm
if (content.contains("table.AudioDetect[0].MutationThreold=")) {
String value = ipCameraHandler.returnValueFromString(content, "table.AudioDetect[0].MutationThreold=");
if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=")) {
String value = ipCameraHandler.returnValueFromString(content,
"table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=");
ipCameraHandler.setChannelState(CHANNEL_THRESHOLD_AUDIO_ALARM, PercentType.valueOf(value));
}
// CrossLineDetection alarm on/off
if (content.contains("table.VideoAnalyseRule[0][1].Enable=true")) {
if (content.contains("table.VideoAnalyseRule[" + nvrChannelAdjusted + "][1].Enable=true")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.ON);
} else if (content.contains("table.VideoAnalyseRule[0][1].Enable=false")) {
} else if (content.contains("table.VideoAnalyseRule[" + nvrChannelAdjusted + "][1].Enable=false")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.OFF);
}
// Privacy Mode on/off
if (content.contains("table.LeLensMask[0].Enable=true")) {
if (content.contains("table.LeLensMask[" + nvrChannelAdjusted + "].Enable=true")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.ON);
} else if (content.contains("table.LeLensMask[0].Enable=false")) {
} else if (content.contains("table.LeLensMask[" + nvrChannelAdjusted + "].Enable=false")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
}
}
@ -269,16 +271,30 @@ public class DahuaHandler extends ChannelDuplexHandler {
if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_ENABLE_AUDIO_ALARM:
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=AudioDetect[0]");
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=getConfig&name=AudioDetect[" + nvrChannelAdjusted + "]");
return;
case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=VideoAnalyseRule");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=VideoAnalyseRule["
+ nvrChannelAdjusted + "]");
return;
case CHANNEL_ENABLE_MOTION_ALARM:
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=MotionDetect[0]");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=MotionDetect["
+ nvrChannelAdjusted + "]");
return;
case CHANNEL_ENABLE_PRIVACY_MODE:
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=LeLensMask[0]");
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=getConfig&name=LeLensMask[" + nvrChannelAdjusted + "]");
return;
case CHANNEL_AUTO_LED:
case CHANNEL_ENABLE_LED:
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=getConfig&name=Light[" + nvrChannelAdjusted + "]");
return;
case CHANNEL_AUTO_WHITE_LED:
case CHANNEL_WHITE_LED:
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=getConfig&name=Lighting_V2["
+ nvrChannelAdjusted + "][0][1].Mode");
return;
}
return;
@ -287,76 +303,109 @@ public class DahuaHandler extends ChannelDuplexHandler {
case CHANNEL_TEXT_OVERLAY:
String text = Helper.encodeSpecialChars(command.toString());
if (text.isEmpty()) {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoWidget["
+ nvrChannelAdjusted + "].CustomTitle[1].EncodeBlend=false");
} else {
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[" + nvrChannelAdjusted
+ "].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].Text=" + text);
}
return;
case CHANNEL_WHITE_LED:
if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) {
// IR to auto and white light off.
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2[" + nvrChannelAdjusted
+ "][0][1].Mode=Off&Lighting_V2[" + nvrChannelAdjusted + "][0][0].Mode=Auto");
} else if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2["
+ nvrChannelAdjusted + "][0][1].Mode=Manual");
} else if (command instanceof PercentType percentCommand) {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting_V2["
+ nvrChannelAdjusted + "][0][1].Mode=Manual&Lighting_V2[" + nvrChannelAdjusted
+ "][0][1].NearLight[0].Light=" + command.toString());
}
return;
case CHANNEL_AUTO_WHITE_LED:
if (OnOffType.ON.equals(command)) {
// we do not know the state anymore as it now will turns on and off via motion
ipCameraHandler.setChannelState(CHANNEL_WHITE_LED, UnDefType.UNDEF);
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].EncodeBlend=false");
"/cgi-bin/configManager.cgi?action=setConfig&AlarmLighting[" + nvrChannelAdjusted
+ "][0].Enable=true&Alarm[2].EventHandler.LightingLink.LightDuration=300");
} else {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].Text="
+ text);
"/cgi-bin/configManager.cgi?action=setConfig&AlarmLighting[" + nvrChannelAdjusted
+ "][0].Enable=false&Alarm[2].EventHandler.LightingLink.LightDuration=0");
}
return;
case CHANNEL_ENABLE_LED:
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Off");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
+ nvrChannelAdjusted + "][0].Mode=Off");
} else if (OnOffType.ON.equals(command)) {
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
+ nvrChannelAdjusted + "][0].Mode=Manual");
} else {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual&Lighting[0][0].MiddleLight[0].Light="
+ command.toString());
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
+ nvrChannelAdjusted + "][0].Mode=Manual&Lighting[" + nvrChannelAdjusted
+ "][0].MiddleLight[0].Light=" + command.toString());
}
return;
case CHANNEL_AUTO_LED:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_LED, UnDefType.UNDEF);
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Auto");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
+ nvrChannelAdjusted + "][0].Mode=Auto");
}
return;
case CHANNEL_THRESHOLD_AUDIO_ALARM:
int threshold = Math.round(Float.valueOf(command.toString()));
if (threshold == 0) {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=1");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
+ nvrChannelAdjusted + "].MutationThreold=1");
} else {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=" + threshold);
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
+ nvrChannelAdjusted + "].MutationThreold=" + threshold);
}
return;
case CHANNEL_ENABLE_AUDIO_ALARM:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
+ nvrChannelAdjusted + "].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1");
} else {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=false");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
+ nvrChannelAdjusted + "].MutationDetect=false");
}
return;
case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=true");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
+ nvrChannelAdjusted + "][1].Enable=true");
} else {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=false");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
+ nvrChannelAdjusted + "][1].Enable=false");
}
return;
case CHANNEL_ENABLE_MOTION_ALARM:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=true&MotionDetect[0].EventHandler.Dejitter=1");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
+ nvrChannelAdjusted + "].Enable=true&MotionDetect[0].EventHandler.Dejitter=1");
} else {
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=false");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
+ nvrChannelAdjusted + "].Enable=false");
}
return;
case CHANNEL_ACTIVATE_ALARM_OUTPUT:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[0].Mode=1");
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[" + nvrChannelAdjusted + "].Mode=1");
} else {
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[0].Mode=0");
ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&AlarmOut[" + nvrChannelAdjusted + "].Mode=0");
}
return;
case CHANNEL_ACTIVATE_ALARM_OUTPUT2:
@ -368,11 +417,11 @@ public class DahuaHandler extends ChannelDuplexHandler {
return;
case CHANNEL_ENABLE_PRIVACY_MODE:
if (OnOffType.OFF.equals(command)) {
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=false");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
+ nvrChannelAdjusted + "].Enable=false");
} else if (OnOffType.ON.equals(command)) {
ipCameraHandler
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=true");
ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
+ nvrChannelAdjusted + "].Enable=true");
}
return;
}

View File

@ -117,8 +117,10 @@ public class IpCameraBindingConstants {
public static final String CHANNEL_TRIGGER_EXTERNAL_ALARM_INPUT = "triggerExternalAlarmInput";
public static final String CHANNEL_EXTERNAL_ALARM_INPUT = "externalAlarmInput";
public static final String CHANNEL_EXTERNAL_ALARM_INPUT2 = "externalAlarmInput2";
public static final String CHANNEL_AUTO_WHITE_LED = "autoWhiteLED";
public static final String CHANNEL_AUTO_LED = "autoLED";
public static final String CHANNEL_ENABLE_LED = "enableLED";
public static final String CHANNEL_WHITE_LED = "whiteLED";
public static final String CHANNEL_ENABLE_PIR_ALARM = "enablePirAlarm";
public static final String CHANNEL_PIR_ALARM = "pirAlarm";
public static final String CHANNEL_CELL_MOTION_ALARM = "cellMotionAlarm";
@ -143,5 +145,7 @@ public class IpCameraBindingConstants {
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_EMAIL = "enableEmail";
public static final String CHANNEL_ENABLE_PUSH = "enablePush";
public static final String CHANNEL_ENABLE_RECORDINGS = "enableRecordings";
}

View File

@ -19,6 +19,7 @@ import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ipcamera.internal.ReolinkState.GetAbilityResponse;
import org.openhab.binding.ipcamera.internal.ReolinkState.GetAiStateResponse;
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
import org.openhab.core.library.types.OnOffType;
@ -27,8 +28,10 @@ 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 org.openhab.core.types.UnDefType;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
@ -80,67 +83,157 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel="
+ ipCameraHandler.cameraConfig.getNvrChannel() + "&rs=openHAB"
+ ipCameraHandler.reolinkAuth;
// admin user in case username in config is a restricted user account. This may cause channels
// to be removed due to restricted user, causing missing channels to be falsely reported as a
// bug.
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAbility" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\""
+ ipCameraHandler.cameraConfig.getUser() + "\" }}}]");
"[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\"admin\" }}}]");
} 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);
org.openhab.core.thing.Channel channel = null;
try {
GetAbilityResponse[] getAbilityResponse = gson.fromJson(content, GetAbilityResponse[].class);
if (getAbilityResponse == null) {
return;
}
}
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 (getAbilityResponse[0].value == null || getAbilityResponse[0].value.ability == null) {
ipCameraHandler.logger.warn("The GetAbilityResponse could not be parsed: {}",
getAbilityResponse[0].error.detail);
return;
}
}
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.reolinkScheduleVersion = getAbilityResponse[0].value.ability.scheduleVersion.ver;
if (getAbilityResponse[0].value.ability.supportFtpEnable == null
|| getAbilityResponse[0].value.ability.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 (getAbilityResponse[0].value.ability.supportRecordEnable == null
|| getAbilityResponse[0].value.ability.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 (getAbilityResponse[0].value.ability.abilityChn[0].supportAiDogCat == null
|| getAbilityResponse[0].value.ability.abilityChn[0].supportAiDogCat.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AiDogCat support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ANIMAL_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.abilityChn[0].supportAiPeople == null
|| getAbilityResponse[0].value.ability.abilityChn[0].supportAiPeople.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AiPeople support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_HUMAN_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.abilityChn[0].supportAiVehicle == null
|| getAbilityResponse[0].value.ability.abilityChn[0].supportAiVehicle.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AiVehicle support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_CAR_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.supportEmailEnable == null
|| getAbilityResponse[0].value.ability.supportEmailEnable.permit == 0) {
ipCameraHandler.logger.debug("Camera has no EmailEnable support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_EMAIL);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.push == null
|| getAbilityResponse[0].value.ability.push.permit == 0) {
ipCameraHandler.logger.debug("Camera has no Push support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_PUSH);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.supportAudioAlarm == null
|| getAbilityResponse[0].value.ability.supportAudioAlarm.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AudioAlarm support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_AUDIO_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.supportAudioAlarmEnable == null
|| getAbilityResponse[0].value.ability.supportAudioAlarmEnable.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AudioAlarm support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_THRESHOLD_AUDIO_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_AUDIO_ALARM);
if (channel != null) {
removeChannels.add(channel);
}
}
if (getAbilityResponse[0].value.ability.abilityChn[0].supportAiFace == null
|| getAbilityResponse[0].value.ability.abilityChn[0].supportAiFace.permit == 0) {
ipCameraHandler.logger.debug("Camera has no AiFace support.");
channel = ipCameraHandler.getThing().getChannel(CHANNEL_FACE_DETECTED);
if (channel != null) {
removeChannels.add(channel);
}
}
} catch (JsonParseException e) {
ipCameraHandler.logger.warn("API command GetAbility may not be supported by the camera");
}
if (channel != null) {
ipCameraHandler.removeChannels(removeChannels);
}
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.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);
try {
GetAiStateResponse[] aiResponse = gson.fromJson(content, GetAiStateResponse[].class);
if (aiResponse == null) {
return;
}
if (aiResponse[0].value == null) {
ipCameraHandler.logger.debug("The GetAiStateResponse could not be parsed: {}",
aiResponse[0].error.detail);
return;
}
if (aiResponse[0].value.dogCat.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
}
if (aiResponse[0].value.face.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
}
if (aiResponse[0].value.people.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.OFF);
}
if (aiResponse[0].value.vehicle.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.ON);
} else {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.OFF);
}
} catch (JsonParseException e) {
ipCameraHandler.logger.debug("API GetAiState is not supported by the camera.");
}
break;
case "/api.cgi?cmd=GetAudioAlarm":
case "/api.cgi?cmd=GetAudioAlarmV20":
if (content.contains("\"enable\" : 1")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
@ -155,6 +248,13 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetMdAlarm":
if (content.contains("00000")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetMdState":
if (content.contains("\"state\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
@ -162,6 +262,37 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetEmail":
case "/api.cgi?cmd=GetEmailV20":
if (content.contains("\"enable\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetPush":
case "/api.cgi?cmd=GetPushV20":
if (content.contains("\"enable\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
}
break;
case "/api.cgi?cmd=GetWhiteLed":
if (content.contains("\"state\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_WHITE_LED, OnOffType.OFF);
} else {
ipCameraHandler.setChannelState(CHANNEL_WHITE_LED, OnOffType.ON);
}
break;
case "/cgi-bin/api.cgi?cmd=Snap":
break;
default:
if (!cutDownURL.contains("cmd=Set")) {// ignore the responses from all Setxxxxx commands
ipCameraHandler.logger.warn(
"URL {} is not handled currently by the binding, please report this message",
cutDownURL);
}
}
} finally {
ReferenceCountUtil.release(msg);
@ -173,7 +304,9 @@ public class ReolinkHandler extends ChannelDuplexHandler {
if (command instanceof RefreshType) {
switch (channelUID.getId()) {
case CHANNEL_ENABLE_MOTION_ALARM:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetMdState" + ipCameraHandler.reolinkAuth);
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetMdAlarm" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"GetMdAlarm\", \"action\": 1,\"param\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + "}}]");
break;
case CHANNEL_ENABLE_AUDIO_ALARM:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
@ -183,6 +316,20 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetIrLights" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetIrLights\"}]");
break;
case CHANNEL_AUTO_WHITE_LED:
case CHANNEL_WHITE_LED:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"GetWhiteLed\",\"action\": 0,\"param\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + "}}]");
break;
case CHANNEL_ENABLE_EMAIL:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetEmailV20" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetEmailV20\"}]");
break;
case CHANNEL_ENABLE_PUSH:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetPushV20" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetPush\"}]");
break;
}
return;
} // end of "REFRESH"
@ -209,43 +356,108 @@ public class ReolinkHandler extends ChannelDuplexHandler {
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
}
break;
case CHANNEL_AUTO_WHITE_LED:
if (OnOffType.ON.equals(command)) {
ipCameraHandler.setChannelState(CHANNEL_WHITE_LED, UnDefType.UNDEF);
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetWhiteLed\",\"param\":{\"WhiteLed\":{\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ", \"mode\": 1}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetWhiteLed\",\"param\":{\"WhiteLed\":{\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ", \"mode\": 0}}}]");
}
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\"}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetAudioAlarmV20\",\"param\":{\"Audio\" : {\"enable\" : 1}}}]");
} else {
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\"}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarmV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetAudioAlarmV20\",\"param\":{\"Audio\" : {\"enable\" : 0}}}]");
} 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}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtpV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtpV20\",\"param\":{\"Ftp\" : {\"enable\" : 1}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Ftp\" : {\"schedule\" : {\"enable\" : 1}}}}]");
}
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtpV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtpV20\",\"param\":{\"Ftp\" : {\"enable\" : 0}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Ftp\" : {\"schedule\" : {\"enable\" : 0}}}}]");
}
}
break;
case CHANNEL_ENABLE_EMAIL:
if (OnOffType.ON.equals(command)) {
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetEmailV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetEmailV20\",\"param\":{\"Email\" : {\"enable\" : 1}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetEmail" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetEmail\",\"param\":{\"Email\" : {\"schedule\" : {\"enable\" : 1}}}}]");
}
} else {
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetEmailV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetEmailV20\",\"param\":{\"Email\" : {\"enable\" : 0}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetEmail" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetEmail\",\"param\":{\"Email\" : {\"schedule\" : {\"enable\" : 0}}}}]");
}
}
break;
case CHANNEL_ENABLE_PUSH:
if (OnOffType.ON.equals(command)) {
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetPushV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetPushV20\",\"param\":{\"Push\":{\"enable\":1}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetPush" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetPush\",\"param\":{\"Push\" : {\"schedule\" : {\"enable\" : 1}}}}]");
}
} else {
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetPushV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetPushV20\",\"param\":{\"Push\":{\"enable\":0}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetPush" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetPush\",\"param\":{\"Push\" : {\"schedule\" : {\"enable\" : 0}}}}]");
}
}
break;
case CHANNEL_ENABLE_LED:
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
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 percentCommand) {
int value = percentCommand.toBigDecimal().intValue();
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
+ "}}}]");
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
} else if (OnOffType.ON.equals(command) || command instanceof PercentType percentCommand) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"On\"}}}]");
} else {
ipCameraHandler.logger.warn("Unsupported command sent to enableLED channel");
}
case CHANNEL_ENABLE_MOTION_ALARM:
if (OnOffType.ON.equals(command)) {
@ -256,17 +468,46 @@ public class ReolinkHandler extends ChannelDuplexHandler {
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}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRecV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRecV20\",\"param\":{\"Rec\":{\"enable\":1}}}]");
} else {
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}}}}]");
if (ipCameraHandler.reolinkScheduleVersion == 1) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRecV20" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRecV20\",\"param\":{\"Rec\":{\"enable\":0}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
}
}
break;
case CHANNEL_WHITE_LED:
ipCameraHandler.setChannelState(CHANNEL_AUTO_WHITE_LED, OnOffType.OFF);
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\": 0}}}]");
} else if (command instanceof PercentType percentCommand) {
int value = percentCommand.toBigDecimal().intValue();
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
+ "}}}]");
}
}
}

View File

@ -14,8 +14,10 @@ package org.openhab.binding.ipcamera.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ReolinkState} class holds the state and GSON parsed replies for a single Reolink Camera.
* The {@link ReolinkState} DTO holds the state and GSON parsed replies from a Reolink Camera.
*
* @author Matthew Skinner - Initial contribution
*/
@ -24,19 +26,59 @@ public class ReolinkState {
public class GetAiStateResponse {
public class Value {
public class Alarm {
public int alarm_state = 0;
public int support = 0;
@SerializedName(value = "alarmState", alternate = { "alarm_state" }) // alarm_state is used in json
public int alarmState = 0;
}
public int channel = 0;
public Alarm dog_cat = new Alarm();
@SerializedName(value = "dogCat", alternate = { "dog_cat" }) // dog_cat is used in json
public Alarm dogCat = 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 class Error {
public String detail = "";
}
public Value value = new Value();
public Error error = new Error();
}
public class GetAbilityResponse {
public class Value {
public class Ability {
public class AbilityKey {
public int permit = 0;
public int ver = 0;
}
public class AbilityChn {
public AbilityKey supportAiFace = new AbilityKey();
public AbilityKey supportAiPeople = new AbilityKey();
public AbilityKey supportAiVehicle = new AbilityKey();
public AbilityKey supportAiDogCat = new AbilityKey();
}
public AbilityChn[] abilityChn = new AbilityChn[1];
public AbilityKey push = new AbilityKey();
public AbilityKey scheduleVersion = new AbilityKey();
public AbilityKey supportAudioAlarm = new AbilityKey();
public AbilityKey supportAudioAlarmEnable = new AbilityKey();
public AbilityKey supportEmailEnable = new AbilityKey();
public AbilityKey supportFtpEnable = new AbilityKey();
public AbilityKey supportRecordEnable = new AbilityKey();
}
@SerializedName(value = "ability", alternate = { "Ability" }) // uses uppercase A
public Ability ability = new Ability();
}
public class Error {
public String detail = "";
}
public Value value = new Value();
public Error error = new Error();
}
}

View File

@ -38,6 +38,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -173,6 +174,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 int reolinkScheduleVersion = 0;
public boolean useBasicAuth = false;
public boolean useDigestAuth = false;
public boolean newInstarApi = false;
@ -183,7 +185,7 @@ public class IpCameraHandler extends BaseThingHandler {
public String rtspUri = "";
public boolean audioAlarmUpdateSnapshot = false;
private boolean motionAlarmUpdateSnapshot = false;
private boolean isOnline = false; // Used so only 1 error is logged when a network issue occurs.
private AtomicBoolean isOnline = new AtomicBoolean(); // Used so only 1 error is logged when a network issue occurs.
private boolean firstAudioAlarm = false;
private boolean firstMotionAlarm = false;
public BigDecimal motionThreshold = BigDecimal.ZERO;
@ -436,7 +438,7 @@ public class IpCameraHandler extends BaseThingHandler {
return false;
}
private String getCorrectUrlFormat(String longUrl) {
public String getCorrectUrlFormat(String longUrl) {
String temp = longUrl;
URL url;
@ -516,13 +518,12 @@ public class IpCameraHandler extends BaseThingHandler {
Ffmpeg localSnapshot = ffmpegSnapshot;
if (localSnapshot != null && !localSnapshot.isAlive()) {
cameraCommunicationError("FFmpeg Snapshots Stopped: Check that your camera can be reached.");
return;
}
return; // ffmpeg snapshot stream is still alive
}
// if ONVIF cam also use connection state which is updated by regular messages to camera
if (thing.getThingTypeUID().getId().equals(ONVIF_THING) && snapshotUri.isEmpty() && onvifCamera.isConnected()) {
// ONVIF cameras get regular event messages from the camera
if (supportsOnvifEvents() && onvifCamera.isConnected()) {
return;
}
@ -536,8 +537,8 @@ public class IpCameraHandler extends BaseThingHandler {
return;
}
}
cameraCommunicationError(
"Connection Timeout: Check your IP and PORT are correct and the camera can be reached.");
cameraCommunicationError("Connection Timeout: Check your IP:" + cameraConfig.getIp() + " and PORT:"
+ cameraConfig.getPort() + " are correct and the camera can be reached.");
}
// Always use this as sendHttpGET(GET/POST/PUT/DELETE, "/foo/bar",null)//
@ -546,7 +547,7 @@ public class IpCameraHandler extends BaseThingHandler {
public void sendHttpRequest(String httpMethod, String httpRequestURLFull, @Nullable String digestString) {
int port = getPortFromShortenedUrl(httpRequestURLFull);
String httpRequestURL = getTinyUrl(httpRequestURLFull);
logger.trace("Sending camera: {}: http://{}:{}{}", httpMethod, cameraConfig.getIp(), port, httpRequestURL);
if (mainBootstrap == null) {
mainBootstrap = new Bootstrap();
mainBootstrap.group(mainEventLoopGroup);
@ -637,12 +638,9 @@ public class IpCameraHandler extends BaseThingHandler {
if (future.isDone() && future.isSuccess()) {
Channel ch = future.channel();
openChannels.add(ch);
if (!isOnline) {
if (cameraConnectionJob != null && !isOnline.get()) {
bringCameraOnline();
}
logger.trace("Sending camera: {}: http://{}:{}{}", httpMethod, cameraConfig.getIp(), port,
httpRequestURL);
openChannel(ch, httpRequestURL);
CommonCameraHandler commonHandler = (CommonCameraHandler) ch.pipeline().get(COMMON_HANDLER);
commonHandler.setURL(httpRequestURLFull);
@ -1350,7 +1348,7 @@ public class IpCameraHandler extends BaseThingHandler {
}
private void bringCameraOnline() {
isOnline = true;
isOnline.set(true);
updateStatus(ThingStatus.ONLINE);
groupTracker.listOfOnlineCameraHandlers.add(this);
groupTracker.listOfOnlineCameraUID.add(getThing().getUID().getId());
@ -1405,6 +1403,12 @@ public class IpCameraHandler extends BaseThingHandler {
}
}
/**
* The {@link pollingCameraConnection} This polls to see if the camera is reachable only until the camera
* successfully connects.
*
*/
void pollingCameraConnection() {
keepMjpegRunning();
if (thing.getThingTypeUID().getId().equals(GENERIC_THING)
@ -1421,12 +1425,6 @@ public class IpCameraHandler extends BaseThingHandler {
return;
}
if (cameraConfig.getOnvifPort() > 0 && !onvifCamera.isConnected()) {
if (onvifCamera.isConnectError()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Camera is not reachable");
} else if (onvifCamera.isRefusedError()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Camera refused connection on ONVIF ports.");
}
logger.debug("About to connect to the IP Camera using the ONVIF PORT at IP: {}:{}", cameraConfig.getIp(),
cameraConfig.getOnvifPort());
onvifCamera.connect(supportsOnvifEvents());
@ -1454,7 +1452,7 @@ public class IpCameraHandler extends BaseThingHandler {
public void cameraCommunicationError(String reason) {
// will try to reconnect again as camera may be rebooting.
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
if (isOnline) { // if already offline dont try reconnecting in 6 seconds, we want 30sec wait.
if (isOnline.get()) { // if already offline dont try reconnecting in 6 seconds, we want 30sec wait.
resetAndRetryConnecting();
}
}
@ -1489,7 +1487,7 @@ public class IpCameraHandler extends BaseThingHandler {
}
public byte[] getSnapshot() {
if (!isOnline) {
if (!isOnline.get()) {
// Single gray pixel JPG to keep streams open when the camera goes offline so they dont stop.
return new byte[] { (byte) 0xff, (byte) 0xd8, (byte) 0xff, (byte) 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, (byte) 0xff, (byte) 0xdb, 0x00, 0x43,
@ -1596,13 +1594,8 @@ public class IpCameraHandler extends BaseThingHandler {
+ 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);
}
} else if (!snapshotPolling) {
checkCameraConnection();
}
break;
case DAHUA_THING:
@ -1736,6 +1729,9 @@ public class IpCameraHandler extends BaseThingHandler {
TimeUnit.MINUTES);
} else {
reolinkAuth = "&user=" + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword();
// The reply to api.cgi?cmd=Login also sends this only with a token
sendHttpPOST("/api.cgi?cmd=GetAbility" + reolinkAuth,
"[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\"admin\" }}}]");
}
if (snapshotUri.isEmpty()) {
if (cameraConfig.getNvrChannel() < 1) {
@ -1766,9 +1762,6 @@ public class IpCameraHandler extends BaseThingHandler {
}
private void tryConnecting() {
int firstDelay = 4;
int normalDelay = 12; // doesn't make sense to have faster retry than CONNECT_TIMEOUT, which is 10 seconds, if
// camera is off
if (!thing.getThingTypeUID().getId().equals(GENERIC_THING)
&& !thing.getThingTypeUID().getId().equals(DOORBIRD_THING) && cameraConfig.getOnvifPort() > 0) {
onvifCamera = new OnvifConnection(this, cameraConfig.getIp() + ":" + cameraConfig.getOnvifPort(),
@ -1776,16 +1769,8 @@ public class IpCameraHandler extends BaseThingHandler {
onvifCamera.setSelectedMediaProfile(cameraConfig.getOnvifMediaProfile());
// Only use ONVIF events if it is not an API camera.
onvifCamera.connect(supportsOnvifEvents());
if (supportsOnvifEvents()) {
// it takes some time to try to retrieve the ONVIF snapshot and stream URLs and update internal members
// on first connect; if connection lost, doesn't make sense to poll to often
firstDelay = 12;
normalDelay = 30;
}
}
cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, firstDelay, normalDelay,
TimeUnit.SECONDS);
cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, 4, 12, TimeUnit.SECONDS);
}
private boolean supportsOnvifEvents() {
@ -1817,7 +1802,7 @@ public class IpCameraHandler extends BaseThingHandler {
}
private void offline() {
isOnline = false;
isOnline.set(false);
snapshotPolling = false;
Future<?> localFuture = pollCameraJob;
if (localFuture != null) {

View File

@ -129,11 +129,9 @@ public class OnvifConnection {
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
private boolean connectError = false;
private boolean refusedError = false;
private boolean isConnected = false;
private int mediaProfileIndex = 0;
private String snapshotUri = "";
// private String snapshotUri = "";
private String rtspUri = "";
private IpCameraHandler ipCameraHandler;
private boolean usingEvents = false;
@ -315,7 +313,6 @@ public class OnvifConnection {
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
} else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
setIsConnected(true);
sendOnvifRequest(RequestType.GetCapabilities, deviceXAddr);
parseDateAndTime(message);
logger.debug("openHAB UTC dateTime is: {}", getUTCdateTime());
} else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
@ -362,11 +359,14 @@ public class OnvifConnection {
} else if (message.contains("GetSnapshotUriResponse")) {
String url = Helper.fetchXML(message, ":MediaUri", ":Uri");
if (!url.isBlank()) {
snapshotUri = removeIPfromUrl(url);
logger.debug("GetSnapshotUri: {}", snapshotUri);
logger.debug("GetSnapshotUri: {}", url);
if (ipCameraHandler.snapshotUri.isEmpty()
&& !"ffmpeg".equals(ipCameraHandler.cameraConfig.getSnapshotUrl())) {
ipCameraHandler.snapshotUri = snapshotUri;
ipCameraHandler.snapshotUri = ipCameraHandler.getCorrectUrlFormat(url);
if (ipCameraHandler.getPortFromShortenedUrl(url) != ipCameraHandler.cameraConfig.getPort()) {
logger.warn("ONVIF is reporting the snapshot does not match the things configured port of:{}",
ipCameraHandler.cameraConfig.getPort());
}
}
}
} else if (message.contains("GetStreamUriResponse")) {
@ -382,12 +382,13 @@ public class OnvifConnection {
}
/**
* The {@link removeIPfromUrl} Will throw away all text before the cameras IP, also removes the IP and the PORT
* The {@link removeIPandPortFromUrl} Will throw away all text before the cameras IP, also removes the IP and the
* PORT
* leaving just the URL.
*
* @author Matthew Skinner - Initial contribution
*/
String removeIPfromUrl(String url) {
String removeIPandPortFromUrl(String url) {
int index = url.indexOf("//");
if (index != -1) {// now remove the :port
index = url.indexOf("/", index + 2);
@ -503,7 +504,8 @@ public class OnvifConnection {
}
public void sendOnvifRequest(RequestType requestType, String xAddr) {
logger.trace("Sending ONVIF request: {}", requestType);
logger.trace("Sending ONVIF request: {} to {}", requestType, xAddr);
int port = extractPortFromUrl(xAddr);
String security = "";
String extraEnvelope = "";
String headerTo = "";
@ -531,12 +533,13 @@ public class OnvifConnection {
headers = "";
}
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"),
removeIPfromUrl(xAddr));
removeIPandPortFromUrl(xAddr));
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
request.headers().add("Content-Type",
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
request.headers().add("Charset", "utf-8");
request.headers().set("Host", ipAddress + ":" + onvifPort);
// Tapo brand have different ports for the event xAddr to the other xAddr, can't use 1 port for all ONVIF calls.
request.headers().set("Host", ipAddress + ":" + port);
request.headers().set("Connection", HttpHeaderValues.CLOSE);
request.headers().set("Accept-Encoding", "gzip, deflate");
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
@ -571,28 +574,30 @@ public class OnvifConnection {
bootstrap = localBootstap;
}
if (!mainEventLoopGroup.isShuttingDown()) {
localBootstap.connect(new InetSocketAddress(ipAddress, onvifPort)).addListener(new ChannelFutureListener() {
// Tapo brand have different ports for the event xAddr to the other xAddr, can't use 1 port for all calls.
localBootstap.connect(new InetSocketAddress(ipAddress, port)).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(@Nullable ChannelFuture future) {
if (future == null) {
return;
}
if (future.isSuccess()) {
connectError = false;
Channel ch = future.channel();
ch.writeAndFlush(request);
} else { // an error occurred
if (future.isDone() && !future.isCancelled()) {
Throwable cause = future.cause();
String msg = cause.getMessage();
logger.trace("connect failed - cause {}", cause.getMessage());
logger.debug("connect failed - cause {}", cause.getMessage());
if (cause instanceof ConnectTimeoutException) {
logger.debug("Camera is not reachable on IP {}", ipAddress);
connectError = true;
usingEvents = false;// Prevent Unsubscribe from being sent
ipCameraHandler.cameraCommunicationError(
"Camera timed out when trying to connect to the ONVIF port:" + port);
} else if ((cause instanceof ConnectException) && msg != null
&& msg.contains("Connection refused")) {
logger.debug("Camera ONVIF port {} is refused.", onvifPort);
refusedError = true;
usingEvents = false;// Prevent Unsubscribe from being sent
ipCameraHandler.cameraCommunicationError(
"Camera refused to connect when using ONVIF to port:" + port);
}
}
if (isConnected) {
@ -944,14 +949,6 @@ public class OnvifConnection {
}
}
public boolean isConnectError() {
return connectError;
}
public boolean isRefusedError() {
return refusedError;
}
public boolean isConnected() {
connecting.lock();
try {
@ -965,8 +962,6 @@ public class OnvifConnection {
connecting.lock();
try {
this.isConnected = isConnected;
this.connectError = false;
this.refusedError = false;
} finally {
connecting.unlock();
}

View File

@ -629,6 +629,8 @@ channel-type.ipcamera.audioAlarm.label = Audio Alarm
channel-type.ipcamera.audioAlarm.description = Audio has triggered an Alarm.
channel-type.ipcamera.autoLED.label = Auto LED
channel-type.ipcamera.autoLED.description = Turn the automatic mode for the LED ON and OFF.
channel-type.ipcamera.autoWhiteLED.label = Auto White LED
channel-type.ipcamera.autoWhiteLED.description = Turn the automatic mode for the visible white LED ON or OFF.
channel-type.ipcamera.carAlarm.label = Car Alarm
channel-type.ipcamera.carAlarm.description = A car has triggered the Vehicle Detection.
channel-type.ipcamera.cellMotionAlarm.label = Cell Motion Alarm
@ -637,6 +639,8 @@ channel-type.ipcamera.doorBell.label = Door Bell
channel-type.ipcamera.doorBell.description = The button has been pushed.
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.enableEmail.label = Enable Email
channel-type.ipcamera.enableEmail.description = Turn the email features of the camera on or off
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
@ -653,6 +657,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.enablePush.label = Enable Push
channel-type.ipcamera.enablePush.description = Turn the push notification features of the camera on or 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
@ -764,5 +770,7 @@ channel-type.ipcamera.tooDarkAlarm.label = Too Dark Alarm
channel-type.ipcamera.tooDarkAlarm.description = Image is too dark.
channel-type.ipcamera.triggerExternalAlarmInput.label = Alarm In 1 TRIGGER high ON/low OFF
channel-type.ipcamera.triggerExternalAlarmInput.description = Change the External Alarm Input to trigger on high or low states.
channel-type.ipcamera.whiteLED.label = White LED
channel-type.ipcamera.whiteLED.description = Turn the visible white LED ON and OFF and if supported 0-100% dimming.
channel-type.ipcamera.zoom.label = Zoom
channel-type.ipcamera.zoom.description = Zoom the camera to a new value.

View File

@ -905,6 +905,8 @@
<channel id="itemTaken" typeId="itemTaken"/>
<channel id="autoLED" typeId="autoLED"/>
<channel id="enableLED" typeId="enableLED"/>
<channel id="whiteLED" typeId="whiteLED"/>
<channel id="autoWhiteLED" typeId="autoWhiteLED"/>
<channel id="textOverlay" typeId="textOverlay"/>
<channel id="pan" typeId="pan"/>
<channel id="tilt" typeId="tilt"/>
@ -921,6 +923,11 @@
<channel id="sceneChangeAlarm" typeId="sceneChangeAlarm"/>
<channel id="tooBlurryAlarm" typeId="tooBlurryAlarm"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description>
<parameter-group name="Settings">
@ -2297,13 +2304,22 @@
<channel id="animalAlarm" typeId="animalAlarm"/>
<channel id="faceDetected" typeId="faceDetected"/>
<channel id="autoLED" typeId="autoLED"/>
<channel id="autoWhiteLED" typeId="autoWhiteLED"/>
<channel id="enableLED" typeId="enableLED"/>
<channel id="whiteLED" typeId="whiteLED"/>
<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"/>
<channel id="enableEmail" typeId="enableEmail"/>
<channel id="enablePush" typeId="enablePush"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description>
<parameter-group name="Settings">
@ -2948,6 +2964,13 @@
<category>Light</category>
</channel-type>
<channel-type id="whiteLED" advanced="true">
<item-type>Dimmer</item-type>
<label>White LED</label>
<description>Turn the visible white LED ON and OFF and if supported 0-100% dimming.</description>
<category>Light</category>
</channel-type>
<channel-type id="enablePrivacyMode" advanced="true">
<item-type>Switch</item-type>
<label>Enable Privacy Mode</label>
@ -2966,12 +2989,30 @@
<description>Turn the FTP features of the camera on and off</description>
</channel-type>
<channel-type id="enableEmail" advanced="true">
<item-type>Switch</item-type>
<label>Enable Email</label>
<description>Turn the email features of the camera on or off</description>
</channel-type>
<channel-type id="enablePush" advanced="true">
<item-type>Switch</item-type>
<label>Enable Push</label>
<description>Turn the push notification features of the camera on or off</description>
</channel-type>
<channel-type id="autoLED" advanced="true">
<item-type>Switch</item-type>
<label>Auto LED</label>
<description>Turn the automatic mode for the LED ON and OFF.</description>
</channel-type>
<channel-type id="autoWhiteLED" advanced="true">
<item-type>Switch</item-type>
<label>Auto White LED</label>
<description>Turn the automatic mode for the visible white LED ON or OFF.</description>
</channel-type>
<channel-type id="externalLight" advanced="true">
<item-type>Switch</item-type>
<label>External Light</label>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="ipcamera:reolink">
<instruction-set targetVersion="1">
<add-channel id="whiteLED">
<type>ipcamera:whiteLED</type>
</add-channel>
<add-channel id="autoWhiteLED">
<type>ipcamera:autoWhiteLED</type>
</add-channel>
<add-channel id="enableEmail">
<type>ipcamera:enableEmail</type>
</add-channel>
<add-channel id="enablePush">
<type>ipcamera:enablePush</type>
</add-channel>
</instruction-set>
</thing-type>
<thing-type uid="ipcamera:dahua">
<instruction-set targetVersion="1">
<add-channel id="whiteLED">
<type>ipcamera:whiteLED</type>
</add-channel>
<add-channel id="autoWhiteLED">
<type>ipcamera:autoWhiteLED</type>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>