mirror of
https://github.com/koush/scrypted.git
synced 2026-02-07 07:52:12 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f603877b | ||
|
|
9adc803206 | ||
|
|
031e290017 | ||
|
|
a9ab2d0110 | ||
|
|
9592b6087b | ||
|
|
5c34213b5d | ||
|
|
4435fe1e0a |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.0.20",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
@@ -35,7 +35,7 @@
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"alexa-smarthome-ts": "^0.0.1",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^1.3.4",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk';
|
||||
import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
|
||||
import { isSupported } from './types';
|
||||
@@ -12,16 +12,22 @@ const { systemManager, deviceManager } = sdk;
|
||||
const client_id = "amzn1.application-oa2-client.3283807e04d8408eb44a698c10f9dd13";
|
||||
const client_secret = "bed445e2b26730acd818b90e175b275f6b67b18ff8645e571c5b3e311fa75ee9";
|
||||
|
||||
class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, MixinProvider {
|
||||
class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, MixinProvider, Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
tokenInfo: {
|
||||
hide: true,
|
||||
json: true,
|
||||
json: true
|
||||
},
|
||||
syncedDevices: {
|
||||
multiple: true,
|
||||
hide: true,
|
||||
hide: true
|
||||
},
|
||||
apiEndpoint: {
|
||||
title: 'Alexa Endpoint',
|
||||
description: 'This is the endpoint Alexa will use to send events to. This is set after you login.',
|
||||
type: 'string',
|
||||
readonly: true
|
||||
}
|
||||
});
|
||||
|
||||
handlers = new Map<string, AlexaHandler>();
|
||||
@@ -85,6 +91,13 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
});
|
||||
}
|
||||
|
||||
getSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
putSetting(key: string, value: SettingValue): Promise<void> {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
|
||||
return mixinDevice;
|
||||
}
|
||||
@@ -99,11 +112,41 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
deviceManager.requestRestart();
|
||||
}
|
||||
|
||||
readonly endpoints: string[] = [
|
||||
'api.amazonalexa.com',
|
||||
'api.eu.amazonalexa.com',
|
||||
'api.fe.amazonalexa.com'
|
||||
];
|
||||
|
||||
async getAlexaEndpoint() : Promise<string> {
|
||||
if (this.storageSettings.values.apiEndpoint)
|
||||
return this.storageSettings.values.apiEndpoint;
|
||||
|
||||
try {
|
||||
const accessToken = await this.getAccessToken();
|
||||
const response = await axios.get(`https://${this.endpoints[0]}/v1/alexaApiEndpoint`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + accessToken,
|
||||
}
|
||||
});
|
||||
|
||||
const endpoint: string = response.data.endpoints[0];
|
||||
this.storageSettings.values.apiEndpoint = endpoint;
|
||||
return endpoint;
|
||||
} catch (err) {
|
||||
this.console.error(err);
|
||||
|
||||
// default to NA/RoW endpoint if we can't get the endpoint.
|
||||
return this.endpoints[0];
|
||||
}
|
||||
}
|
||||
|
||||
async postEvent(data: any) {
|
||||
const accessToken = await this.getAccessToken();
|
||||
const endpoint = await this.getAlexaEndpoint();
|
||||
const self = this;
|
||||
|
||||
return axios.post('https://api.amazonalexa.com/v3/events', data, {
|
||||
return axios.post(`https://${endpoint}/v3/events`, data, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + accessToken,
|
||||
}
|
||||
@@ -267,6 +310,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
const json = JSON.parse(request.body);
|
||||
const { grant } = json.directive.payload;
|
||||
this.storageSettings.values.tokenInfo = grant;
|
||||
this.storageSettings.values.apiEndpoint = undefined;
|
||||
this.accessToken = undefined;
|
||||
|
||||
const self = this;
|
||||
@@ -274,6 +318,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
self.console.error(`Failed to handle the AcceptGrant directive because ${reason}`);
|
||||
|
||||
this.storageSettings.values.tokenInfo = undefined;
|
||||
this.storageSettings.values.apiEndpoint = undefined;
|
||||
this.accessToken = undefined;
|
||||
|
||||
response.send(JSON.stringify({
|
||||
@@ -311,6 +356,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
this.console.error(`AcceptGrant.Response failed because ${error}`);
|
||||
|
||||
this.storageSettings.values.tokenInfo = undefined;
|
||||
this.storageSettings.values.apiEndpoint = undefined;
|
||||
this.accessToken = undefined;
|
||||
throw error;
|
||||
}
|
||||
@@ -455,7 +501,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
catch (e) {
|
||||
this.console.error(`request failed due to invalid authorization`, e);
|
||||
response.send(e.message, {
|
||||
code: 500,
|
||||
code: 500
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export class AlexaSignalingSession implements RTCSignalingSession {
|
||||
// this could be a low resolution screen, no way of knowing, so never send a
|
||||
// 1080p+ stream.
|
||||
screen: {
|
||||
devicePixelRatio: 1, // TODO: get this from the device
|
||||
width: 1280,
|
||||
height: 720,
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../common/fs/unavailable.jpg
|
||||
@@ -1,14 +1,13 @@
|
||||
import sdk, { DeviceManifest, DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, HumiditySensor, MediaObject, MotionSensor, OauthClient, Refresh, ScryptedDeviceType, ScryptedInterface, Setting, Settings, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode, VideoCamera, MediaStreamOptions, BinarySensor, DeviceInformation, RTCAVSignalingSetup, Camera, PictureOptions, ObjectsDetected, ObjectDetector, ObjectDetectionTypes, FFmpegInput, RequestMediaStreamOptions, Readme, RTCSignalingChannel, RTCSessionControl, RTCSignalingSession, ResponseMediaStreamOptions, RTCSignalingOptions, RTCSignalingSendIceCandidate, ScryptedMimeTypes, MediaStreamUrl } from '@scrypted/sdk';
|
||||
import { ScryptedDeviceBase } from '@scrypted/sdk';
|
||||
import qs from 'query-string';
|
||||
import ClientOAuth2 from 'client-oauth2';
|
||||
import { URL } from 'url';
|
||||
import axios from 'axios';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import fs from 'fs';
|
||||
import sdk, { BinarySensor, Camera, DeviceInformation, DeviceManifest, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, HumiditySensor, MediaObject, MediaStreamUrl, MotionSensor, OauthClient, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PictureOptions, Readme, Refresh, RequestMediaStreamOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, TemperatureCommand, TemperatureSetting, TemperatureUnit, Thermometer, ThermostatMode, VideoCamera } from '@scrypted/sdk';
|
||||
import axios from 'axios';
|
||||
import ClientOAuth2 from 'client-oauth2';
|
||||
import { randomBytes } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import throttle from 'lodash/throttle';
|
||||
import qs from 'query-string';
|
||||
import { URL } from 'url';
|
||||
|
||||
const { deviceManager, mediaManager, endpointManager, systemManager } = sdk;
|
||||
|
||||
@@ -101,6 +100,10 @@ class NestRTCSessionControl implements RTCSessionControl {
|
||||
constructor(public camera: NestCamera, public options: { streamExtensionToken: string, mediaSessionId: string }) {
|
||||
}
|
||||
|
||||
async setPlayback(options: { audio: boolean; video: boolean; }): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
async getRefreshAt(): Promise<number> {
|
||||
return this.refreshAt;
|
||||
}
|
||||
@@ -372,6 +375,52 @@ class NestThermostat extends ScryptedDeviceBase implements HumiditySensor, Therm
|
||||
this.reload();
|
||||
}
|
||||
|
||||
async setTemperature(command: TemperatureCommand): Promise<void> {
|
||||
// set this in case round trip is slow.
|
||||
let { mode, setpoint } = command;
|
||||
if (mode) {
|
||||
const nestMode = toNestMode(mode);
|
||||
this.device.traits['sdm.devices.traits.ThermostatMode'].mode = nestMode;
|
||||
|
||||
this.executeCommandSetMode = {
|
||||
command: 'sdm.devices.commands.ThermostatMode.SetMode',
|
||||
params: {
|
||||
mode: nestMode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (command.setpoint) {
|
||||
mode ||= fromNestMode(this.device.traits['sdm.devices.traits.ThermostatMode'].mode);
|
||||
|
||||
this.executeCommandSetCelsius = {
|
||||
command: setpointReverseMap.get(mode),
|
||||
params: {
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof command.setpoint === 'number') {
|
||||
if (mode === ThermostatMode.Heat) {
|
||||
this.executeCommandSetCelsius.params.heatCelsius = command.setpoint;
|
||||
|
||||
}
|
||||
else if (mode === ThermostatMode.Cool) {
|
||||
this.executeCommandSetCelsius.params.coolCelsius = command.setpoint;
|
||||
}
|
||||
else {
|
||||
this.executeCommandSetCelsius.params.coolCelsius = command.setpoint;
|
||||
this.executeCommandSetCelsius.params.heatCelsius = command.setpoint;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.executeCommandSetCelsius.params.heatCelsius = command[0];
|
||||
this.executeCommandSetCelsius.params.coolCelsius = command[1];
|
||||
}
|
||||
}
|
||||
await this.executeThrottle();
|
||||
await this.refresh(null, true);
|
||||
}
|
||||
|
||||
async setTemperatureUnit(temperatureUnit: TemperatureUnit): Promise<void> {
|
||||
// not supported by API. throw?
|
||||
}
|
||||
@@ -398,26 +447,37 @@ class NestThermostat extends ScryptedDeviceBase implements HumiditySensor, Therm
|
||||
const heat = device.traits?.['sdm.devices.traits.ThermostatTemperatureSetpoint']?.heatCelsius;
|
||||
const cool = device.traits?.['sdm.devices.traits.ThermostatTemperatureSetpoint']?.coolCelsius;
|
||||
|
||||
let setpoint: number | [number, number];
|
||||
if (this.thermostatMode === ThermostatMode.Heat) {
|
||||
this.thermostatSetpoint = heat;
|
||||
this.thermostatSetpointHigh = undefined;
|
||||
this.thermostatSetpointLow = undefined;
|
||||
setpoint = heat;
|
||||
}
|
||||
else if (this.thermostatMode === ThermostatMode.Cool) {
|
||||
this.thermostatSetpoint = cool;
|
||||
this.thermostatSetpointHigh = undefined;
|
||||
this.thermostatSetpointLow = undefined;
|
||||
setpoint = cool;
|
||||
}
|
||||
else if (this.thermostatMode === ThermostatMode.HeatCool) {
|
||||
this.thermostatSetpoint = undefined;
|
||||
this.thermostatSetpointHigh = heat;
|
||||
this.thermostatSetpointLow = cool;
|
||||
setpoint = [heat, cool];
|
||||
}
|
||||
else {
|
||||
this.thermostatSetpoint = undefined;
|
||||
this.thermostatSetpointHigh = undefined;
|
||||
this.thermostatSetpointLow = undefined;
|
||||
}
|
||||
|
||||
this.temperatureSetting = {
|
||||
activeMode: this.thermostatActiveMode,
|
||||
mode: this.thermostatMode,
|
||||
setpoint,
|
||||
availableModes: modes,
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(refreshInterface: string, userInitiated: boolean): Promise<void> {
|
||||
@@ -560,6 +620,8 @@ export class GoogleSmartDeviceAccess extends ScryptedDeviceBase implements Oauth
|
||||
}
|
||||
})();
|
||||
}
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
}
|
||||
|
||||
async onRequest(request: HttpRequest, response: HttpResponse): Promise<void> {
|
||||
const payload = JSON.parse(Buffer.from(JSON.parse(request.body).message.data, 'base64').toString());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.6.20",
|
||||
"version": "0.6.22",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"build": "tsc --outDir dist",
|
||||
"postbuild": "node test/check-build-output.js",
|
||||
"prepublishOnly": "npm version patch && git add package.json && npm run build && git commit -m prepublish",
|
||||
"postpublish": "git push origin v$npm_package_version",
|
||||
"postpublish": "git tag v$npm_package_version && git push origin v$npm_package_version",
|
||||
"docker": "scripts/github-workflow-publish-docker.sh"
|
||||
},
|
||||
"author": "",
|
||||
|
||||
Reference in New Issue
Block a user