alexa: doorbells

This commit is contained in:
Koushik Dutta
2022-03-23 22:38:36 -07:00
parent 83bcd6d956
commit 65c87b02e6
5 changed files with 84 additions and 9 deletions

View File

@@ -2,10 +2,9 @@ import axios from 'axios';
import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk';
import { StorageSettings } from '@scrypted/common/src/settings';
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
import crypto from 'crypto';
import { isSupported } from './types';
import { DiscoveryEndpoint, DiscoverEvent } from 'alexa-smarthome-ts';
import { AlexaHandler, capabilityHandlers } from './types/common';
import { AlexaHandler, capabilityHandlers, supportedTypes } from './types/common';
import { createMessageId } from './message';
const { systemManager, deviceManager } = sdk;
@@ -35,6 +34,44 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
this.handlers.set('Alexa.Discovery', this.alexaDiscovery);
this.syncDevices();
systemManager.listen(async (eventSource, eventDetails, eventData) => {
if (!this.storageSettings.values.syncedDevices.includes(eventSource.id))
return;
const supportedType = supportedTypes.get(eventSource.type);
if (!supportedType) {
this.console.warn(`${eventSource.name} no longer supported type?`);
return;
}
const report = await supportedType.reportState(eventSource, eventDetails, eventData);
if (report?.type === 'event') {
const accessToken = await this.getAccessToken();
const data = {
"context": {},
"event": {
"header": {
"messageId": createMessageId(),
"namespace": report.namespace,
"name": report.name,
"payloadVersion": "3"
},
"endpoint": {
"scope": {
"type": "BearerToken",
"token": accessToken,
},
"endpointId": eventSource.id,
},
payload: report.payload,
}
}
await this.postEvent(accessToken, data);
}
});
}
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
@@ -237,7 +274,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
}
async saveEndpoints(endpoints: DiscoveryEndpoint<any>[]) {
const existingEndpoints: string[] = this.storageSettings.values.syncedDevices;
const existingEndpoints: string[] = this.storageSettings.values.syncedDevices;
const newEndpoints = endpoints.map(endpoint => endpoint.endpointId);
const deleted = new Set(existingEndpoints);

View File

@@ -25,6 +25,9 @@ addSupportedType(ScryptedDeviceType.Camera, {
},
],
}
},
async reportState() {
return undefined;
}
});

View File

@@ -1,14 +1,27 @@
import { HttpRequest, HttpResponse, ScryptedDevice, ScryptedDeviceType } from "@scrypted/sdk";
import { EventDetails, HttpRequest, HttpResponse, ScryptedDevice, ScryptedDeviceType } from "@scrypted/sdk";
import {DiscoveryEndpoint, Directive} from 'alexa-smarthome-ts';
export type AlexaHandler = (request: HttpRequest, response: HttpResponse, directive: Directive) => Promise<void>
export type AlexaCapabilityHandler<T> = (request: HttpRequest, response: HttpResponse, directive: Directive, device: ScryptedDevice & T) => Promise<void>
const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
export const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
export const capabilityHandlers = new Map<string, AlexaCapabilityHandler<any>>();
export interface EventReport {
type: 'event';
payload: any;
namespace: string;
name: string;
}
export interface StateReport {
type: 'state';
payload: any;
}
export interface SupportedType {
probe(device: ScryptedDevice): Partial<DiscoveryEndpoint<any>>;
reportState(eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any): Promise<EventReport | StateReport>;
}
export function addSupportedType(type: ScryptedDeviceType, supportedType: SupportedType) {

View File

@@ -1,13 +1,13 @@
import sdk, { ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
import { addSupportedType } from "./common";
import sdk, { BinarySensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk";
import { addSupportedType, EventReport } from "./common";
addSupportedType(ScryptedDeviceType.Doorbell, {
probe(device) {
if (!device.interfaces.includes(ScryptedInterface.VideoCamera))
if (!device.interfaces.includes(ScryptedInterface.VideoCamera) || !device.interfaces.includes(ScryptedInterface.BinarySensor))
return;
return {
displayCategories: ['DOORBELL'],
displayCategories: ['CAMERA'],
capabilities: [
{
"type": "AlexaInterface",
@@ -17,7 +17,28 @@ addSupportedType(ScryptedDeviceType.Doorbell, {
"isFullDuplexAudioSupported": false,
}
},
{
"type": "AlexaInterface",
"interface": "Alexa.DoorbellEventSource",
"version": "3",
"proactivelyReported": true
}
],
}
},
async reportState(eventSource: ScryptedDevice & BinarySensor, eventDetails, eventData): Promise<EventReport> {
if (!eventSource.binaryState)
return undefined;
return {
type: 'event',
namespace: 'Alexa.DoorbellEventSource',
name: 'DoorbellPress',
payload: {
"cause": {
"type": "PHYSICAL_INTERACTION"
},
"timestamp": new Date().toISOString(),
}
};
}
});

View File

@@ -1,4 +1,5 @@
import './camera';
import './doorbell';
export { isSupported} from './common';