mirror of
https://github.com/koush/scrypted.git
synced 2026-06-20 16:40:30 +01:00
mqtt: Added support for ColorSettingTemperature and ColorSettingHsv to the MQTT support. (#1317)
This commit is contained in:
130
plugins/mqtt/package-lock.json
generated
130
plugins/mqtt/package-lock.json
generated
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "@scrypted/mqtt",
|
||||
"version": "0.0.76",
|
||||
"version": "0.0.77",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/mqtt",
|
||||
"version": "0.0.76",
|
||||
"version": "0.0.77",
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"aedes": "^0.46.1",
|
||||
"axios": "^0.23.0",
|
||||
"mqtt": "^4.2.8",
|
||||
@@ -18,6 +17,7 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.4.2",
|
||||
"@types/nunjucks": "^3.2.0"
|
||||
}
|
||||
},
|
||||
@@ -29,49 +29,50 @@
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.11.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.206",
|
||||
"version": "0.3.5",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.0"
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"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/node": "^18.11.18",
|
||||
"@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"
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
@@ -86,9 +87,13 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
"version": "18.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz",
|
||||
"integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nunjucks": {
|
||||
"version": "3.2.0",
|
||||
@@ -359,9 +364,9 @@
|
||||
"integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ=="
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -502,9 +507,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -574,9 +579,9 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/nunjucks": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
|
||||
"integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==",
|
||||
"dependencies": {
|
||||
"a-sync-waterfall": "^1.0.0",
|
||||
"asap": "^2.0.3",
|
||||
@@ -717,6 +722,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -836,39 +847,44 @@
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"@types/node": "^20.11.0",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
"version": "18.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz",
|
||||
"integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"@types/nunjucks": {
|
||||
"version": "3.2.0",
|
||||
@@ -1087,9 +1103,9 @@
|
||||
"integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
|
||||
},
|
||||
"from2": {
|
||||
"version": "2.3.0",
|
||||
@@ -1195,9 +1211,9 @@
|
||||
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -1253,9 +1269,9 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"nunjucks": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz",
|
||||
"integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==",
|
||||
"requires": {
|
||||
"a-sync-waterfall": "^1.0.0",
|
||||
"asap": "^2.0.3",
|
||||
@@ -1355,6 +1371,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"aedes": "^0.46.1",
|
||||
"axios": "^0.23.0",
|
||||
"mqtt": "^4.2.8",
|
||||
@@ -37,9 +36,10 @@
|
||||
"websocket-stream": "^5.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.4.2",
|
||||
"@types/nunjucks": "^3.2.0"
|
||||
},
|
||||
"version": "0.0.76"
|
||||
"version": "0.0.77"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import crypto from 'crypto';
|
||||
import { Brightness, DeviceProvider, Lock, LockState, MixinDeviceBase, OnOff, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty, Setting, Settings } from "@scrypted/sdk";
|
||||
import { Online, Brightness, ColorSettingHsv, ColorSettingTemperature, DeviceProvider, Lock, LockState, MixinDeviceBase, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty } from "@scrypted/sdk";
|
||||
import { Client, MqttClient, connect } from "mqtt";
|
||||
import { MqttDeviceBase } from "./api/mqtt-device-base";
|
||||
import nunjucks from 'nunjucks';
|
||||
import sdk from "@scrypted/sdk";
|
||||
import type { MqttProvider } from './main';
|
||||
import { getHsvFromXyColor, getXyYFromHsvColor } from './color-util';
|
||||
|
||||
const { deviceManager } = sdk;
|
||||
|
||||
@@ -20,7 +21,20 @@ typeMap.set('switch', {
|
||||
type: ScryptedDeviceType.Switch,
|
||||
});
|
||||
typeMap.set('light', {
|
||||
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.Brightness],
|
||||
getInterfaces(config: any) {
|
||||
const interfaces = [ScryptedInterface.OnOff, ScryptedInterface.Brightness];
|
||||
if (config.color_mode) {
|
||||
config.supported_color_modes.forEach(color_mode => {
|
||||
if (color_mode === 'xy')
|
||||
interfaces.push(ScryptedInterface.ColorSettingHsv);
|
||||
else if (color_mode === 'hs')
|
||||
interfaces.push(ScryptedInterface.ColorSettingHsv);
|
||||
else if (color_mode === 'color_temp')
|
||||
interfaces.push(ScryptedInterface.ColorSettingTemperature);
|
||||
});
|
||||
}
|
||||
return interfaces;
|
||||
},
|
||||
type: ScryptedDeviceType.Light,
|
||||
});
|
||||
typeMap.set('lock', {
|
||||
@@ -102,7 +116,7 @@ export class MqttAutoDiscoveryProvider extends MqttDeviceBase implements DeviceP
|
||||
|
||||
const nativeId = 'autodiscovered:' + this.nativeId + ':' + nativeIdSuffix;
|
||||
|
||||
let deviceInterfaces: string[];
|
||||
let deviceInterfaces: string[]
|
||||
if (type.interfaces)
|
||||
deviceInterfaces = type.interfaces;
|
||||
else
|
||||
@@ -111,8 +125,10 @@ export class MqttAutoDiscoveryProvider extends MqttDeviceBase implements DeviceP
|
||||
if (!deviceInterfaces)
|
||||
return;
|
||||
|
||||
deviceInterfaces.push(ScryptedInterface.Online);
|
||||
|
||||
let interfaces = [
|
||||
'@scrypted/mqtt',
|
||||
'@scrypted/mqtt'
|
||||
];
|
||||
interfaces.push(...deviceInterfaces);
|
||||
// try combine into existing device if this mqtt device presents
|
||||
@@ -196,13 +212,26 @@ function scaleBrightness(scryptedBrightness: number, brightnessScale: number) {
|
||||
return Math.round(scryptedBrightness * brightnessScale / 100);
|
||||
}
|
||||
|
||||
function getMiredFromKelvin(kelvin: number) {
|
||||
return Math.round(1000000 / kelvin);
|
||||
}
|
||||
|
||||
function getKelvinFromMired(mired: number) {
|
||||
return Math.round(1000000 / mired);
|
||||
}
|
||||
|
||||
function unscaleBrightness(mqttBrightness: number, brightnessScale: number) {
|
||||
brightnessScale = brightnessScale || 255;
|
||||
return Math.round(mqttBrightness * 100 / brightnessScale);
|
||||
}
|
||||
|
||||
export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff, Brightness, Lock {
|
||||
export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements Online, OnOff, Brightness, Lock, ColorSettingTemperature, ColorSettingHsv {
|
||||
messageListeners: ((topic: string, payload: Buffer) => void)[] = [];
|
||||
debounceCallbacks: Map<string, Set<(payload: Buffer) => void>>;
|
||||
modelId: any;
|
||||
xyY: { x: number; y: number; brightness: number; };
|
||||
colorMode: string;
|
||||
|
||||
|
||||
constructor(nativeId: string, public provider: MqttAutoDiscoveryProvider, noBind?: boolean) {
|
||||
super(nativeId);
|
||||
@@ -216,6 +245,13 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff
|
||||
this.console.warn('delayed bind')
|
||||
return;
|
||||
}
|
||||
|
||||
this.debounceCallbacks = new Map<string, Set<(payload: Buffer) => void>>();
|
||||
|
||||
const { client } = provider;
|
||||
client.on('message', this.listener.bind(this));
|
||||
this.messageListeners.push(this.listener.bind(this));
|
||||
|
||||
this.bind();
|
||||
}
|
||||
|
||||
@@ -227,25 +263,135 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff
|
||||
}
|
||||
|
||||
bindMessage(topic: string, cb: (payload: Buffer) => void) {
|
||||
this.console.log('subscribing', topic);
|
||||
const listener = (messageTopic: string, payload: Buffer) => {
|
||||
if (topic !== messageTopic)
|
||||
return;
|
||||
this.console.log('message', topic, payload?.toString());
|
||||
try {
|
||||
cb(payload);
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('callback error', e);
|
||||
}
|
||||
};
|
||||
this.provider.client.on('message', listener);
|
||||
this.messageListeners.push(listener);
|
||||
let set: Set<(payload: Buffer) => void> = this.debounceCallbacks.get(topic);
|
||||
if (set) {
|
||||
this.console.log('subscribing', topic);
|
||||
|
||||
set.add(cb);
|
||||
} else {
|
||||
this.console.log('subscribing to new topic', topic);
|
||||
|
||||
set = new Set([cb]);
|
||||
this.debounceCallbacks.set(topic, set);
|
||||
}
|
||||
}
|
||||
|
||||
listener(topic: string, payload: Buffer) {
|
||||
let set = this.debounceCallbacks?.get(topic);
|
||||
|
||||
if (!set)
|
||||
return;
|
||||
|
||||
this.console.log('message', topic, payload?.toString());
|
||||
try {
|
||||
set.forEach(callback => {
|
||||
callback(payload);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('callback error', e);
|
||||
}
|
||||
}
|
||||
|
||||
bind() {
|
||||
this.console.log('binding...');
|
||||
const { client } = this.provider;
|
||||
|
||||
this.debounceCallbacks = new Map<string, Set<(payload: Buffer) => void>>();
|
||||
|
||||
if (this.providedInterfaces.includes(ScryptedInterface.Online)) {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.Online);
|
||||
if (config.availability && config.availability.length > 0) {
|
||||
const availabilityTopic = config.availability[0].topic;
|
||||
client.subscribe(availabilityTopic);
|
||||
this.bindMessage(availabilityTopic,
|
||||
payload => this.online =
|
||||
(config.payload_on || 'online') === this.eval(config.availability[0].value_template || '{{ value_json.state }}', payload));
|
||||
}
|
||||
}
|
||||
if (this.providedInterfaces.includes(ScryptedInterface.ColorSettingHsv)) {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingHsv);
|
||||
const colorStateTopic = config.hs_state_topic || config.state_topic;
|
||||
client.subscribe(colorStateTopic);
|
||||
this.bindMessage(colorStateTopic,
|
||||
payload => {
|
||||
let obj = JSON.parse(payload.toString());
|
||||
|
||||
// exit updating the below because the user set the color_temp
|
||||
if (obj.color_mode !== "xy" && obj.color_mode !== "hs") {
|
||||
this.hsv = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// handle hs_value_template if present
|
||||
if (config.hs_value_template) {
|
||||
this.hsv = this.eval(config.hs_value_template, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle xy_value_template if present
|
||||
if (config.xy_value_template) {
|
||||
var xy = this.eval(config.xy_value_template, payload);
|
||||
this.hsv = getHsvFromXyColor(xy.x, xy.y, this.xyY?.brightness ?? 1);
|
||||
return;
|
||||
}
|
||||
|
||||
let color = obj.color;
|
||||
this.modelId = obj.device?.model;
|
||||
|
||||
// handle color_mode hs if present
|
||||
if (color.h !== undefined && color.s !== undefined) {
|
||||
this.colorMode = "hs";
|
||||
|
||||
// skip update if the colors match
|
||||
if (color.h === this.hsv.h && color.s === this.hsv.s)
|
||||
return;
|
||||
|
||||
const brightness = unscaleBrightness(obj.brightness, config.brightness_scale);
|
||||
this.hsv = {
|
||||
h: color.h,
|
||||
s: color.s,
|
||||
v: brightness
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// handle color_mode xy if present
|
||||
if (color.x !== undefined && color.y !== undefined) {
|
||||
this.colorMode = "xy";
|
||||
|
||||
const hsv = getHsvFromXyColor(color.x, color.y, this.xyY?.brightness ?? 100);
|
||||
this.hsv = {
|
||||
h: hsv.h,
|
||||
s: hsv.s,
|
||||
v: hsv.v
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.providedInterfaces.includes(ScryptedInterface.ColorSettingTemperature)) {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature);
|
||||
const colorTempStateTopic = config.color_temp_command_topic || config.state_topic;
|
||||
client.subscribe(colorTempStateTopic);
|
||||
this.bindMessage(colorTempStateTopic,
|
||||
payload => {
|
||||
let obj = JSON.parse(payload.toString());
|
||||
|
||||
// exit updating the below because the user set the color_temp
|
||||
if (obj.color_mode !== "color_temp") {
|
||||
this.colorTemperature = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.color_temp_value_template) {
|
||||
this.colorTemperature = this.eval(config.color_temp_value_template, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
this.colorTemperature = getKelvinFromMired(obj.color_temp);
|
||||
});
|
||||
}
|
||||
if (this.providedInterfaces.includes(ScryptedInterface.Brightness)) {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.Brightness);
|
||||
const brightnessStateTopic = config.brightness_state_topic || config.state_topic;
|
||||
@@ -292,11 +438,13 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff
|
||||
publishValue(command_topic: string, template: string, value: any, defaultValue: any) {
|
||||
if (value == null)
|
||||
value = defaultValue;
|
||||
|
||||
const payload = template ? nunjucks.renderString(template, {
|
||||
value_json: {
|
||||
value,
|
||||
}
|
||||
}) : value.toString();
|
||||
}) : JSON.stringify(value);
|
||||
|
||||
this.provider.client.publish(command_topic, Buffer.from(payload), {
|
||||
qos: 1,
|
||||
retain: true,
|
||||
@@ -305,32 +453,148 @@ export class MqttAutoDiscoveryDevice extends ScryptedDeviceBase implements OnOff
|
||||
|
||||
async turnOff(): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.OnOff);
|
||||
if (config.on_command_type === 'brightness')
|
||||
return this.publishValue(config.brightness_command_topic,
|
||||
config.brightness_value_template, 0, 0);
|
||||
return this.publishValue(config.command_topic,
|
||||
config.brightness_value_template,
|
||||
config.payload_off, 'OFF');
|
||||
}
|
||||
|
||||
if (config.on_command_type === 'brightness') {
|
||||
await this.setBrightnessInternal(0, config);
|
||||
return;
|
||||
}
|
||||
|
||||
let command = {
|
||||
state: "OFF"
|
||||
};
|
||||
|
||||
if (config.command_off_template) {
|
||||
this.publishValue(config.command_topic,
|
||||
config.command_off_template,
|
||||
command, "ON");
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
undefined, command, command);
|
||||
}
|
||||
}
|
||||
async turnOn(): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.OnOff);
|
||||
if (config.on_command_type === 'brightness')
|
||||
return this.publishValue(config.brightness_command_topic,
|
||||
config.brightness_value_template,
|
||||
config.brightness_scale || 255,
|
||||
config.brightness_scale || 255);
|
||||
return this.publishValue(config.command_topic,
|
||||
config.brightness_value_template,
|
||||
config.payload_on, 'ON');
|
||||
|
||||
if (config.on_command_type === 'brightness') {
|
||||
await this.setBrightnessInternal(config.brightness_scale || 255, config);
|
||||
return;
|
||||
}
|
||||
|
||||
let command = {
|
||||
state: "ON"
|
||||
};
|
||||
|
||||
if (config.command_on_template) {
|
||||
this.publishValue(config.command_topic,
|
||||
config.command_on_template,
|
||||
command, "ON");
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
undefined, command, command);
|
||||
}
|
||||
}
|
||||
async setBrightness(brightness: number): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.Brightness);
|
||||
const scaledBrightness = scaleBrightness(brightness, config.brightness_scale);
|
||||
this.publishValue(config.brightness_command_topic,
|
||||
config.brightness_value_template,
|
||||
scaledBrightness, scaledBrightness);
|
||||
await this.setBrightnessInternal(brightness, config);
|
||||
}
|
||||
async setBrightnessInternal(brightness: number, config: any): Promise<void> {
|
||||
const scaledBrightness = scaleBrightness(brightness, config.brightness_scale);
|
||||
|
||||
// use brightness_command_topic and fallback to JSON if not provided
|
||||
if (config.brightness_value_template) {
|
||||
this.publishValue(config.brightness_command_topic,
|
||||
config.brightness_value_template,
|
||||
scaledBrightness, scaledBrightness);
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
`{ "state": "${ scaledBrightness === 0 ? 'OFF' : 'ON'}", "brightness": ${scaledBrightness} }`,
|
||||
scaledBrightness, 255);
|
||||
}
|
||||
}
|
||||
async getTemperatureMaxK(): Promise<number> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature);
|
||||
return getKelvinFromMired(Math.min(config.min_mireds, config.max_mireds));
|
||||
}
|
||||
async getTemperatureMinK(): Promise<number> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature);
|
||||
return getKelvinFromMired(Math.max(config.min_mireds, config.max_mireds));
|
||||
}
|
||||
async setColorTemperature(kelvin: number): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingTemperature);
|
||||
|
||||
if (kelvin >= 0 || kelvin <= 100) {
|
||||
const min = await this.getTemperatureMinK();
|
||||
const max = await this.getTemperatureMaxK();
|
||||
const diff = (max - min) * (kelvin/100);
|
||||
kelvin = Math.round(min + diff);
|
||||
}
|
||||
|
||||
const mired = getMiredFromKelvin(kelvin);
|
||||
const color = {
|
||||
state: "ON",
|
||||
//color_mode: "color_temp",
|
||||
color_temp: mired ?? 370
|
||||
};
|
||||
|
||||
// use color_temp_command_topic and fallback to JSON if not provided
|
||||
if (config.color_temp_command_template) {
|
||||
this.publishValue(config.color_temp_command_topic,
|
||||
config.color_temp_command_template,
|
||||
color, color);
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
undefined, color, color);
|
||||
}
|
||||
}
|
||||
async setHsv(hue: number, saturation: number, value: number): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.ColorSettingHsv);
|
||||
|
||||
this.colorMode = this.colorMode ?? (config.supported_color_modes.includes("hs") ? "hs" : "xy");
|
||||
|
||||
if (this.colorMode === "hs") {
|
||||
const color = {
|
||||
state: "ON",
|
||||
//color_mode: "hs",
|
||||
color: {
|
||||
h: hue ?? 0,
|
||||
s: (saturation ?? 1) * 100
|
||||
}
|
||||
};
|
||||
|
||||
// use hs_command_topic and fallback to JSON if not provided
|
||||
if (config.hs_command_template) {
|
||||
this.publishValue(config.hs_command_topic,
|
||||
config.hs_command_template,
|
||||
color, color);
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
undefined, color, color);
|
||||
}
|
||||
} else if (this.colorMode === "xy") {
|
||||
const xy = getXyYFromHsvColor(hue, saturation, value, this.modelId);
|
||||
const color = {
|
||||
state: "ON",
|
||||
//color_mode: "xy",
|
||||
color: {
|
||||
x: xy.x,
|
||||
y: xy.y
|
||||
}
|
||||
};
|
||||
|
||||
this.xyY = xy;
|
||||
|
||||
// use xy_command_template and fallback to JSON if not provided
|
||||
if (config.xy_command_template) {
|
||||
this.publishValue(config.xy_command_topic,
|
||||
config.xy_command_template,
|
||||
color, color);
|
||||
} else {
|
||||
this.publishValue(config.command_topic,
|
||||
undefined, color, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async lock(): Promise<void> {
|
||||
const config = this.loadComponentConfig(ScryptedInterface.Lock);
|
||||
return this.publishValue(config.command_topic,
|
||||
|
||||
345
plugins/mqtt/src/color-util.ts
Normal file
345
plugins/mqtt/src/color-util.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
export function getXyYFromHsvColor(h: number, s: number, v: number, hueModelId: string = null) {
|
||||
if (s > 1 || v > 1 || h > 360)
|
||||
throw new Error('invalid hsv color, h must not be greater than 360, and s and v must not be greater than 1');
|
||||
|
||||
const rgb = hsvToRgb(h, s, v);
|
||||
const xyz = rgbToXyz(rgb.r, rgb.g, rgb.b);
|
||||
const { x, y, z } = xyz;
|
||||
|
||||
let xyY = {
|
||||
x: x / (x + y + z),
|
||||
y: y / (x + y + z),
|
||||
brightness: y
|
||||
};
|
||||
|
||||
if (!xyIsInGamutRange(xyY, hueModelId)) {
|
||||
xyY = getClosestColor(xyY, hueModelId);
|
||||
}
|
||||
|
||||
return xyY;
|
||||
}
|
||||
|
||||
export function getHsvFromXyColor(x: number, y: number, brightness: number) {
|
||||
if (x > 1 || y > 1 || brightness > 1)
|
||||
throw new Error('invalid xy color, x, y, and brightness must not be greater than 1');
|
||||
|
||||
const Y = brightness;
|
||||
const z = 1 - x - Y;
|
||||
const X = (Y / y) * x;
|
||||
const Z = (Y / y) * z;
|
||||
const rgb = xyzToRgb(X, Y, Z);
|
||||
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
||||
|
||||
const h: number = hsv[0];
|
||||
const s: number = hsv[1];
|
||||
const v: number = hsv[2];
|
||||
|
||||
return {
|
||||
h, s, v
|
||||
};
|
||||
}
|
||||
|
||||
export function getRgbFromXyColor(x: number, y: number, brightness: number) {
|
||||
if (x > 1 || y > 1 || brightness > 1)
|
||||
throw new Error('invalid xy color, x, y, and brightness must not be greater than 1');
|
||||
|
||||
const Y = brightness;
|
||||
const z = 1 - x - Y;
|
||||
const X = (Y / y) * x;
|
||||
const Z = (Y / y) * z;
|
||||
const rgb = xyzToRgb(X, Y, Z);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
export function getXyFromRgbColor(r: number, g: number, b: number, hueModelId: string = null) {
|
||||
if (r > 255 || g > 255 || b > 255)
|
||||
throw new Error('invalid rgb color, r, g, and b must not be greater than 255');
|
||||
|
||||
const xyz = rgbToXyz(r, g, b);
|
||||
const { x, y, z } = xyz;
|
||||
|
||||
let xyY = {
|
||||
x: x / (x + y + z),
|
||||
y: y / (x + y + z),
|
||||
brightness: y
|
||||
};
|
||||
|
||||
if (!xyIsInGamutRange(xyY, hueModelId)) {
|
||||
xyY = getClosestColor(xyY, hueModelId);
|
||||
}
|
||||
|
||||
return xyY;
|
||||
}
|
||||
|
||||
function xyzToRgb (x: number, y: number, z: number) {
|
||||
let r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
|
||||
let g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
|
||||
let b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
|
||||
|
||||
// Assume sRGB
|
||||
r = r > 0.0031308
|
||||
? ((1.055 * (r ** (1.0 / 2.4))) - 0.055)
|
||||
: r * 12.92;
|
||||
|
||||
g = g > 0.0031308
|
||||
? ((1.055 * (g ** (1.0 / 2.4))) - 0.055)
|
||||
: g * 12.92;
|
||||
|
||||
b = b > 0.0031308
|
||||
? ((1.055 * (b ** (1.0 / 2.4))) - 0.055)
|
||||
: b * 12.92;
|
||||
|
||||
r = Math.min(Math.max(0, r), 1);
|
||||
g = Math.min(Math.max(0, g), 1);
|
||||
b = Math.min(Math.max(0, b), 1);
|
||||
|
||||
return {
|
||||
r: r * 255,
|
||||
g: g * 255,
|
||||
b: b * 255
|
||||
};
|
||||
}
|
||||
|
||||
function hsvToRgb (h: number, s: number, v: number) {
|
||||
h /= 60;
|
||||
|
||||
const hi = Math.floor(h) % 6;
|
||||
|
||||
const f = h - Math.floor(h);
|
||||
const p = 255 * v * (1 - s);
|
||||
const q = 255 * v * (1 - (s * f));
|
||||
const t = 255 * v * (1 - (s * (1 - f)));
|
||||
v *= 255;
|
||||
|
||||
switch (hi) {
|
||||
case 0:
|
||||
return { r:v, g:t, b:p };
|
||||
case 1:
|
||||
return { r:q, g:v, b:p };
|
||||
case 2:
|
||||
return { r:p, g:v, b:t };
|
||||
case 3:
|
||||
return { r:p, g:q, b:v };
|
||||
case 4:
|
||||
return { r:t, g:p, b:v };
|
||||
case 5:
|
||||
return { r:v, g:p, b:q };
|
||||
}
|
||||
}
|
||||
|
||||
function rgbToXyz(r: number, g: number, b: number) {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
|
||||
r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
|
||||
g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
|
||||
b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
|
||||
|
||||
const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
|
||||
const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
||||
const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
|
||||
|
||||
return {
|
||||
x, y, z
|
||||
};
|
||||
}
|
||||
|
||||
function rgbToHsv (r: number, g: number, b: number) {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
|
||||
const v = Math.max(r, g, b);
|
||||
const diff = v - Math.min(r, g, b);
|
||||
const diffc = function (c) {
|
||||
return (v - c) / 6 / diff + 1 / 2;
|
||||
};
|
||||
|
||||
let rdif: number;
|
||||
let gdif: number;
|
||||
let bdif: number;
|
||||
let h: number;
|
||||
let s: number;
|
||||
|
||||
if (diff === 0) {
|
||||
h = 0;
|
||||
s = 0;
|
||||
} else {
|
||||
s = diff / v;
|
||||
rdif = diffc(r);
|
||||
gdif = diffc(g);
|
||||
bdif = diffc(b);
|
||||
|
||||
if (r === v) {
|
||||
h = bdif - gdif;
|
||||
} else if (g === v) {
|
||||
h = (1 / 3) + rdif - bdif;
|
||||
} else if (b === v) {
|
||||
h = (2 / 3) + gdif - rdif;
|
||||
}
|
||||
|
||||
if (h < 0) {
|
||||
h += 1;
|
||||
} else if (h > 1) {
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
h * 360, s, v
|
||||
];
|
||||
}
|
||||
|
||||
export function xyIsInGamutRange(xy: any, hueModelId: string = null) {
|
||||
let gamut = getLightColorGamutRange(hueModelId);
|
||||
if (Array.isArray(xy)) {
|
||||
xy = {
|
||||
x: xy[0],
|
||||
y: xy[1]
|
||||
};
|
||||
}
|
||||
|
||||
let v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]];
|
||||
let v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]];
|
||||
let v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]];
|
||||
|
||||
let dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]);
|
||||
let dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]);
|
||||
let dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]);
|
||||
let dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]);
|
||||
let dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]);
|
||||
|
||||
let invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
|
||||
|
||||
let u = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
||||
let v = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
||||
|
||||
return ((u >= 0) && (v >= 0) && (u + v < 1));
|
||||
}
|
||||
|
||||
export function getLightColorGamutRange(hueModelId: string = null): any {
|
||||
|
||||
// legacy LivingColors Bloom, Aura, Light Strips and Iris (Gamut A)
|
||||
let gamutA = {
|
||||
red: [0.704, 0.296],
|
||||
green: [0.2151, 0.7106],
|
||||
blue: [0.138, 0.08]
|
||||
};
|
||||
|
||||
// older model hue bulb (Gamut B)
|
||||
let gamutB = {
|
||||
red: [0.675, 0.322],
|
||||
green: [0.409, 0.518],
|
||||
blue: [0.167, 0.04]
|
||||
};
|
||||
|
||||
// newer model Hue lights (Gamut C)
|
||||
let gamutC = {
|
||||
red: [0.692, 0.308],
|
||||
green: [0.17, 0.7],
|
||||
blue: [0.153, 0.048]
|
||||
};
|
||||
|
||||
let defaultGamut ={
|
||||
red: [1.0, 0],
|
||||
green: [0.0, 1.0],
|
||||
blue: [0.0, 0.0]
|
||||
};
|
||||
|
||||
let philipsModels = {
|
||||
"9290012573A": gamutB
|
||||
};
|
||||
|
||||
if(!!philipsModels[hueModelId]){
|
||||
return philipsModels[hueModelId];
|
||||
}
|
||||
|
||||
return defaultGamut;
|
||||
}
|
||||
|
||||
export function getClosestColor(xy: any, hueModelId: string = null) {
|
||||
function getLineDistance(pointA, pointB){
|
||||
return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y);
|
||||
}
|
||||
|
||||
function getClosestPoint(xy, pointA, pointB) {
|
||||
let xy2a = [xy.x - pointA.x, xy.y - pointA.y];
|
||||
let a2b = [pointB.x - pointA.x, pointB.y - pointA.y];
|
||||
let a2bSqr = Math.pow(a2b[0],2) + Math.pow(a2b[1],2);
|
||||
let xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1];
|
||||
let t = xy2a_dot_a2b /a2bSqr;
|
||||
|
||||
return {
|
||||
x: pointA.x + a2b[0] * t,
|
||||
y: pointA.y + a2b[1] * t,
|
||||
brightness: xy.brightness
|
||||
}
|
||||
}
|
||||
|
||||
let gamut = getLightColorGamutRange(hueModelId);
|
||||
|
||||
let greenBlue = {
|
||||
a: {
|
||||
x: gamut.green[0],
|
||||
y: gamut.green[1]
|
||||
},
|
||||
b: {
|
||||
x: gamut.blue[0],
|
||||
y: gamut.blue[1]
|
||||
}
|
||||
};
|
||||
|
||||
let greenRed = {
|
||||
a: {
|
||||
x: gamut.green[0],
|
||||
y: gamut.green[1]
|
||||
},
|
||||
b: {
|
||||
x: gamut.red[0],
|
||||
y: gamut.red[1]
|
||||
}
|
||||
};
|
||||
|
||||
let blueRed = {
|
||||
a: {
|
||||
x: gamut.red[0],
|
||||
y: gamut.red[1]
|
||||
},
|
||||
b: {
|
||||
x: gamut.blue[0],
|
||||
y: gamut.blue[1]
|
||||
}
|
||||
};
|
||||
|
||||
let closestColorPoints = {
|
||||
greenBlue : getClosestPoint(xy,greenBlue.a,greenBlue.b),
|
||||
greenRed : getClosestPoint(xy,greenRed.a,greenRed.b),
|
||||
blueRed : getClosestPoint(xy,blueRed.a,blueRed.b)
|
||||
};
|
||||
|
||||
let distance = {
|
||||
greenBlue : getLineDistance(xy,closestColorPoints.greenBlue),
|
||||
greenRed : getLineDistance(xy,closestColorPoints.greenRed),
|
||||
blueRed : getLineDistance(xy,closestColorPoints.blueRed)
|
||||
};
|
||||
|
||||
let closestDistance;
|
||||
let closestColor;
|
||||
for (let i in distance){
|
||||
if(distance.hasOwnProperty(i)){
|
||||
if(!closestDistance){
|
||||
closestDistance = distance[i];
|
||||
closestColor = i;
|
||||
}
|
||||
|
||||
if(closestDistance > distance[i]){
|
||||
closestDistance = distance[i];
|
||||
closestColor = i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return closestColorPoints[closestColor];
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "Node16",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
|
||||
Reference in New Issue
Block a user