mirror of
https://github.com/koush/scrypted.git
synced 2026-02-12 18:12:04 +00:00
hue: migrate
This commit is contained in:
4
plugins/hue/.gitignore
vendored
4
plugins/hue/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
@@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/*.map
|
||||
22
plugins/hue/.vscode/launch.json
vendored
22
plugins/hue/.vscode/launch.json
vendored
@@ -1,22 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"address": "${config:scrypted.debugHost}",
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "pwa-node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/hue/.vscode/settings.json
vendored
4
plugins/hue/.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
20
plugins/hue/.vscode/tasks.json
vendored
20
plugins/hue/.vscode/tasks.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "scrypted: deploy+debug",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# A Philips Hue plugin
|
||||
|
||||
## npm commands
|
||||
* npm run scrypted-webpack
|
||||
* npm run scrypted-deploy <ipaddress>
|
||||
* npm run scrypted-debug <ipaddress>
|
||||
|
||||
## scrypted distribution via npm
|
||||
1. Ensure package.json is set up properly for publishing on npm.
|
||||
2. npm publish
|
||||
|
||||
## Visual Studio Code configuration
|
||||
|
||||
* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server.
|
||||
* Launch Scrypted Debugger from the launch menu.
|
||||
216
plugins/hue/package-lock.json
generated
216
plugins/hue/package-lock.json
generated
@@ -1,216 +0,0 @@
|
||||
{
|
||||
"name": "@scrypted/hue",
|
||||
"version": "1.2.20",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/hue",
|
||||
"version": "1.2.20",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"node-hue-api": "^4.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.7.1",
|
||||
"webpack-merge": "^4.2.1"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.199",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
|
||||
"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bottleneck": {
|
||||
"version": "2.19.5",
|
||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
|
||||
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/get-ssl-certificate": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/get-ssl-certificate/-/get-ssl-certificate-2.3.3.tgz",
|
||||
"integrity": "sha512-aKYXS1S5+2IYw4W5+lKC/M+lvaNYPe0PhnQ144NWARcBg35H3ZvyVZ6y0LNGtiAxggFBHeO7LaVGO4bgHK4g1Q=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-hue-api": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-hue-api/-/node-hue-api-4.0.10.tgz",
|
||||
"integrity": "sha512-s+UvFttQfNXFadk8p6N9q9A5hteY2Q48W/mVze9nFPR5gwPH374cdA61ezKOx1WgBrN4btHj1z81veznhEZZAA==",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"bottleneck": "^2.19.5",
|
||||
"get-ssl-certificate": "^2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-merge": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz",
|
||||
"integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
|
||||
"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"bottleneck": {
|
||||
"version": "2.19.5",
|
||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
|
||||
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A=="
|
||||
},
|
||||
"get-ssl-certificate": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/get-ssl-certificate/-/get-ssl-certificate-2.3.3.tgz",
|
||||
"integrity": "sha512-aKYXS1S5+2IYw4W5+lKC/M+lvaNYPe0PhnQ144NWARcBg35H3ZvyVZ6y0LNGtiAxggFBHeO7LaVGO4bgHK4g1Q=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-hue-api": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-hue-api/-/node-hue-api-4.0.10.tgz",
|
||||
"integrity": "sha512-s+UvFttQfNXFadk8p6N9q9A5hteY2Q48W/mVze9nFPR5gwPH374cdA61ezKOx1WgBrN4btHj1z81veznhEZZAA==",
|
||||
"requires": {
|
||||
"axios": "^0.21.1",
|
||||
"bottleneck": "^2.19.5",
|
||||
"get-ssl-certificate": "^2.3.3"
|
||||
}
|
||||
},
|
||||
"webpack-merge": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz",
|
||||
"integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "@scrypted/hue",
|
||||
"version": "1.2.20",
|
||||
"description": "A Philips Hue plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
"build": "scrypted-webpack",
|
||||
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
|
||||
"prescrypted-vscode-launch": "scrypted-webpack",
|
||||
"scrypted-vscode-launch": "scrypted-deploy-debug",
|
||||
"scrypted-deploy-debug": "scrypted-deploy-debug",
|
||||
"scrypted-debug": "scrypted-debug",
|
||||
"scrypted-deploy": "scrypted-deploy",
|
||||
"scrypted-readme": "scrypted-readme",
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"keywords": [
|
||||
"hue",
|
||||
"scrypted",
|
||||
"plugin"
|
||||
],
|
||||
"scrypted": {
|
||||
"name": "Hue Bridge",
|
||||
"type": "DeviceProvider",
|
||||
"interfaces": [
|
||||
"DeviceProvider",
|
||||
"Settings"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.7.1",
|
||||
"webpack-merge": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hue-api": "^4.0.9"
|
||||
}
|
||||
}
|
||||
83
plugins/hue/src/bootstrap.js
vendored
83
plugins/hue/src/bootstrap.js
vendored
@@ -1,83 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const url = require('url')
|
||||
, axios = require('axios')
|
||||
, Transport = require('node-hue-api/lib/api/http/Transport')
|
||||
, Api = require('node-hue-api/lib/api/Api')
|
||||
;
|
||||
|
||||
const DEBUG = /node-hue-api/.test(process.env.NODE_DEBUG);
|
||||
|
||||
/**
|
||||
* @typedef {import('./Api)} Api
|
||||
* @type {LocalBootstrap}
|
||||
*/
|
||||
module.exports = class LocalBootstrap {
|
||||
|
||||
/**
|
||||
* Create a Local Netowrk Bootstrap for connecting to the Hue Bridge. The connection is ALWAYS over TLS/HTTPS.
|
||||
*
|
||||
* @param {String} hostname The hostname or ip address of the hue bridge on the lcoal network.
|
||||
* @param {number=} port The port number for the connections, defaults to 443 and should not need to be specified in the majority of use cases.
|
||||
*/
|
||||
constructor(hostname, port) {
|
||||
this._baseUrl = url.format({protocol: 'https', hostname: hostname, port: port || 443});
|
||||
this._hostname = hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Base URL for the local connection to the bridge.
|
||||
* @returns {String}
|
||||
*/
|
||||
get baseUrl() {
|
||||
return this._baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hostname being used to connect to the hue bridge (ip address or fully qualified domain name).
|
||||
* @returns {String}
|
||||
*/
|
||||
get hostname() {
|
||||
return this._hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the Hue Bridge using the local network.
|
||||
*
|
||||
* The connection will perform checks on the Hue Bridge TLS Certificate to verify it is correct before sending any
|
||||
* sensitive information.
|
||||
*
|
||||
* @param {String=} username The username to use when connecting, can be null, but will severely limit the endpoints that you can call/access
|
||||
* @param {String=} clientkey The clientkey for the user, used by the entertainment API, can be null
|
||||
* @param {Number=} timeout The timeout for requests sent to the Hue Bridge. If not set will default to 20 seconds.
|
||||
* @returns {Promise<Api>} The API for interacting with the hue bridge.
|
||||
*/
|
||||
connect(username, clientkey, timeout) {
|
||||
const self = this
|
||||
, hostname = self.hostname
|
||||
, baseUrl = self.baseUrl
|
||||
;
|
||||
|
||||
return axios.get(`${baseUrl}/api/config`)
|
||||
.then(res => {
|
||||
const bridgeId = res.data.bridgeid.toLowerCase();
|
||||
|
||||
const apiBaseUrl = `${baseUrl}/api`
|
||||
, transport = new Transport(username, axios.create({baseURL: apiBaseUrl}))
|
||||
, config = {
|
||||
remote: false,
|
||||
baseUrl: apiBaseUrl,
|
||||
clientkey: clientkey,
|
||||
username: username,
|
||||
}
|
||||
;
|
||||
|
||||
return new Api(config, transport);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getTimeout(timeout) {
|
||||
return timeout || 20000;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
log.i('Using empty dgram shim. NupnpSearch will be used.');
|
||||
@@ -1,271 +0,0 @@
|
||||
import hue from "node-hue-api";
|
||||
const { v3 } = hue;
|
||||
import sdk, { Brightness, Device, DeviceManager, DeviceProvider, OnOff, Refresh, ScryptedDeviceBase, Setting, Settings } from '@scrypted/sdk';
|
||||
const { deviceManager, log } = sdk;
|
||||
import axios from "axios";
|
||||
import Api from "node-hue-api/lib/api/Api";
|
||||
|
||||
const LightState = v3.lightStates.LightState;
|
||||
const LocalBootstrap = require("./bootstrap");
|
||||
|
||||
const StateSetters = {
|
||||
OnOff: function (s, state) {
|
||||
state.on = !!(s && s.on && s.reachable);
|
||||
},
|
||||
Brightness: function (s, state) {
|
||||
state.brightness = (s && s.bri && (s.bri * 100 / 254)) || 0;
|
||||
},
|
||||
ColorSettingTemperature: function (s, state) {
|
||||
state.colorTemperature = (s && s.ct && (1000000 / s.ct)) || 0;
|
||||
},
|
||||
ColorSettingHsv: function (st, state) {
|
||||
var h = (st && st.hue && st.hue / 182.5487) || 0;
|
||||
var s = (st && st.sat && (st.sat / 254)) || 0;
|
||||
var v = (st && st.bri && (st.bri / 254)) || 0;
|
||||
state.hsv = { h, s, v };
|
||||
}
|
||||
}
|
||||
|
||||
class HueBulb extends ScryptedDeviceBase implements OnOff, Brightness, Refresh {
|
||||
api: Api;
|
||||
light: any;
|
||||
device: Device;
|
||||
|
||||
constructor(api, light, device) {
|
||||
super(light.id.toString());
|
||||
|
||||
this.api = api;
|
||||
this.light = light;
|
||||
this.device = device;
|
||||
|
||||
// wait for this device to be synced, then report the current state.
|
||||
process.nextTick(() => {
|
||||
this.updateState(light.state);
|
||||
});
|
||||
}
|
||||
|
||||
async refresh(refreshInterface: string, userInitiated: boolean) {
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
updateState(state) {
|
||||
for (var event of this.device.interfaces) {
|
||||
var setter = StateSetters[event];
|
||||
if (setter) {
|
||||
setter(state, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _refresh() {
|
||||
const result = await this.api.lights.getLight(this.light.id);
|
||||
if (result && result.state) {
|
||||
this.updateState(result.state);
|
||||
}
|
||||
}
|
||||
|
||||
async getRefreshFrequency() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
async turnOff() {
|
||||
await this.api.lights.setLightState(this.light.id, new LightState().off());
|
||||
this._refresh();
|
||||
};
|
||||
|
||||
async turnOn() {
|
||||
await this.api.lights.setLightState(this.light.id, new LightState().on(undefined));
|
||||
this._refresh();
|
||||
};
|
||||
|
||||
async setBrightness(level) {
|
||||
await this.api.lights.setLightState(this.light.id, new LightState().brightness(level));
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
async setTemperature(kelvin) {
|
||||
var mired = Math.round(1000000 / kelvin);
|
||||
await this.api.lights.setLightState(this.light.id, new LightState().ct(mired));
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
async setHsv(h, s, v) {
|
||||
await this.api.lights.setLightState(this.light.id, new LightState().hsb(h, s * 100, v * 100));
|
||||
this._refresh();
|
||||
}
|
||||
}
|
||||
|
||||
class HueHub extends ScryptedDeviceBase implements DeviceProvider, Settings {
|
||||
api: Api;
|
||||
devices = {};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
(async() => {
|
||||
while (true) {
|
||||
try {
|
||||
await this.discoverDevices(0);
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 30000));
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
title: 'Bridge Address Override',
|
||||
key: 'bridgeAddress',
|
||||
placeholder: '192.168.2.100',
|
||||
value: localStorage.getItem('bridgeAddress'),
|
||||
},
|
||||
{
|
||||
title: 'Bridge ID',
|
||||
description: 'Bridge ID of the bridge currently in use. Unused if Address Override is present.',
|
||||
key: 'bridgeId',
|
||||
value: localStorage.getItem('bridgeId'),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string | number | boolean) {
|
||||
localStorage.setItem(key, value?.toString());
|
||||
this.discoverDevices(0);
|
||||
}
|
||||
|
||||
async discoverDevices(duration: number) {
|
||||
|
||||
let addressOverride = localStorage.getItem('bridgeAddress');
|
||||
let bridgeAddress: string;
|
||||
let bridgeId: string;
|
||||
|
||||
if (!addressOverride) {
|
||||
bridgeId = localStorage.getItem('bridgeId');
|
||||
if (bridgeId === 'manual')
|
||||
bridgeId = undefined;
|
||||
|
||||
const response = await axios.get('https://discovery.meethue.com', {
|
||||
headers: { accept: 'application/json' },
|
||||
});
|
||||
|
||||
if (response.status !== 200)
|
||||
throw new Error(`Status code unexpected when using N-UPnP endpoint: ${response.status}`);
|
||||
|
||||
const bridges = response.data;
|
||||
|
||||
if (!bridgeId) {
|
||||
if (bridges.length == 0) {
|
||||
log.a('No Hue bridges found. If you know the bridge address, enter it in Settings.');
|
||||
return;
|
||||
}
|
||||
else if (bridges.length != 1) {
|
||||
console.error('Multiple hue bridges found: ');
|
||||
for (const found of bridges) {
|
||||
console.error(found.id);
|
||||
}
|
||||
log.a('Multiple bridges found. Please specify which bridge to manage using the Plugin Setting "bridgeId"');
|
||||
return;
|
||||
}
|
||||
|
||||
bridgeId = bridges[0].id;
|
||||
console.log(`Found bridge ${bridgeId}. Setting as default.`);
|
||||
localStorage.setItem('bridgeId', bridgeId);
|
||||
}
|
||||
|
||||
for (let found of bridges) {
|
||||
if (found.id === bridgeId) {
|
||||
bridgeAddress = found.internalipaddress;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bridgeAddress) {
|
||||
console.warn(`Unable to locate bridge address for bridge: ${bridgeId}.`);
|
||||
console.warn('Unable to locate most recent bridge address with nupnp search. using last known address.')
|
||||
|
||||
bridgeAddress = localStorage.getItem('lastKnownBridgeAddress');
|
||||
}
|
||||
else {
|
||||
localStorage.setItem('lastKnownBridgeAddress', bridgeAddress);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bridgeAddress = addressOverride;
|
||||
bridgeId = 'manual';
|
||||
localStorage.setItem('bridgeId', 'manual');
|
||||
}
|
||||
|
||||
if (!bridgeAddress) {
|
||||
log.a('Unable to discover bridge. Enter an IP address in Settings');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Hue Bridges Found: ${bridgeId}`);
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
let username = localStorage.getItem(`user-${bridgeId}`);
|
||||
|
||||
if (!username) {
|
||||
const unauthenticatedApi = await v3.api.createLocal(bridgeAddress).connect();
|
||||
try {
|
||||
const createdUser = await unauthenticatedApi.users.createUser(bridgeAddress, 'ScryptedServer');
|
||||
console.log(`Created user on ${bridgeId}: ${createdUser}`);
|
||||
username = createdUser.username;
|
||||
localStorage.setItem(`user-${bridgeId}`, username);
|
||||
}
|
||||
catch (e) {
|
||||
console.error('user creation error', e);
|
||||
log.a('Unable to create user on bridge. You may need to press the pair button on the bridge.');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Querying devices...');
|
||||
|
||||
this.api = await new LocalBootstrap(bridgeAddress).connect(username);
|
||||
|
||||
log.clearAlerts();
|
||||
|
||||
|
||||
const result = await this.api.lights.getAll()
|
||||
|
||||
var devices = [];
|
||||
var payload = {
|
||||
devices: devices,
|
||||
};
|
||||
|
||||
// 182.5487
|
||||
|
||||
for (var light of result) {
|
||||
var interfaces = ['OnOff', 'Brightness', 'Refresh'];
|
||||
if (light.type.toLowerCase().indexOf('color') != -1) {
|
||||
interfaces.push('ColorSettingHsv');
|
||||
interfaces.push('ColorSettingTemperature');
|
||||
}
|
||||
|
||||
var device = {
|
||||
nativeId: light.id,
|
||||
name: light.name,
|
||||
interfaces: interfaces,
|
||||
type: 'Light',
|
||||
};
|
||||
|
||||
console.log('Found device', device);
|
||||
devices.push(device);
|
||||
|
||||
this.devices[light.id] = new HueBulb(this.api, light, device);
|
||||
}
|
||||
|
||||
deviceManager.onDevicesChanged(payload);
|
||||
this.console.log('device discovery complete');
|
||||
}
|
||||
getDevice(nativeId: string): object {
|
||||
return this.devices[nativeId];
|
||||
}
|
||||
}
|
||||
|
||||
export default new HueHub();
|
||||
@@ -1,19 +0,0 @@
|
||||
function Deferred() {
|
||||
}
|
||||
|
||||
function defer() {
|
||||
var deferred = new Deferred();
|
||||
deferred.promise = new Promise(function(resolve, reject) {
|
||||
deferred.resolve = resolve
|
||||
deferred.reject = reject
|
||||
})
|
||||
|
||||
return deferred
|
||||
}
|
||||
|
||||
var all = Promise.all;
|
||||
|
||||
export {
|
||||
defer,
|
||||
all,
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
log.i('Using empty xml2js shim. NupnpSearch will be used.');
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
const defaultWebpackConfig = require('@scrypted/sdk/bin').getDefaultWebpackConfig();
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
|
||||
const webpackConfig = {
|
||||
resolve: {
|
||||
alias: {
|
||||
// disable this since, since nupnp is used
|
||||
dgram: path.resolve(__dirname, 'src/dgram'),
|
||||
// empty xml2js, since nupnp is used.
|
||||
xml2js: path.resolve(__dirname, 'src/xml2js'),
|
||||
// Q shim to es6 promise polyfill.
|
||||
q: path.resolve(__dirname, 'src/q'),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = merge(defaultWebpackConfig, webpackConfig);
|
||||
Reference in New Issue
Block a user