Skip to content

Commit

Permalink
ring: add lights, switches & outlets (#642)
Browse files Browse the repository at this point in the history
  • Loading branch information
leedsalex committed Mar 19, 2023
1 parent c62d4bd commit fac6769
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 12 deletions.
2 changes: 2 additions & 0 deletions plugins/ring/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Do not enable prebuffer on Ring cameras and doorbells.
- Water Sensor
- Mailbox Sensor
- Smart Locks
- Ring Smart Lights (Flood/Path/Step/Spot Lights, Bulbs, Transformer)
- Lights, Switches & Outlets

## Problems and Solutions

Expand Down
4 changes: 2 additions & 2 deletions plugins/ring/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/ring/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@
"got": "11.8.6",
"socket.io-client": "^2.5.0"
},
"version": "0.0.103"
"version": "0.0.104"
}
127 changes: 119 additions & 8 deletions plugins/ring/src/location.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sdk, { Battery, Device, DeviceProvider, EntrySensor, FloodSensor, Lock, LockState, MotionSensor, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, SecuritySystem, SecuritySystemMode, TamperSensor } from '@scrypted/sdk';
import sdk, { Battery, Brightness, Device, DeviceProvider, EntrySensor, FloodSensor, Lock, LockState, MotionSensor, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, SecuritySystem, SecuritySystemMode, TamperSensor } from '@scrypted/sdk';
import { RingCameraDevice } from './camera';
import RingPlugin from './main';
import { Location, LocationMode, RingCamera, RingDevice, RingDeviceData, RingDeviceType } from './ring-client-api';
import { Location, LocationMode, RingCamera, RingDevice, RingDeviceCategory, RingDeviceData, RingDeviceType } from './ring-client-api';

const { deviceManager } = sdk;

Expand All @@ -11,6 +11,8 @@ class RingLock extends ScryptedDeviceBase implements Battery, Lock {
constructor(nativeId: string, device: RingDevice) {
super(nativeId);
this.device = device;
this.updateState(device.data);

device.onData.subscribe(async (data: RingDeviceData) => {
this.updateState(data);
});
Expand Down Expand Up @@ -42,7 +44,7 @@ class RingLock extends ScryptedDeviceBase implements Battery, Lock {
}
}

class RingSensor extends ScryptedDeviceBase implements TamperSensor, Battery, EntrySensor, MotionSensor, FloodSensor {
class RingLight extends ScryptedDeviceBase implements Battery, TamperSensor, MotionSensor, OnOff, Brightness {
device: RingDevice;
data: RingDeviceData;

Expand All @@ -58,16 +60,93 @@ class RingSensor extends ScryptedDeviceBase implements TamperSensor, Battery, En
});
}

private isBeamDevice() {
return [RingDeviceType.BeamsMultiLevelSwitch, RingDeviceType.BeamsSwitch, RingDeviceType.BeamsTransformerSwitch].includes(this.device.deviceType);
}

updateState(data: RingDeviceData) {
this.batteryLevel = data.batteryLevel;
this.tampered = data.tamperStatus === 'tamper';
this.motionDetected = data.motionStatus === 'faulted';
this.on = data.on;
this.brightness = data.level && !isNaN(data.level) ? 100 * data.level : 0;
}

turnOff(): Promise<void> {
if (this.isBeamDevice()) {
this.device.sendCommand('light-mode.set', { lightMode: 'default' });
return;
} else {
return this.device.setInfo({ device: { v1: { on: false } } });
}
}

turnOn(): Promise<void> {
if (this.isBeamDevice()) {
this.device.sendCommand('light-mode.set', { lightMode: 'on' });
return;
} else {
return this.device.setInfo({ device: { v1: { on: true } } });
}
}

setBrightness(brightness: number): Promise<void> {
return this.device.setInfo({
device: { v1: { level: brightness / 100 } },
});
}
}

class RingSwitch extends ScryptedDeviceBase implements OnOff {
device: RingDevice;
data: RingDeviceData;

constructor(nativeId: string, device: RingDevice) {
super(nativeId);
this.device = device;
this.updateState(device.data);

device.onData.subscribe(async (data: RingDeviceData) => {
this.updateState(data);
});
}

updateState(data: RingDeviceData) {
this.on = data.on;
}

turnOff(): Promise<void> {
return this.device.setInfo({ device: { v1: { on: false } } });
}

turnOn(): Promise<void> {
return this.device.setInfo({ device: { v1: { on: true } } });
}
}

class RingSensor extends ScryptedDeviceBase implements TamperSensor, Battery, EntrySensor, MotionSensor, FloodSensor {
device: RingDevice;

constructor(nativeId: string, device: RingDevice) {
super(nativeId);
this.device = device;
this.updateState(device.data);

device.onData.subscribe(async (data: RingDeviceData) => {
this.updateState(data);
});
}

updateState(data: RingDeviceData) {
this.batteryLevel = data.batteryLevel;
this.tampered = data.tamperStatus === 'tamper';
this.entryOpen = data.faulted;
this.motionDetected = this.device.deviceType === RingDeviceType.BeamsMotionSensor ? data.motionStatus === 'faulted' : data.faulted;
this.flooded = data.flood?.faulted || data.faulted;
}

isBypassable() {
return (this.device.deviceType === RingDeviceType.ContactSensor || this.device.deviceType === RingDeviceType.RetrofitZone) && this.data.faulted;
return (this.device.deviceType === RingDeviceType.ContactSensor || this.device.deviceType === RingDeviceType.RetrofitZone) && this.device.data.faulted;
}
}

