diff --git a/plugins/reolink/src/main.ts b/plugins/reolink/src/main.ts index dcb02f328..0c2ec49e7 100644 --- a/plugins/reolink/src/main.ts +++ b/plugins/reolink/src/main.ts @@ -14,7 +14,6 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff { constructor(public camera: ReolinkCamera, nativeId: string) { super(nativeId); - this.on = false; } async turnOff() { @@ -53,7 +52,6 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff { class ReolinkCameraFloodlight extends ScryptedDeviceBase implements OnOff, Brightness { constructor(public camera: ReolinkCamera, nativeId: string) { super(nativeId); - this.on = false; } async setBrightness(brightness: number): Promise { @@ -81,7 +79,6 @@ class ReolinkCameraFloodlight extends ScryptedDeviceBase implements OnOff, Brigh class ReolinkCameraPirSensor extends ScryptedDeviceBase implements OnOff { constructor(public camera: ReolinkCamera, nativeId: string) { super(nativeId); - this.on = false; } async turnOff() { @@ -240,12 +237,63 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R await this.updateAbilities(); await this.updateDevice(); await this.reportDevices(); + this.startDevicesStatesPolling(); })() .catch(e => { this.console.log('device refresh failed', e); }); } + async pollDeviceStates() { + try { + const api = this.getClient(); + + try { + if (this.hasFloodlight() && this.floodlight) { + const { enabled } = await api.getWhiteLedState(); + + if (enabled !== this.floodlight.on) { + this.floodlight.on = enabled; + } + } + } catch { } + + try { + if (this.hasSiren() && this.siren) { + const { enabled } = await api.getSiren(); + + if (enabled !== this.siren.on) { + this.siren.on = enabled; + } + } + } catch { } + + try { + if (this.hasPirSensor() && this.pirSensor) { + const { enabled } = await api.getPirState(); + + if (enabled !== this.pirSensor.on) { + this.pirSensor.on = enabled; + } + } + } catch { } + } catch (e) { + this.console.error('Error in pollDeviceStates', e); + } + } + + async startDevicesStatesPolling() { + if ( + !this.hasBattery() && + (this.hasFloodlight() || this.hasSiren() || this.hasPirSensor()) + ) { + while (true) { + await this.pollDeviceStates(); + await sleep(1000 * 5); + } + } + } + async getVideoTextOverlays(): Promise> { const client = this.getClient(); const osd = await client.getOsd(); @@ -458,6 +506,9 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R if (sleeping !== this.sleeping) { this.sleeping = sleeping; + if (!sleeping) { + await this.pollDeviceStates(); + } } if (batteryPercent !== this.batteryLevel) { this.batteryLevel = batteryPercent; diff --git a/plugins/reolink/src/reolink-api.ts b/plugins/reolink/src/reolink-api.ts index 78d78ede1..9017d2297 100644 --- a/plugins/reolink/src/reolink-api.ts +++ b/plugins/reolink/src/reolink-api.ts @@ -476,6 +476,31 @@ export class ReolinkCameraClient { } } + async getSiren() { + const url = new URL(`http://${this.host}/api.cgi`); + + const body = [{ + cmd: 'GetAudioAlarmV20', + action: 0, + param: { channel: this.channelId } + }]; + + 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 getSiren', JSON.stringify(body), error); + } + + return { + enabled: response.body?.[0]?.value?.Audio?.enable === 1 + }; + } + async setSiren(on: boolean, duration?: number) { const url = new URL(`http://${this.host}/api.cgi`); const params = url.searchParams; @@ -515,6 +540,31 @@ export class ReolinkCameraClient { }; } + async getWhiteLedState() { + const url = new URL(`http://${this.host}/api.cgi`); + + const body = [{ + cmd: 'GetWhiteLed', + action: 0, + param: { channel: this.channelId } + }]; + + 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 { + enabled: response.body?.[0]?.value?.WhiteLed?.state === 1 + }; + } + async setWhiteLedState(on?: boolean, brightness?: number) { const url = new URL(`http://${this.host}/api.cgi`); @@ -608,7 +658,7 @@ export class ReolinkCameraClient { }; } - async getPirState(on?: boolean) { + async getPirState() { const url = new URL(`http://${this.host}/api.cgi`); const body = [{ @@ -625,10 +675,13 @@ export class ReolinkCameraClient { const error = response.body?.[0]?.error; if (error) { - this.console.error('error during call to setWhiteLedState', JSON.stringify(body), error); + this.console.error('error during call to getPirState', JSON.stringify(body), error); } - return response.body?.[0]?.value?.pirInfo; + return { + enabled: response.body?.[0]?.value?.pirInfo?.enable === 1, + state: response.body?.[0]?.value?.pirInfo + }; } async setPirState(on: boolean) { @@ -637,7 +690,7 @@ export class ReolinkCameraClient { const currentPir = await this.getPirState(); const newState = on ? 1 : 0; - if (!currentPir || currentPir.enable === newState) { + if (!currentPir || currentPir.state?.enable === newState) { return; }