diff --git a/plugins/reolink/src/main.ts b/plugins/reolink/src/main.ts index ba4046ba6..73a9eaaca 100644 --- a/plugins/reolink/src/main.ts +++ b/plugins/reolink/src/main.ts @@ -7,7 +7,7 @@ import { OnvifCameraAPI, OnvifEvent, connectCameraAPI } from './onvif-api'; import { listenEvents } from './onvif-events'; import { OnvifIntercom } from './onvif-intercom'; import { DevInfo } from './probe'; -import { AIState, Enc, isDeviceNvr, ReolinkCameraClient } from './reolink-api'; +import { AIState, Enc, isDeviceNvr, ReolinkCameraClient, isDeviceHomeHub } from './reolink-api'; class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff { sirenTimeout: NodeJS.Timeout; @@ -237,6 +237,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R await this.updateAbilities(); await this.updateDevice(); await this.reportDevices(); + await this.checkNetData(); this.startDevicesStatesPolling(); })() .catch(e => { @@ -244,6 +245,48 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R }); } + async checkNetData() { + try { + const api = this.getClientWithToken(); + const { netData } = await api.getNetData(); + this.console.log('netData', JSON.stringify(netData)); + const deviceInfo = this.storageSettings.values.deviceInfo; + const isHomeHub = isDeviceHomeHub(deviceInfo); + + const shouldDisableHttps = this.hasHttps() ? netData.httpsEnable === 1 : false; + const shouldEnableRtmp = this.hasRtmp() ? (!isHomeHub && netData.rtmpEnable === 0) : false; + const shouldDisableRtmp = this.hasRtmp() ? (isHomeHub && netData.rtmpEnable === 1) : false; + const shouldEnableRtsp = this.hasRtsp() ? netData.rtspEnable === 0 : false; + const shouldEnableOnvif = this.hasOnvif() ? netData.onvifEnable === 0 : false; + + if (shouldDisableHttps || shouldEnableRtmp || shouldEnableRtsp || shouldEnableOnvif || shouldDisableRtmp) { + this.console.log(`Fixing netdata settings: shouldDisableHttps: ${shouldDisableHttps}, shouldEnableRtmp: ${shouldEnableRtmp}, shouldEnableRtsp: ${shouldEnableRtsp}, shouldEnableOnvif: ${shouldEnableOnvif}, shouldDisableRtmp: ${shouldDisableRtmp}`); + const newNetData = { + ...netData + }; + + if (shouldDisableHttps) { + newNetData.httpsEnable = 0; + } + if (shouldEnableRtmp) { + newNetData.rtmpEnable = 1; + } + if (shouldDisableRtmp) { + newNetData.rtmpEnable = 0; + } + if (shouldEnableRtsp) { + newNetData.rtspEnable = 1; + } + if (shouldEnableOnvif) { + newNetData.onvifEnable = 1; + } + await api.setNetData(newNetData); + } + } catch (e) { + this.console.error('Error in pollDeviceStates', e); + } + } + async pollDeviceStates() { try { const api = this.getClient(); @@ -425,43 +468,52 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R return this.onvifIntercom.stopIntercom(); } - hasSiren() { + hasAbility(ability: string) { const channel = this.getRtspChannel(); - const mainAbility = this.storageSettings.values.abilities?.value?.Ability?.supportAudioAlarm - const channelAbility = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[channel]?.supportAudioAlarm + const mainAbility = this.storageSettings.values.abilities?.value?.Ability?.[ability]; + const channelAbility = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[channel]?.[ability]; return (mainAbility && mainAbility?.ver !== 0) || (channelAbility && channelAbility?.ver !== 0); + } + hasSiren() { + return this.hasAbility('supportAudioAlarm'); + } + + hasRtsp() { + return this.hasAbility('supportRtspEnable'); + } + + hasRtmp() { + return this.hasAbility('supportRtmpEnable'); + } + + hasOnvif() { + return this.hasAbility('supportOnvifEnable'); + } + + hasHttps() { + return this.hasAbility('supportHttpsEnable'); } hasFloodlight() { - const channel = this.getRtspChannel(); + const hasFloodlight = this.hasAbility('floodLight'); + const hasSupportFLswitch = this.hasAbility('supportFLswitch'); + const hasSupportFLBrightness = this.hasAbility('supportFLBrightness'); - const channelData = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[channel]; - if (channelData) { - const floodLightConfigVer = channelData.floodLight?.ver ?? 0; - const supportFLswitchConfigVer = channelData.supportFLswitch?.ver ?? 0; - const supportFLBrightnessConfigVer = channelData.supportFLBrightness?.ver ?? 0; - - return floodLightConfigVer > 0 || supportFLswitchConfigVer > 0 || supportFLBrightnessConfigVer > 0; - } - - return false; + return hasFloodlight || hasSupportFLswitch || hasSupportFLBrightness; } hasBattery() { - const batteryConfigVer = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()]?.battery?.ver ?? 0; - return batteryConfigVer > 0; + return this.hasAbility('battery'); } hasPirEvents() { - const pirEvents = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()]?.mdWithPir?.ver ?? 0; - return pirEvents > 0; + return this.hasAbility('mdWithPir'); } hasPirSensor() { - const batteryConfigVer = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()]?.mdWithPir?.ver ?? 0; - return batteryConfigVer > 0; + return this.hasAbility('mdWithPir'); } async updateDevice() { @@ -842,7 +894,10 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R // anecdotally, encoders of type h265 do not have a working RTMP main stream. const mainEncType = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[rtspChannel]?.mainEncType?.ver; - if (live === 2) { + // RTMP streams on homehub connected devices is bad + if (isDeviceHomeHub(deviceInfo)) { + streams.push(...[rtspMain, rtspSub]); + } else if (live === 2) { if (mainEncType === 1) { streams.push(rtmpSub, rtspMain, rtspSub); } diff --git a/plugins/reolink/src/reolink-api.ts b/plugins/reolink/src/reolink-api.ts index c479a96d1..de6c34af9 100644 --- a/plugins/reolink/src/reolink-api.ts +++ b/plugins/reolink/src/reolink-api.ts @@ -54,7 +54,6 @@ export interface Osd { value: Initial; } - export interface AIDetectionState { alarm_state: number; support: number; @@ -75,7 +74,22 @@ export interface PtzPreset { name: string; } +export interface NetData { + httpEnable: 1 | 0, + httpPort: number, + httpsEnable: 1 | 0, + httpsPort: number, + mediaPort: number, + onvifEnable: 1 | 0, + onvifPort: number, + rtmpEnable: 1 | 0, + rtmpPort: number, + rtspEnable: 1 | 0, + rtspPort: number +} + export const isDeviceNvr = (deviceInfo: DevInfo) => ['HOMEHUB', 'NVR', 'NVR_WIFI'].includes(deviceInfo.exactType); +export const isDeviceHomeHub = (deviceInfo: DevInfo) => deviceInfo.exactType === 'HOMEHUB'; export class ReolinkCameraClient { credential: AuthFetchCredentialState; @@ -767,4 +781,51 @@ export class ReolinkCameraClient { isWifi }; } + + async getNetData() { + const url = new URL(`http://${this.host}/api.cgi`); + + const body = [{ + cmd: 'GetNetPort', + action: 1, + }]; + + const response = await this.requestWithLogin({ + url, + method: 'POST', + responseType: 'json', + }, this.createReadable(body)); + + const error = response.body?.[0]?.error; + if (error) { + this.console.error('error during call to getWhiteLedState', JSON.stringify(body), error); + } + + + return { + netData: response.body?.[0]?.value as NetData, + }; + } + + async setNetData(netData: NetData) { + const url = new URL(`http://${this.host}/api.cgi`); + + const body = [{ + cmd: 'SetNetPort', + param: { + NetPort: netData + } + }]; + + const response = await this.requestWithLogin({ + url, + method: 'POST', + responseType: 'json', + }, this.createReadable(body)); + + const error = response.body?.[0]?.error; + if (error) { + this.console.error('error during call to setNetData', JSON.stringify(body), error); + } + } }