@koush/ring-client-api
Version:
Unofficial API for Ring doorbells, cameras, security alarm system and smart lighting
204 lines (203 loc) • 10.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Camera = void 0;
const hap_1 = require("./hap");
const base_data_accessory_1 = require("./base-data-accessory");
const operators_1 = require("rxjs/operators");
const camera_source_1 = require("./camera-source");
const rxjs_1 = require("rxjs");
const target_value_timer_1 = require("./target-value-timer");
class Camera extends base_data_accessory_1.BaseDataAccessory {
constructor(device, accessory, logger, config) {
super();
this.device = device;
this.accessory = accessory;
this.logger = logger;
this.config = config;
this.cameraSource = new camera_source_1.CameraSource(this.device);
if (!hap_1.hap.CameraController) {
const error = 'HAP CameraController not found. Please make sure you are on homebridge version 1.0.0 or newer';
logger.error(error);
throw new Error(error);
}
const { Characteristic, Service } = hap_1.hap, { ChargingState, StatusLowBattery } = Characteristic;
accessory.configureController(this.cameraSource.controller);
this.registerCharacteristic({
characteristicType: Characteristic.Mute,
serviceType: Service.Microphone,
getValue: () => false,
});
this.registerCharacteristic({
characteristicType: Characteristic.Mute,
serviceType: Service.Speaker,
getValue: () => false,
});
if (!config.hideCameraMotionSensor) {
this.registerObservableCharacteristic({
characteristicType: Characteristic.MotionDetected,
serviceType: Service.MotionSensor,
onValue: device.onMotionDetected.pipe((0, operators_1.switchMap)((motion) => {
if (!motion) {
return Promise.resolve(false);
}
return this.loadSnapshotForEvent('Detected Motion', true);
})),
});
}
if (device.isDoorbot) {
const onDoorbellPressed = device.onDoorbellPressed.pipe((0, operators_1.mapTo)('Doorbell Pressed')), onEventDescription = config.sendDoorbellMotionNotificationsToTv
? (0, rxjs_1.merge)(onDoorbellPressed, device.onMotionStarted.pipe((0, operators_1.mapTo)('Motion Detected - Simulating Doorbell Press')))
: onDoorbellPressed;
this.registerObservableCharacteristic({
characteristicType: Characteristic.ProgrammableSwitchEvent,
serviceType: Service.Doorbell,
onValue: onEventDescription.pipe((0, operators_1.switchMap)((eventDescription) => {
return this.loadSnapshotForEvent(eventDescription, Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
})),
});
if (!config.hideDoorbellSwitch) {
this.registerObservableCharacteristic({
characteristicType: Characteristic.ProgrammableSwitchEvent,
serviceType: Service.StatelessProgrammableSwitch,
onValue: device.onDoorbellPressed.pipe((0, operators_1.mapTo)(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS)),
});
// Hide long and double press events by setting max value
this.getService(Service.StatelessProgrammableSwitch)
.getCharacteristic(Characteristic.ProgrammableSwitchEvent)
.setProps({
maxValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
});
}
}
else if (config.sendCameraMotionNotificationsToTv) {
// allow standalone cameras to act as a doorbell press when motion is detected
// this allows tvOS 14 notifications to show camera motion alerts
this.registerObservableCharacteristic({
characteristicType: Characteristic.ProgrammableSwitchEvent,
serviceType: Service.Doorbell,
onValue: device.onMotionStarted.pipe((0, operators_1.switchMap)(() => {
return this.loadSnapshotForEvent('Motion Detected - Simulating Doorbell Press', Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS);
})),
});
}
if (device.hasLight && !config.hideCameraLight) {
const lightTargetTimer = new target_value_timer_1.TargetValueTimer();
this.registerCharacteristic({
characteristicType: Characteristic.On,
serviceType: Service.Lightbulb,
getValue: (data) => {
const value = lightTargetTimer.hasTarget()
? lightTargetTimer.getTarget()
: data.led_status === 'on';
return value;
},
setValue: (value) => {
// Allow 5 seconds for the light value to update in our status updates from Ring
lightTargetTimer.setTarget(value, 5000);
return device.setLight(value);
},
requestUpdate: () => device.requestUpdate(),
});
}
if (device.hasSiren && !config.hideCameraSirenSwitch) {
this.registerCharacteristic({
characteristicType: Characteristic.On,
serviceType: Service.Switch,
serviceSubType: 'Siren',
name: device.name + ' Siren',
getValue: (data) => {
return Boolean(data.siren_status && data.siren_status.seconds_remaining);
},
setValue: (value) => device.setSiren(value),
requestUpdate: () => device.requestUpdate(),
});
}
if (device.hasInHomeDoorbell && !config.hideInHomeDoorbellSwitch) {
this.device.onInHomeDoorbellStatus.subscribe((data) => {
this.inHomeDoorbellStatus = data;
});
this.registerObservableCharacteristic({
characteristicType: Characteristic.On,
serviceType: Service.Switch,
serviceSubType: 'In-Home Doorbell',
name: device.name + ' In-Home Doorbell',
onValue: device.onInHomeDoorbellStatus,
setValue: (value) => device.setInHomeDoorbell(value),
requestUpdate: () => device.requestUpdate(),
});
}
this.registerCharacteristic({
characteristicType: Characteristic.Manufacturer,
serviceType: Service.AccessoryInformation,
getValue: () => 'Ring',
});
this.registerCharacteristic({
characteristicType: Characteristic.Model,
serviceType: Service.AccessoryInformation,
getValue: (data) => `${device.model} (${data.kind})`,
});
this.registerCharacteristic({
characteristicType: Characteristic.SerialNumber,
serviceType: Service.AccessoryInformation,
getValue: (data) => data.device_id,
});
if (device.hasBattery ||
// detected as having battery in the past
accessory.getService(Service.BatteryService)) {
this.registerCharacteristic({
characteristicType: Characteristic.StatusLowBattery,
serviceType: Service.BatteryService,
getValue: () => {
return device.hasLowBattery
? StatusLowBattery.BATTERY_LEVEL_LOW
: StatusLowBattery.BATTERY_LEVEL_NORMAL;
},
});
this.registerCharacteristic({
characteristicType: Characteristic.ChargingState,
serviceType: Service.BatteryService,
getValue: (data) => {
return data.external_connection
? ChargingState.CHARGING
: ChargingState.NOT_CHARGING;
},
});
this.registerObservableCharacteristic({
characteristicType: Characteristic.BatteryLevel,
serviceType: Service.BatteryService,
onValue: device.onBatteryLevel.pipe((0, operators_1.map)((batteryLevel) => {
return batteryLevel === null ? 100 : batteryLevel;
})),
});
}
}
loadSnapshotForEvent(eventDescription, characteristicValue) {
return __awaiter(this, void 0, void 0, function* () {
if (this.device.operatingOnBattery) {
// battery cameras cannot fetch a new snapshot while recording is in progress
this.logger.info(this.device.name + ' ' + eventDescription);
return characteristicValue;
}
this.logger.info(this.device.name +
` ${eventDescription}. Loading snapshot before sending event to HomeKit`);
try {
yield this.cameraSource.loadSnapshot();
}
catch (e) {
this.logger.info(this.device.name +
' Failed to load snapshot. Sending event to HomeKit without new snapshot');
}
return characteristicValue;
});
}
}
exports.Camera = Camera;