[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. 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. The channels are kept consistent as much as possible from brand to brand to make upgrading to a different camera easier.
| Channel | Type | Description | | Channel | Type | Read/Write | Description |
|-|-|-| |-------------------------------|---------|------------|------------------------------------------------------------------------------------------|
| `activateAlarmOutput` | Switch | Toggles a cameras relay output 1. | | `activateAlarmOutput` | Switch | RW |Toggles a cameras relay output 1. |
| `activateAlarmOutput2` | Switch | Toggles a cameras relay output 2. | | `activateAlarmOutput2` | Switch | RW | Toggles a cameras relay output 2. |
| `animalAlarm` | Switch | Toggles when an animal is in view. | | `animalAlarm` | Switch | RW | 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. | | `audioAlarm` | Switch | R | 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. | | `autoLED` | Switch | RW |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. | | `autoWhiteLED` | Switch | RW |When ON this sets a cameras visible white LED to automatically turn on or off. |
| `cellMotionAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. | | `carAlarm` | Switch | RW | When a car is detected the switch will turn ON. |
| `doorBell` | Switch (read only) | Doorbird only, will reflect the status of the doorbell button. | | `cellMotionAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `enableAudioAlarm` | Switch | Allows the audio alarm to be turned ON or OFF. | | `doorBell` | Switch | R | Doorbird only, will reflect the status of the doorbell button. |
| `enableExternalAlarmInput` | Switch | Hikvision and Instar allow the Alarm input terminals to be disabled by this control. | | `enableAudioAlarm` | Switch | RW |Allows the audio alarm to be turned ON or OFF. |
| `enableFieldDetectionAlarm` | Switch | Allows the field detection alarm to be turned ON or OFF. Some cameras will call this the Intrusion Alarm. | | `enableEmail` | Switch | RW |Allows the email features to be turned ON or OFF. |
| `enableFTP` | Switch | Turn the cameras internal FTP recordings ON or OFF. | | `enableExternalAlarmInput` | Switch | RW |Hikvision and Instar allow the Alarm input terminals to be disabled by this control. |
| `enableLED` | Switch | Turn the IR LED ON or OFF. Some cameras have 3 states the LED can be in, so see the `autoLED` channel. | | `enableFieldDetectionAlarm`| Switch | RW |Allows the field detection alarm to be turned ON or OFF. Some cameras will call this the Intrusion Alarm. |
| `enableLineCrossingAlarm` | Switch | Turns the line crossing alarm for API cameras, ON and OFF. | | `enableFTP` | Switch | RW |Turn the cameras internal FTP recordings ON or 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. | | `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. |
| `enablePirAlarm` | Switch | Turn PIR sensor ON or OFF. | | `enableLineCrossingAlarm` | Switch | RW |Turns the line crossing alarm for API cameras, ON and OFF. |
| `enableRecordings` | Switch | Turn the cameras internal recordings ON or 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. |
| `externalAlarmInput` | Switch (read only) | Reflects the status of the alarm input terminals on some cameras. | | `enablePirAlarm` | Switch | RW |Turn PIR sensor ON or OFF. |
| `externalAlarmInput2` | Switch (read only) | Reflects the status of the alarm input 2 terminals on some cameras. | | `enablePush` | Switch | RW | Allows the push notification features to be turned ON or OFF. |
| `externalLight` | Switch | Some cameras have a dedicated relay output for turning lights on and off with. | | `enableRecordings` | Switch | RW |Turn the cameras internal recordings ON or OFF. |
| `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. | | `externalAlarmInput` | Switch | R | Reflects the status of the alarm input terminals on some cameras. |
| `faceDetected` | Switch (read only) | When a camera detects a face (API cameras only) this switch will move to ON. | | `externalAlarmInput2` | Switch | R | | Reflects the status of the alarm input 2 terminals on some cameras. |
| `fieldDetectionAlarm` | Switch (read only) | Reflects the cameras status for the field or intrusion alarm. | | `externalLight` | Switch | RW |Some cameras have a dedicated relay output for turning lights on and off with. |
| `ffmpegMotionAlarm` | Switch (read only) | The status of the FFmpeg based motion alarm. | | `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. |
| `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`. | | `faceDetected` | Switch | R | When a camera detects a face (API cameras only) this switch will move to ON. |
| `gifHistory` | String | The 50 most recent filenames the binding has used unless reset. | | `fieldDetectionAlarm` | Switch | R | Reflects the cameras status for the field or intrusion alarm. |
| `gifHistoryLength` | Number | How many filenames are in the `gifHistory`. | | `ffmpegMotionAlarm` | Switch | R | The status of the FFmpeg based motion alarm. |
| `gotoPreset` | String | ONVIF cameras that can move only. Will cause the camera to move to a preset location. | | `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`. |
| `hlsUrl` | String | The URL for the ipcamera.m3u8 file. | | `gifHistory` | String | RW |The 50 most recent filenames the binding has used unless reset. |
| `humanAlarm` | Switch | When a camera detects a human this switch will turn ON. | | `gifHistoryLength` | Number | RW |How many filenames are in the `gifHistory`. |
| `imageUrl` | String | The URL for the ipcamera.jpg file. | | `gotoPreset` | String | RW |ONVIF cameras that can move only. Will cause the camera to move to a preset location. |
| `itemLeft` | Switch (read only) | Will turn ON if an API camera detects an item has been left behind. | | `hlsUrl` | String | RW |The URL for the ipcamera.m3u8 file. |
| `itemTaken` | Switch (read only) | Will turn ON if an API camera detects an item has been stolen. | | `humanAlarm` | Switch | RW |When a camera detects a human this switch will turn ON. |
| `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. | | `imageUrl` | String | RW |The URL for the ipcamera.jpg file. |
| `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. | | `itemLeft` | Switch | R | | Will turn ON if an API camera detects an item has been left behind. |
| `lineCrossingAlarm` | Switch (read only) | Will turn on if the API camera detects motion has crossed a line. | | `itemTaken` | Switch | R | Will turn ON if an API camera detects an item has been stolen. |
| `mjpegUrl` | String | The URL for the ipcamera.mjpeg stream. | | `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. |
| `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. | | `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. |
| `mp4History` | String | The 50 most recent filenames the binding has used unless reset. | | `lineCrossingAlarm` | Switch | R | Will turn on if the API camera detects motion has crossed a line. |
| `mp4HistoryLength` | Number | How many filenames are in the `mp4History`. Setting this to 0 will clear the history. | | `mjpegUrl` | String | RW | The URL for the ipcamera.mjpeg stream. |
| `pan` | Dimmer | Works with ONVIF cameras that can be moved. | | `motionAlarm` | Switch | R | The status of the 'video motion' events in ONVIF and API cameras. Also see `cellMotionAlarm` as these can give different results. |
| `parkingAlarm` | Switch (read only) | When an API camera detects a car, this will turn ON. | | `mp4History` | String | RW | The 50 most recent filenames the binding has used unless reset. |
| `pirAlarm` | Switch (read only) | When a camera with PIR ability detects motion, this turns ON. | | `mp4HistoryLength` | Number | RW | How many filenames are in the `mp4History`. Setting this to 0 will clear the history. |
| `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. | | `pan` | Dimmer | RW | Works with ONVIF cameras that can be moved. |
| `recordingGif` | Number (read only) | How many seconds recording to GIF for. 0 when file ready. | | `parkingAlarm` | Switch | R | When an API camera detects a car, this will turn ON. |
| `recordingMp4` | Number (read only) | How many seconds recording to MP4 for. 0 when file ready. | | `pirAlarm` | Switch | R | When a camera with PIR ability detects motion, this turns ON. |
| `rtspUrl` | String | The URL for the cameras auto detected RTSP stream. | | `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. |
| `sceneChangeAlarm` | Switch (read only) | When an API camera detects the camera has moved, this turns ON. | | `recordingGif` | Number | R | How many seconds recording to GIF for. 0 when file ready. |
| `startStream` | Switch | Starts the HLS files being created, if it not manually moved it will indicate if the files are being created on demand. | | `recordingMp4` | Number | R | How many seconds recording to MP4 for. 0 when file ready. |
| `storageAlarm` | Switch (read only) | When an ONVIF cameras storage is full and/or removed, this turns ON. | | `rtspUrl` | String | RW | The URL for the cameras auto detected RTSP stream. |
| `tamperAlarm` | Switch (read only) | When an ONVIF cameras tamper switch is tripped, this turns ON. | | `sceneChangeAlarm` | Switch | R | When an API camera detects the camera has moved, this turns ON. |
| `textOverlay` | String | Dahua, Instar and Hikvision can overlay any text you enter here over the video stream. | | `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. |
| `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. | | `storageAlarm` | Switch | R | When an ONVIF cameras storage is full and/or removed, this turns ON. |
| `tilt` | Dimmer | Works with ONVIF cameras that can be moved. | | `tamperAlarm` | Switch | R | When an ONVIF cameras tamper switch is tripped, this turns ON. |
| `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. | | `textOverlay` | String | RW | Dahua, Instar and Hikvision can overlay any text you enter here over the video stream. |
| `tooBlurryAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. | | `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. |
| `tooBrightAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. | | `tilt` | Dimmer | RW |Works with ONVIF cameras that can be moved. |
| `tooDarkAlarm` | Switch (read only) | ONVIF cameras only will reflect the status of the ONVIF event of the same name. | | `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. |
| `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. | | `tooBlurryAlarm` | Switch | R | ONVIF cameras only will reflect the status of the ONVIF event of the same name. |
| `zoom` | Dimmer | Works with ONVIF cameras that can be moved. | | `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 ## Moving PTZ Cameras

View File

@ -43,12 +43,13 @@ import io.netty.util.ReferenceCountUtil;
@NonNullByDefault @NonNullByDefault
public class DahuaHandler extends ChannelDuplexHandler { public class DahuaHandler extends ChannelDuplexHandler {
private IpCameraHandler ipCameraHandler; private IpCameraHandler ipCameraHandler;
private int nvrChannel; private int nvrChannelAdjusted;
private Pattern boundaryPattern; private Pattern boundaryPattern;
public DahuaHandler(IpCameraHandler handler, int nvrChannel) { public DahuaHandler(IpCameraHandler handler, int nvrChannel) {
ipCameraHandler = handler; 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); boundaryPattern = Pattern.compile("^-- ?myboundary$", Pattern.MULTILINE);
} }
@ -209,35 +210,36 @@ public class DahuaHandler extends ChannelDuplexHandler {
private void processSettings(String content) { private void processSettings(String content) {
// determine if the motion detection is turned on or off. // 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); 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); ipCameraHandler.setChannelState(CHANNEL_ENABLE_MOTION_ALARM, OnOffType.OFF);
} }
// determine if the audio alarm is turned on or 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); 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); ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.OFF);
} }
// Handle AudioMutationThreshold alarm // Handle AudioMutationThreshold alarm
if (content.contains("table.AudioDetect[0].MutationThreold=")) { if (content.contains("table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=")) {
String value = ipCameraHandler.returnValueFromString(content, "table.AudioDetect[0].MutationThreold="); String value = ipCameraHandler.returnValueFromString(content,
"table.AudioDetect[" + nvrChannelAdjusted + "].MutationThreold=");
ipCameraHandler.setChannelState(CHANNEL_THRESHOLD_AUDIO_ALARM, PercentType.valueOf(value)); ipCameraHandler.setChannelState(CHANNEL_THRESHOLD_AUDIO_ALARM, PercentType.valueOf(value));
} }
// CrossLineDetection alarm on/off // 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); 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); ipCameraHandler.setChannelState(CHANNEL_ENABLE_LINE_CROSSING_ALARM, OnOffType.OFF);
} }
// Privacy Mode on/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); 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); ipCameraHandler.setChannelState(CHANNEL_ENABLE_PRIVACY_MODE, OnOffType.OFF);
} }
} }
@ -269,16 +271,30 @@ public class DahuaHandler extends ChannelDuplexHandler {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_ENABLE_AUDIO_ALARM: 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; return;
case CHANNEL_ENABLE_LINE_CROSSING_ALARM: 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; return;
case CHANNEL_ENABLE_MOTION_ALARM: 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; return;
case CHANNEL_ENABLE_PRIVACY_MODE: 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;
} }
return; return;
@ -287,76 +303,109 @@ public class DahuaHandler extends ChannelDuplexHandler {
case CHANNEL_TEXT_OVERLAY: case CHANNEL_TEXT_OVERLAY:
String text = Helper.encodeSpecialChars(command.toString()); String text = Helper.encodeSpecialChars(command.toString());
if (text.isEmpty()) { 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( 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 { } else {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET(
"/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[0].CustomTitle[1].EncodeBlend=true&VideoWidget[0].CustomTitle[1].Text=" "/cgi-bin/configManager.cgi?action=setConfig&AlarmLighting[" + nvrChannelAdjusted
+ text); + "][0].Enable=false&Alarm[2].EventHandler.LightingLink.LightDuration=0");
} }
return; return;
case CHANNEL_ENABLE_LED: case CHANNEL_ENABLE_LED:
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF); ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
if (DecimalType.ZERO.equals(command) || OnOffType.OFF.equals(command)) { 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)) { } else if (OnOffType.ON.equals(command)) {
ipCameraHandler ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual"); + nvrChannelAdjusted + "][0].Mode=Manual");
} else { } else {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&Lighting["
"/cgi-bin/configManager.cgi?action=setConfig&Lighting[0][0].Mode=Manual&Lighting[0][0].MiddleLight[0].Light=" + nvrChannelAdjusted + "][0].Mode=Manual&Lighting[" + nvrChannelAdjusted
+ command.toString()); + "][0].MiddleLight[0].Light=" + command.toString());
} }
return; return;
case CHANNEL_AUTO_LED: case CHANNEL_AUTO_LED:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_LED, UnDefType.UNDEF); 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; return;
case CHANNEL_THRESHOLD_AUDIO_ALARM: case CHANNEL_THRESHOLD_AUDIO_ALARM:
int threshold = Math.round(Float.valueOf(command.toString())); int threshold = Math.round(Float.valueOf(command.toString()));
if (threshold == 0) { if (threshold == 0) {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=1"); + nvrChannelAdjusted + "].MutationThreold=1");
} else { } else {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationThreold=" + threshold); + nvrChannelAdjusted + "].MutationThreold=" + threshold);
} }
return; return;
case CHANNEL_ENABLE_AUDIO_ALARM: case CHANNEL_ENABLE_AUDIO_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1"); + nvrChannelAdjusted + "].MutationDetect=true&AudioDetect[0].EventHandler.Dejitter=1");
} else { } else {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&AudioDetect["
"/cgi-bin/configManager.cgi?action=setConfig&AudioDetect[0].MutationDetect=false"); + nvrChannelAdjusted + "].MutationDetect=false");
} }
return; return;
case CHANNEL_ENABLE_LINE_CROSSING_ALARM: case CHANNEL_ENABLE_LINE_CROSSING_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
"/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=true"); + nvrChannelAdjusted + "][1].Enable=true");
} else { } else {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule["
"/cgi-bin/configManager.cgi?action=setConfig&VideoAnalyseRule[0][1].Enable=false"); + nvrChannelAdjusted + "][1].Enable=false");
} }
return; return;
case CHANNEL_ENABLE_MOTION_ALARM: case CHANNEL_ENABLE_MOTION_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpGET( ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
"/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=true&MotionDetect[0].EventHandler.Dejitter=1"); + nvrChannelAdjusted + "].Enable=true&MotionDetect[0].EventHandler.Dejitter=1");
} else { } else {
ipCameraHandler ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect["
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&MotionDetect[0].Enable=false"); + nvrChannelAdjusted + "].Enable=false");
} }
return; return;
case CHANNEL_ACTIVATE_ALARM_OUTPUT: case CHANNEL_ACTIVATE_ALARM_OUTPUT:
if (OnOffType.ON.equals(command)) { 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 { } 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; return;
case CHANNEL_ACTIVATE_ALARM_OUTPUT2: case CHANNEL_ACTIVATE_ALARM_OUTPUT2:
@ -368,11 +417,11 @@ public class DahuaHandler extends ChannelDuplexHandler {
return; return;
case CHANNEL_ENABLE_PRIVACY_MODE: case CHANNEL_ENABLE_PRIVACY_MODE:
if (OnOffType.OFF.equals(command)) { if (OnOffType.OFF.equals(command)) {
ipCameraHandler ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=false"); + nvrChannelAdjusted + "].Enable=false");
} else if (OnOffType.ON.equals(command)) { } else if (OnOffType.ON.equals(command)) {
ipCameraHandler ipCameraHandler.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask["
.sendHttpGET("/cgi-bin/configManager.cgi?action=setConfig&LeLensMask[0].Enable=true"); + nvrChannelAdjusted + "].Enable=true");
} }
return; 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_TRIGGER_EXTERNAL_ALARM_INPUT = "triggerExternalAlarmInput";
public static final String CHANNEL_EXTERNAL_ALARM_INPUT = "externalAlarmInput"; public static final String CHANNEL_EXTERNAL_ALARM_INPUT = "externalAlarmInput";
public static final String CHANNEL_EXTERNAL_ALARM_INPUT2 = "externalAlarmInput2"; 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_AUTO_LED = "autoLED";
public static final String CHANNEL_ENABLE_LED = "enableLED"; 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_ENABLE_PIR_ALARM = "enablePirAlarm";
public static final String CHANNEL_PIR_ALARM = "pirAlarm"; public static final String CHANNEL_PIR_ALARM = "pirAlarm";
public static final String CHANNEL_CELL_MOTION_ALARM = "cellMotionAlarm"; 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_HUMAN_ALARM = "humanAlarm";
public static final String CHANNEL_ANIMAL_ALARM = "animalAlarm"; public static final String CHANNEL_ANIMAL_ALARM = "animalAlarm";
public static final String CHANNEL_ENABLE_FTP = "enableFTP"; 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"; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.ReolinkState.GetAiStateResponse;
import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler; import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
import org.openhab.core.library.types.OnOffType; 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.thing.ChannelUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -80,67 +83,157 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel=" ipCameraHandler.snapshotUri = "/cgi-bin/api.cgi?cmd=Snap&channel="
+ ipCameraHandler.cameraConfig.getNvrChannel() + "&rs=openHAB" + ipCameraHandler.cameraConfig.getNvrChannel() + "&rs=openHAB"
+ ipCameraHandler.reolinkAuth; + 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, ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAbility" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\"" "[{ \"cmd\":\"GetAbility\", \"param\":{ \"User\":{ \"userName\":\"admin\" }}}]");
+ ipCameraHandler.cameraConfig.getUser() + "\" }}}]");
} else { } else {
ipCameraHandler.logger.info("Your Reolink camera gave a bad login response:{}", content); ipCameraHandler.logger.info("Your Reolink camera gave a bad login response:{}", content);
} }
break; break;
case "/api.cgi?cmd=GetAbility": // Used to check what channels the camera supports case "/api.cgi?cmd=GetAbility": // Used to check what channels the camera supports
List<org.openhab.core.thing.Channel> removeChannels = new ArrayList<>(); List<org.openhab.core.thing.Channel> removeChannels = new ArrayList<>();
org.openhab.core.thing.Channel channel; org.openhab.core.thing.Channel channel = null;
if (content.contains("\"supportFtpEnable\": { \"permit\": 0")) { try {
ipCameraHandler.logger.debug("Camera has no Enable FTP support."); GetAbilityResponse[] getAbilityResponse = gson.fromJson(content, GetAbilityResponse[].class);
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_FTP); if (getAbilityResponse == null) {
if (channel != null) { return;
removeChannels.add(channel);
} }
} if (getAbilityResponse[0].value == null || getAbilityResponse[0].value.ability == null) {
if (content.contains("\"supportRecordEnable\": { \"permit\": 0")) { ipCameraHandler.logger.warn("The GetAbilityResponse could not be parsed: {}",
ipCameraHandler.logger.debug("Camera has no enable recording support."); getAbilityResponse[0].error.detail);
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_RECORDINGS); return;
if (channel != null) {
removeChannels.add(channel);
} }
} ipCameraHandler.reolinkScheduleVersion = getAbilityResponse[0].value.ability.scheduleVersion.ver;
if (content.contains("\"floodLight\": { \"permit\": 0")) { if (getAbilityResponse[0].value.ability.supportFtpEnable == null
ipCameraHandler.logger.debug("Camera has no Flood light support."); || getAbilityResponse[0].value.ability.supportFtpEnable.permit == 0) {
channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_LED); ipCameraHandler.logger.debug("Camera has no Enable FTP support.");
if (channel != null) { channel = ipCameraHandler.getThing().getChannel(CHANNEL_ENABLE_FTP);
removeChannels.add(channel); 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; break;
case "/api.cgi?cmd=GetAiState": case "/api.cgi?cmd=GetAiState":
ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content)); ipCameraHandler.setChannelState(CHANNEL_LAST_EVENT_DATA, new StringType(content));
GetAiStateResponse[] aiResponse = gson.fromJson(content, GetAiStateResponse[].class); try {
if (aiResponse == null) { GetAiStateResponse[] aiResponse = gson.fromJson(content, GetAiStateResponse[].class);
ipCameraHandler.logger.debug("The GetAiStateResponse could not be parsed"); if (aiResponse == null) {
return; return;
} }
if (aiResponse[0].value.dog_cat.alarm_state == 1) { if (aiResponse[0].value == null) {
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON); ipCameraHandler.logger.debug("The GetAiStateResponse could not be parsed: {}",
} else { aiResponse[0].error.detail);
ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF); return;
} }
if (aiResponse[0].value.face.alarm_state == 1) { if (aiResponse[0].value.dogCat.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.ON);
} else { } else {
ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF); ipCameraHandler.setChannelState(CHANNEL_ANIMAL_ALARM, OnOffType.OFF);
} }
if (aiResponse[0].value.people.alarm_state == 1) { if (aiResponse[0].value.face.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.ON);
} else { } else {
ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.OFF); ipCameraHandler.setChannelState(CHANNEL_FACE_DETECTED, OnOffType.OFF);
} }
if (aiResponse[0].value.vehicle.alarm_state == 1) { if (aiResponse[0].value.people.alarmState == 1) {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_HUMAN_ALARM, OnOffType.ON);
} else { } else {
ipCameraHandler.setChannelState(CHANNEL_CAR_ALARM, OnOffType.OFF); 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; break;
case "/api.cgi?cmd=GetAudioAlarm":
case "/api.cgi?cmd=GetAudioAlarmV20": case "/api.cgi?cmd=GetAudioAlarmV20":
if (content.contains("\"enable\" : 1")) { if (content.contains("\"enable\" : 1")) {
ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_ENABLE_AUDIO_ALARM, OnOffType.ON);
@ -155,6 +248,13 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.ON);
} }
break; 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": case "/api.cgi?cmd=GetMdState":
if (content.contains("\"state\" : 0")) { if (content.contains("\"state\" : 0")) {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF); ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.OFF);
@ -162,6 +262,37 @@ public class ReolinkHandler extends ChannelDuplexHandler {
ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON); ipCameraHandler.setChannelState(CHANNEL_MOTION_ALARM, OnOffType.ON);
} }
break; 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 { } finally {
ReferenceCountUtil.release(msg); ReferenceCountUtil.release(msg);
@ -173,7 +304,9 @@ public class ReolinkHandler extends ChannelDuplexHandler {
if (command instanceof RefreshType) { if (command instanceof RefreshType) {
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_ENABLE_MOTION_ALARM: 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; break;
case CHANNEL_ENABLE_AUDIO_ALARM: case CHANNEL_ENABLE_AUDIO_ALARM:
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetAudioAlarmV20" + ipCameraHandler.reolinkAuth, 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, ipCameraHandler.sendHttpPOST("/api.cgi?cmd=GetIrLights" + ipCameraHandler.reolinkAuth,
"[{ \"cmd\":\"GetIrLights\"}]"); "[{ \"cmd\":\"GetIrLights\"}]");
break; 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; return;
} // end of "REFRESH" } // end of "REFRESH"
@ -209,43 +356,108 @@ public class ReolinkHandler extends ChannelDuplexHandler {
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]"); + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
} }
break; 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: case CHANNEL_ENABLE_AUDIO_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 1,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]"); 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 { } else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetAudioAlarm" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\": \" SetAudioAlarm\",\"param\": {\"Audio\": {\"schedule\": {\"enable\": 0,\"table\": \"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\"}}}}]"); 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; break;
case CHANNEL_ENABLE_FTP: case CHANNEL_ENABLE_FTP:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : " ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtpV20" + ipCameraHandler.reolinkAuth,
+ ipCameraHandler.cameraConfig.getNvrChannel() "[{\"cmd\":\"SetFtpV20\",\"param\":{\"Ftp\" : {\"enable\" : 1}}}]");
+ ",\"schedule\" : {\"enable\" : 1}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetFtp\",\"param\":{\"Ftp\" : {\"schedule\" : {\"enable\" : 1}}}}]");
}
} else { } else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtp" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\":\"SetFtp\",\"param\":{\"Rec\" : {\"channel\" : " ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetFtpV20" + ipCameraHandler.reolinkAuth,
+ ipCameraHandler.cameraConfig.getNvrChannel() "[{\"cmd\":\"SetFtpV20\",\"param\":{\"Ftp\" : {\"enable\" : 0}}}]");
+ ",\"schedule\" : {\"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; break;
case CHANNEL_ENABLE_LED: case CHANNEL_ENABLE_LED:
ipCameraHandler.setChannelState(CHANNEL_AUTO_LED, OnOffType.OFF);
if (OnOffType.OFF.equals(command) || PercentType.ZERO.equals(command)) { if (OnOffType.OFF.equals(command) || PercentType.ZERO.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth, ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 0,\"channel\": " "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]"); + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"Off\"}}}]");
} else if (OnOffType.ON.equals(command)) { } else if (OnOffType.ON.equals(command) || command instanceof PercentType percentCommand) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth, ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetIrLights" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": " "[{\"cmd\": \"SetIrLights\",\"action\": 0,\"param\": {\"IrLights\": {\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1}}}]"); + ipCameraHandler.cameraConfig.getNvrChannel() + ",\"state\": \"On\"}}}]");
} else if (command instanceof PercentType percentCommand) { } else {
int value = percentCommand.toBigDecimal().intValue(); ipCameraHandler.logger.warn("Unsupported command sent to enableLED channel");
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetWhiteLed" + ipCameraHandler.reolinkAuth,
"[{\"cmd\": \"SetWhiteLed\",\"param\": {\"WhiteLed\": {\"state\": 1,\"channel\": "
+ ipCameraHandler.cameraConfig.getNvrChannel() + ",\"mode\": 1,\"bright\": " + value
+ "}}}]");
} }
case CHANNEL_ENABLE_MOTION_ALARM: case CHANNEL_ENABLE_MOTION_ALARM:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
@ -256,17 +468,46 @@ public class ReolinkHandler extends ChannelDuplexHandler {
break; break;
case CHANNEL_ENABLE_RECORDINGS: case CHANNEL_ENABLE_RECORDINGS:
if (OnOffType.ON.equals(command)) { if (OnOffType.ON.equals(command)) {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : " ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRecV20" + ipCameraHandler.reolinkAuth,
+ ipCameraHandler.cameraConfig.getNvrChannel() "[{\"cmd\":\"SetRecV20\",\"param\":{\"Rec\":{\"enable\":1}}}]");
+ ",\"schedule\" : {\"enable\" : 1}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 1}}}}]");
}
} else { } else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth, if (ipCameraHandler.reolinkScheduleVersion == 1) {
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : " ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRecV20" + ipCameraHandler.reolinkAuth,
+ ipCameraHandler.cameraConfig.getNvrChannel() "[{\"cmd\":\"SetRecV20\",\"param\":{\"Rec\":{\"enable\":0}}}]");
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
} else {
ipCameraHandler.sendHttpPOST("/api.cgi?cmd=SetRec" + ipCameraHandler.reolinkAuth,
"[{\"cmd\":\"SetRec\",\"param\":{\"Rec\" : {\"channel\" : "
+ ipCameraHandler.cameraConfig.getNvrChannel()
+ ",\"schedule\" : {\"enable\" : 0}}}}]");
}
} }
break; 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 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 * @author Matthew Skinner - Initial contribution
*/ */
@ -24,19 +26,59 @@ public class ReolinkState {
public class GetAiStateResponse { public class GetAiStateResponse {
public class Value { public class Value {
public class Alarm { public class Alarm {
public int alarm_state = 0; @SerializedName(value = "alarmState", alternate = { "alarm_state" }) // alarm_state is used in json
public int support = 0; public int alarmState = 0;
} }
public int channel = 0; @SerializedName(value = "dogCat", alternate = { "dog_cat" }) // dog_cat is used in json
public Alarm dog_cat = new Alarm(); public Alarm dogCat = new Alarm();
public Alarm face = new Alarm(); public Alarm face = new Alarm();
public Alarm people = new Alarm(); public Alarm people = new Alarm();
public Alarm vehicle = new Alarm(); public Alarm vehicle = new Alarm();
} }
public String cmd = ""; public class Error {
public int code = 0; public String detail = "";
}
public Value value = new Value(); 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.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault; 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() // basicAuth MUST remain private as it holds the cameraConfig.getPassword()
private String basicAuth = ""; private String basicAuth = "";
public String reolinkAuth = "&token=null"; public String reolinkAuth = "&token=null";
public int reolinkScheduleVersion = 0;
public boolean useBasicAuth = false; public boolean useBasicAuth = false;
public boolean useDigestAuth = false; public boolean useDigestAuth = false;
public boolean newInstarApi = false; public boolean newInstarApi = false;
@ -183,7 +185,7 @@ public class IpCameraHandler extends BaseThingHandler {
public String rtspUri = ""; public String rtspUri = "";
public boolean audioAlarmUpdateSnapshot = false; public boolean audioAlarmUpdateSnapshot = false;
private boolean motionAlarmUpdateSnapshot = 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 firstAudioAlarm = false;
private boolean firstMotionAlarm = false; private boolean firstMotionAlarm = false;
public BigDecimal motionThreshold = BigDecimal.ZERO; public BigDecimal motionThreshold = BigDecimal.ZERO;
@ -436,7 +438,7 @@ public class IpCameraHandler extends BaseThingHandler {
return false; return false;
} }
private String getCorrectUrlFormat(String longUrl) { public String getCorrectUrlFormat(String longUrl) {
String temp = longUrl; String temp = longUrl;
URL url; URL url;
@ -516,13 +518,12 @@ public class IpCameraHandler extends BaseThingHandler {
Ffmpeg localSnapshot = ffmpegSnapshot; Ffmpeg localSnapshot = ffmpegSnapshot;
if (localSnapshot != null && !localSnapshot.isAlive()) { if (localSnapshot != null && !localSnapshot.isAlive()) {
cameraCommunicationError("FFmpeg Snapshots Stopped: Check that your camera can be reached."); cameraCommunicationError("FFmpeg Snapshots Stopped: Check that your camera can be reached.");
return;
} }
return; // ffmpeg snapshot stream is still alive return; // ffmpeg snapshot stream is still alive
} }
// if ONVIF cam also use connection state which is updated by regular messages to camera // ONVIF cameras get regular event messages from the camera
if (thing.getThingTypeUID().getId().equals(ONVIF_THING) && snapshotUri.isEmpty() && onvifCamera.isConnected()) { if (supportsOnvifEvents() && onvifCamera.isConnected()) {
return; return;
} }
@ -536,8 +537,8 @@ public class IpCameraHandler extends BaseThingHandler {
return; return;
} }
} }
cameraCommunicationError( cameraCommunicationError("Connection Timeout: Check your IP:" + cameraConfig.getIp() + " and PORT:"
"Connection Timeout: Check your IP and PORT are correct and the camera can be reached."); + cameraConfig.getPort() + " are correct and the camera can be reached.");
} }
// Always use this as sendHttpGET(GET/POST/PUT/DELETE, "/foo/bar",null)// // 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) { public void sendHttpRequest(String httpMethod, String httpRequestURLFull, @Nullable String digestString) {
int port = getPortFromShortenedUrl(httpRequestURLFull); int port = getPortFromShortenedUrl(httpRequestURLFull);
String httpRequestURL = getTinyUrl(httpRequestURLFull); String httpRequestURL = getTinyUrl(httpRequestURLFull);
logger.trace("Sending camera: {}: http://{}:{}{}", httpMethod, cameraConfig.getIp(), port, httpRequestURL);
if (mainBootstrap == null) { if (mainBootstrap == null) {
mainBootstrap = new Bootstrap(); mainBootstrap = new Bootstrap();
mainBootstrap.group(mainEventLoopGroup); mainBootstrap.group(mainEventLoopGroup);
@ -637,12 +638,9 @@ public class IpCameraHandler extends BaseThingHandler {
if (future.isDone() && future.isSuccess()) { if (future.isDone() && future.isSuccess()) {
Channel ch = future.channel(); Channel ch = future.channel();
openChannels.add(ch); openChannels.add(ch);
if (!isOnline) { if (cameraConnectionJob != null && !isOnline.get()) {
bringCameraOnline(); bringCameraOnline();
} }
logger.trace("Sending camera: {}: http://{}:{}{}", httpMethod, cameraConfig.getIp(), port,
httpRequestURL);
openChannel(ch, httpRequestURL); openChannel(ch, httpRequestURL);
CommonCameraHandler commonHandler = (CommonCameraHandler) ch.pipeline().get(COMMON_HANDLER); CommonCameraHandler commonHandler = (CommonCameraHandler) ch.pipeline().get(COMMON_HANDLER);
commonHandler.setURL(httpRequestURLFull); commonHandler.setURL(httpRequestURLFull);
@ -1350,7 +1348,7 @@ public class IpCameraHandler extends BaseThingHandler {
} }
private void bringCameraOnline() { private void bringCameraOnline() {
isOnline = true; isOnline.set(true);
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
groupTracker.listOfOnlineCameraHandlers.add(this); groupTracker.listOfOnlineCameraHandlers.add(this);
groupTracker.listOfOnlineCameraUID.add(getThing().getUID().getId()); 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() { void pollingCameraConnection() {
keepMjpegRunning(); keepMjpegRunning();
if (thing.getThingTypeUID().getId().equals(GENERIC_THING) if (thing.getThingTypeUID().getId().equals(GENERIC_THING)
@ -1421,12 +1425,6 @@ public class IpCameraHandler extends BaseThingHandler {
return; return;
} }
if (cameraConfig.getOnvifPort() > 0 && !onvifCamera.isConnected()) { 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(), logger.debug("About to connect to the IP Camera using the ONVIF PORT at IP: {}:{}", cameraConfig.getIp(),
cameraConfig.getOnvifPort()); cameraConfig.getOnvifPort());
onvifCamera.connect(supportsOnvifEvents()); onvifCamera.connect(supportsOnvifEvents());
@ -1454,7 +1452,7 @@ public class IpCameraHandler extends BaseThingHandler {
public void cameraCommunicationError(String reason) { public void cameraCommunicationError(String reason) {
// will try to reconnect again as camera may be rebooting. // will try to reconnect again as camera may be rebooting.
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason); 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(); resetAndRetryConnecting();
} }
} }
@ -1489,7 +1487,7 @@ public class IpCameraHandler extends BaseThingHandler {
} }
public byte[] getSnapshot() { 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. // 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, 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, 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()); + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword());
sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + "&user=" sendHttpGET("/api.cgi?cmd=GetMdState&channel=" + cameraConfig.getNvrChannel() + "&user="
+ cameraConfig.getUser() + "&password=" + cameraConfig.getPassword()); + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword());
} else { } else if (!snapshotPolling) {
if (!snapshotPolling) { checkCameraConnection();
checkCameraConnection();
}
if (!onvifCamera.isConnected()) {
onvifCamera.connect(true);
}
} }
break; break;
case DAHUA_THING: case DAHUA_THING:
@ -1736,6 +1729,9 @@ public class IpCameraHandler extends BaseThingHandler {
TimeUnit.MINUTES); TimeUnit.MINUTES);
} else { } else {
reolinkAuth = "&user=" + cameraConfig.getUser() + "&password=" + cameraConfig.getPassword(); 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 (snapshotUri.isEmpty()) {
if (cameraConfig.getNvrChannel() < 1) { if (cameraConfig.getNvrChannel() < 1) {
@ -1766,9 +1762,6 @@ public class IpCameraHandler extends BaseThingHandler {
} }
private void tryConnecting() { 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) if (!thing.getThingTypeUID().getId().equals(GENERIC_THING)
&& !thing.getThingTypeUID().getId().equals(DOORBIRD_THING) && cameraConfig.getOnvifPort() > 0) { && !thing.getThingTypeUID().getId().equals(DOORBIRD_THING) && cameraConfig.getOnvifPort() > 0) {
onvifCamera = new OnvifConnection(this, cameraConfig.getIp() + ":" + cameraConfig.getOnvifPort(), onvifCamera = new OnvifConnection(this, cameraConfig.getIp() + ":" + cameraConfig.getOnvifPort(),
@ -1776,16 +1769,8 @@ public class IpCameraHandler extends BaseThingHandler {
onvifCamera.setSelectedMediaProfile(cameraConfig.getOnvifMediaProfile()); onvifCamera.setSelectedMediaProfile(cameraConfig.getOnvifMediaProfile());
// Only use ONVIF events if it is not an API camera. // Only use ONVIF events if it is not an API camera.
onvifCamera.connect(supportsOnvifEvents()); 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, cameraConnectionJob = threadPool.scheduleWithFixedDelay(this::pollingCameraConnection, 4, 12, TimeUnit.SECONDS);
TimeUnit.SECONDS);
} }
private boolean supportsOnvifEvents() { private boolean supportsOnvifEvents() {
@ -1817,7 +1802,7 @@ public class IpCameraHandler extends BaseThingHandler {
} }
private void offline() { private void offline() {
isOnline = false; isOnline.set(false);
snapshotPolling = false; snapshotPolling = false;
Future<?> localFuture = pollCameraJob; Future<?> localFuture = pollCameraJob;
if (localFuture != null) { if (localFuture != null) {

View File

@ -129,11 +129,9 @@ public class OnvifConnection {
private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service"; private String imagingXAddr = "http://" + ipAddress + "/onvif/device_service";
private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service"; private String ptzXAddr = "http://" + ipAddress + "/onvif/ptz_service";
private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service"; private String subscriptionXAddr = "http://" + ipAddress + "/onvif/device_service";
private boolean connectError = false;
private boolean refusedError = false;
private boolean isConnected = false; private boolean isConnected = false;
private int mediaProfileIndex = 0; private int mediaProfileIndex = 0;
private String snapshotUri = ""; // private String snapshotUri = "";
private String rtspUri = ""; private String rtspUri = "";
private IpCameraHandler ipCameraHandler; private IpCameraHandler ipCameraHandler;
private boolean usingEvents = false; private boolean usingEvents = false;
@ -315,7 +313,6 @@ public class OnvifConnection {
sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr); sendOnvifRequest(RequestType.PullMessages, subscriptionXAddr);
} else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent. } else if (message.contains("GetSystemDateAndTimeResponse")) {// 1st to be sent.
setIsConnected(true); setIsConnected(true);
sendOnvifRequest(RequestType.GetCapabilities, deviceXAddr);
parseDateAndTime(message); parseDateAndTime(message);
logger.debug("openHAB UTC dateTime is: {}", getUTCdateTime()); logger.debug("openHAB UTC dateTime is: {}", getUTCdateTime());
} else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent. } else if (message.contains("GetCapabilitiesResponse")) {// 2nd to be sent.
@ -362,11 +359,14 @@ public class OnvifConnection {
} else if (message.contains("GetSnapshotUriResponse")) { } else if (message.contains("GetSnapshotUriResponse")) {
String url = Helper.fetchXML(message, ":MediaUri", ":Uri"); String url = Helper.fetchXML(message, ":MediaUri", ":Uri");
if (!url.isBlank()) { if (!url.isBlank()) {
snapshotUri = removeIPfromUrl(url); logger.debug("GetSnapshotUri: {}", url);
logger.debug("GetSnapshotUri: {}", snapshotUri);
if (ipCameraHandler.snapshotUri.isEmpty() if (ipCameraHandler.snapshotUri.isEmpty()
&& !"ffmpeg".equals(ipCameraHandler.cameraConfig.getSnapshotUrl())) { && !"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")) { } 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. * leaving just the URL.
* *
* @author Matthew Skinner - Initial contribution * @author Matthew Skinner - Initial contribution
*/ */
String removeIPfromUrl(String url) { String removeIPandPortFromUrl(String url) {
int index = url.indexOf("//"); int index = url.indexOf("//");
if (index != -1) {// now remove the :port if (index != -1) {// now remove the :port
index = url.indexOf("/", index + 2); index = url.indexOf("/", index + 2);
@ -503,7 +504,8 @@ public class OnvifConnection {
} }
public void sendOnvifRequest(RequestType requestType, String xAddr) { 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 security = "";
String extraEnvelope = ""; String extraEnvelope = "";
String headerTo = ""; String headerTo = "";
@ -531,12 +533,13 @@ public class OnvifConnection {
headers = ""; headers = "";
} }
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"), FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, new HttpMethod("POST"),
removeIPfromUrl(xAddr)); removeIPandPortFromUrl(xAddr));
String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\""); String actionString = Helper.fetchXML(getXmlCache, requestType.toString(), "xmlns=\"");
request.headers().add("Content-Type", request.headers().add("Content-Type",
"application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\""); "application/soap+xml; charset=utf-8; action=\"" + actionString + "/" + requestType + "\"");
request.headers().add("Charset", "utf-8"); 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("Connection", HttpHeaderValues.CLOSE);
request.headers().set("Accept-Encoding", "gzip, deflate"); request.headers().set("Accept-Encoding", "gzip, deflate");
String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">" String fullXml = "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\"" + extraEnvelope + ">"
@ -571,28 +574,30 @@ public class OnvifConnection {
bootstrap = localBootstap; bootstrap = localBootstap;
} }
if (!mainEventLoopGroup.isShuttingDown()) { 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 @Override
public void operationComplete(@Nullable ChannelFuture future) { public void operationComplete(@Nullable ChannelFuture future) {
if (future == null) { if (future == null) {
return; return;
} }
if (future.isSuccess()) { if (future.isSuccess()) {
connectError = false;
Channel ch = future.channel(); Channel ch = future.channel();
ch.writeAndFlush(request); ch.writeAndFlush(request);
} else { // an error occurred } else { // an error occurred
if (future.isDone() && !future.isCancelled()) { if (future.isDone() && !future.isCancelled()) {
Throwable cause = future.cause(); Throwable cause = future.cause();
String msg = cause.getMessage(); String msg = cause.getMessage();
logger.trace("connect failed - cause {}", cause.getMessage()); logger.debug("connect failed - cause {}", cause.getMessage());
if (cause instanceof ConnectTimeoutException) { if (cause instanceof ConnectTimeoutException) {
logger.debug("Camera is not reachable on IP {}", ipAddress); usingEvents = false;// Prevent Unsubscribe from being sent
connectError = true; ipCameraHandler.cameraCommunicationError(
"Camera timed out when trying to connect to the ONVIF port:" + port);
} else if ((cause instanceof ConnectException) && msg != null } else if ((cause instanceof ConnectException) && msg != null
&& msg.contains("Connection refused")) { && msg.contains("Connection refused")) {
logger.debug("Camera ONVIF port {} is refused.", onvifPort); usingEvents = false;// Prevent Unsubscribe from being sent
refusedError = true; ipCameraHandler.cameraCommunicationError(
"Camera refused to connect when using ONVIF to port:" + port);
} }
} }
if (isConnected) { if (isConnected) {
@ -944,14 +949,6 @@ public class OnvifConnection {
} }
} }
public boolean isConnectError() {
return connectError;
}
public boolean isRefusedError() {
return refusedError;
}
public boolean isConnected() { public boolean isConnected() {
connecting.lock(); connecting.lock();
try { try {
@ -965,8 +962,6 @@ public class OnvifConnection {
connecting.lock(); connecting.lock();
try { try {
this.isConnected = isConnected; this.isConnected = isConnected;
this.connectError = false;
this.refusedError = false;
} finally { } finally {
connecting.unlock(); 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.audioAlarm.description = Audio has triggered an Alarm.
channel-type.ipcamera.autoLED.label = Auto LED 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.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.label = Car Alarm
channel-type.ipcamera.carAlarm.description = A car has triggered the Vehicle Detection. channel-type.ipcamera.carAlarm.description = A car has triggered the Vehicle Detection.
channel-type.ipcamera.cellMotionAlarm.label = Cell Motion Alarm 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.doorBell.description = The button has been pushed.
channel-type.ipcamera.enableAudioAlarm.label = Enable Audio Alarm 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.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.label = Enable Alarm Input 1
channel-type.ipcamera.enableExternalAlarmInput.description = Turn the External Alarm Input feature on and off. channel-type.ipcamera.enableExternalAlarmInput.description = Turn the External Alarm Input feature on and off.
channel-type.ipcamera.enableFTP.label = Enable FTP 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.enablePirAlarm.description = Enable/Disable the PIR Alarm.
channel-type.ipcamera.enablePrivacyMode.label = Enable Privacy Mode channel-type.ipcamera.enablePrivacyMode.label = Enable Privacy Mode
channel-type.ipcamera.enablePrivacyMode.description = Turn the Privacy Mode on and off. 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.label = Enable Recordings
channel-type.ipcamera.enableRecordings.description = Enable/Disable the cameras internal recordings channel-type.ipcamera.enableRecordings.description = Enable/Disable the cameras internal recordings
channel-type.ipcamera.externalAlarmInput.label = Alarm Input 1 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.tooDarkAlarm.description = Image is too dark.
channel-type.ipcamera.triggerExternalAlarmInput.label = Alarm In 1 TRIGGER high ON/low OFF 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.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.label = Zoom
channel-type.ipcamera.zoom.description = Zoom the camera to a new value. 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="itemTaken" typeId="itemTaken"/>
<channel id="autoLED" typeId="autoLED"/> <channel id="autoLED" typeId="autoLED"/>
<channel id="enableLED" typeId="enableLED"/> <channel id="enableLED" typeId="enableLED"/>
<channel id="whiteLED" typeId="whiteLED"/>
<channel id="autoWhiteLED" typeId="autoWhiteLED"/>
<channel id="textOverlay" typeId="textOverlay"/> <channel id="textOverlay" typeId="textOverlay"/>
<channel id="pan" typeId="pan"/> <channel id="pan" typeId="pan"/>
<channel id="tilt" typeId="tilt"/> <channel id="tilt" typeId="tilt"/>
@ -921,6 +923,11 @@
<channel id="sceneChangeAlarm" typeId="sceneChangeAlarm"/> <channel id="sceneChangeAlarm" typeId="sceneChangeAlarm"/>
<channel id="tooBlurryAlarm" typeId="tooBlurryAlarm"/> <channel id="tooBlurryAlarm" typeId="tooBlurryAlarm"/>
</channels> </channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description> <config-description>
<parameter-group name="Settings"> <parameter-group name="Settings">
@ -2297,13 +2304,22 @@
<channel id="animalAlarm" typeId="animalAlarm"/> <channel id="animalAlarm" typeId="animalAlarm"/>
<channel id="faceDetected" typeId="faceDetected"/> <channel id="faceDetected" typeId="faceDetected"/>
<channel id="autoLED" typeId="autoLED"/> <channel id="autoLED" typeId="autoLED"/>
<channel id="autoWhiteLED" typeId="autoWhiteLED"/>
<channel id="enableLED" typeId="enableLED"/> <channel id="enableLED" typeId="enableLED"/>
<channel id="whiteLED" typeId="whiteLED"/>
<channel id="textOverlay" typeId="textOverlay"/> <channel id="textOverlay" typeId="textOverlay"/>
<channel id="activateAlarmOutput" typeId="activateAlarmOutput"/> <channel id="activateAlarmOutput" typeId="activateAlarmOutput"/>
<channel id="doorBell" typeId="doorBell"/> <channel id="doorBell" typeId="doorBell"/>
<channel id="enableRecordings" typeId="enableRecordings"/> <channel id="enableRecordings" typeId="enableRecordings"/>
<channel id="enableFTP" typeId="enableFTP"/> <channel id="enableFTP" typeId="enableFTP"/>
<channel id="enableEmail" typeId="enableEmail"/>
<channel id="enablePush" typeId="enablePush"/>
</channels> </channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description> <config-description>
<parameter-group name="Settings"> <parameter-group name="Settings">
@ -2948,6 +2964,13 @@
<category>Light</category> <category>Light</category>
</channel-type> </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"> <channel-type id="enablePrivacyMode" advanced="true">
<item-type>Switch</item-type> <item-type>Switch</item-type>
<label>Enable Privacy Mode</label> <label>Enable Privacy Mode</label>
@ -2966,12 +2989,30 @@
<description>Turn the FTP features of the camera on and off</description> <description>Turn the FTP features of the camera on and off</description>
</channel-type> </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"> <channel-type id="autoLED" advanced="true">
<item-type>Switch</item-type> <item-type>Switch</item-type>
<label>Auto LED</label> <label>Auto LED</label>
<description>Turn the automatic mode for the LED ON and OFF.</description> <description>Turn the automatic mode for the LED ON and OFF.</description>
</channel-type> </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"> <channel-type id="externalLight" advanced="true">
<item-type>Switch</item-type> <item-type>Switch</item-type>
<label>External Light</label> <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>