Reolink: add check for net data, enable/disable RTMP/RTSP/ONVIF/HTTPS when necessary (#1931)

- unify methods to get specific abilities
- allow only RTSP streams for homehub devices

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
This commit is contained in:
apocaliss92
2025-11-26 16:29:32 +01:00
committed by GitHub
parent 77dd8cf2a8
commit 370a82dc56
2 changed files with 139 additions and 23 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}
}