Expand Down Expand Up @@ -168,25 +247,51 @@ export class RingLocationDevice extends ScryptedDeviceBase implements DeviceProv
case RingDeviceType.TiltSensor:
case RingDeviceType.GlassbreakSensor:
nativeId = locationDevice.id.toString() + '-sensor';
type = ScryptedDeviceType.Sensor
type = ScryptedDeviceType.Sensor;
interfaces.push(ScryptedInterface.TamperSensor, ScryptedInterface.EntrySensor);
break;
case RingDeviceType.MotionSensor:
case RingDeviceType.BeamsMotionSensor:
nativeId = locationDevice.id.toString() + '-sensor';
type = ScryptedDeviceType.Sensor
type = ScryptedDeviceType.Sensor;
interfaces.push(ScryptedInterface.TamperSensor, ScryptedInterface.MotionSensor);
break;
case RingDeviceType.FloodFreezeSensor:
case RingDeviceType.WaterSensor:
nativeId = locationDevice.id.toString() + '-sensor';
type = ScryptedDeviceType.Sensor
type = ScryptedDeviceType.Sensor;
interfaces.push(ScryptedInterface.TamperSensor, ScryptedInterface.FloodSensor);
break;
case RingDeviceType.BeamsMultiLevelSwitch:
case RingDeviceType.BeamsSwitch:
case RingDeviceType.BeamsTransformerSwitch:
case RingDeviceType.MultiLevelBulb:
nativeId = locationDevice.id.toString() + '-light';
type = ScryptedDeviceType.Light;
interfaces.push(ScryptedInterface.OnOff);
if (data.level !== undefined)
interfaces.push(ScryptedInterface.Brightness)
if (data.motionStatus !== undefined && !!data.motionSensorEnabled)
interfaces.push(ScryptedInterface.TamperSensor, ScryptedInterface.MotionSensor);
break;
case RingDeviceType.MultiLevelSwitch:
if (data.categoryId === RingDeviceCategory.Lights) {
nativeId = locationDevice.id.toString() + '-light';
type = ScryptedDeviceType.Light;
interfaces.push(ScryptedInterface.OnOff);
if (data.level !== undefined)
interfaces.push(ScryptedInterface.Brightness)
break;
}
case RingDeviceType.Switch:
nativeId = locationDevice.id.toString() + '-switch';
type = data.categoryId === RingDeviceCategory.Outlets ? ScryptedDeviceType.Outlet : ScryptedDeviceType.Switch;
interfaces.push(ScryptedInterface.OnOff);
break;
default:
if (/^lock($|\.)/.test(data.deviceType)) {
nativeId = locationDevice.id.toString() + '-lock';
type = ScryptedDeviceType.Lock
type = ScryptedDeviceType.Lock;
interfaces.push(ScryptedInterface.Lock);
break;
} else {
Expand Down Expand Up @@ -228,6 +333,12 @@ export class RingLocationDevice extends ScryptedDeviceBase implements DeviceProv
} else if (nativeId.endsWith('-lock')) {
const device = new RingLock(nativeId, this.locationDevices.get(nativeId) as RingDevice);
this.devices.set(nativeId, device);
} else if (nativeId.endsWith('-light')) {
const device = new RingLight(nativeId, this.locationDevices.get(nativeId) as RingDevice);
this.devices.set(nativeId, device);
} else if (nativeId.endsWith('-switch')) {
const device = new RingSwitch(nativeId, this.locationDevices.get(nativeId) as RingDevice);
this.devices.set(nativeId, device);
} else {
const device = new RingCameraDevice(this.plugin.api, nativeId, this.locationDevices.get(nativeId) as RingCamera);
this.devices.set(nativeId, device);
Expand Down
2 changes: 1 addition & 1 deletion plugins/ring/src/ring-client-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export { BasicPeerConnection } from '@koush/ring-client-api/packages/ring-client
export { SimpleWebRtcSession } from '@koush/ring-client-api/packages/ring-client-api/streaming/simple-webrtc-session';
export { StreamingSession } from '@koush/ring-client-api/packages/ring-client-api/streaming/streaming-session';
export { generateUuid } from '@koush/ring-client-api/packages/ring-client-api/util';
export { RingDeviceType, RingDeviceData } from '@koush/ring-client-api/packages/ring-client-api/ring-types';
export { RingDeviceType, RingDeviceData, RingDeviceCategory } from '@koush/ring-client-api/packages/ring-client-api/ring-types';
export { RingDevice } from '@koush/ring-client-api/packages/ring-client-api/ring-device';
export * as rxjs from '@koush/ring-client-api/node_modules/rxjs';

0 comments on commit fac6769

Please sign in to comment.