309 lines
9.5 KiB
JavaScript
309 lines
9.5 KiB
JavaScript
const {
|
|
HCColorLamp,
|
|
StateUpdateManager,
|
|
utils
|
|
} = require('homecontrol-control-base');
|
|
|
|
const mqtt = require("async-mqtt");
|
|
|
|
class HCschalkematrix extends HCColorLamp {
|
|
constructor(config) {
|
|
super(config);
|
|
|
|
if (!("server" in this._configuration)) {
|
|
throw new Error(`Required configuration field "server" is missing"`);
|
|
}
|
|
|
|
if (!("control_topic" in this._configuration)) {
|
|
throw new Error(`Required configuration field "control_topic" is missing"`);
|
|
}
|
|
|
|
if (!("state_topic" in this._configuration)) {
|
|
throw new Error(`Required configuration field "state_topic" is missing"`);
|
|
}
|
|
|
|
if (!("max_brightness" in this._configuration)) {
|
|
this._configuration.max_brightness = 150;
|
|
}
|
|
|
|
this._client = null;
|
|
|
|
this._sumanager = new StateUpdateManager(this._state);
|
|
}
|
|
|
|
static get version() {
|
|
return require("./package.json").version;
|
|
}
|
|
|
|
// overwrite to make use of the SUManager
|
|
get state() {
|
|
return this._sumanager.state.clone();
|
|
}
|
|
|
|
get effects() {
|
|
return [
|
|
{name: "RGB Strobe", id: "1-80" },
|
|
{name: "Strobe", id: "2-99" },
|
|
{name: "Police", id: "6-90" },
|
|
{name: "Arrow Right SLOW", id: "3-40" },
|
|
{name: "Arrow Right FAST", id: "3-90" },
|
|
{name: "Arrow Left SLOW", id: "4-40" },
|
|
{name: "Arrow Left FAST", id: "4-90" },
|
|
{name: "Arrow Split SLOW", id: "5-50" },
|
|
{name: "Arrow Split FAST", id: "5-93" },
|
|
{name: "Wave White SLOW", id: "7-50" },
|
|
{name: "Wave White FAST", id: "7-90" },
|
|
{name: "Wave Red/Green SLOW", id: "10-50" },
|
|
{name: "Wave Red/Green FAST", id: "10-90" },
|
|
{name: "Spinner White", id: "11-80" },
|
|
{name: "Spinner Double", id: "14-80" },
|
|
{name: "Full Rainbow SLOW", id: "15-20" },
|
|
{name: "Full Rainbow MEDIUM", id: "15-50" },
|
|
{name: "Full Rainbow FAST", id: "15-90" },
|
|
{name: "Rainbow Slide SLOW", id: "17-50" },
|
|
{name: "Rainbow Slide FAST", id: "18-80" },
|
|
];
|
|
}
|
|
|
|
async init() {
|
|
this._client = await mqtt.connectAsync(this._configuration.server);
|
|
|
|
const initialState = this._sumanager.state;
|
|
initialState.color = utils.RGB_to_HSL({red: 255, green: 255, blue: 255 });
|
|
initialState.brightness = Math.round((128 / this._configuration.max_brightness) * 100);
|
|
this._sumanager.insertConfirmedState(initialState);
|
|
|
|
this._client.on("message", (topic, msgRaw) => {
|
|
let msg;
|
|
try {
|
|
msg = JSON.parse(msgRaw.toString());
|
|
} catch(e) {
|
|
return;
|
|
}
|
|
|
|
let newState = this.state;
|
|
newState.brightness = (msg.brightness / this._configuration.max_brightness) * 100;
|
|
newState.on = state_isOn(msg);
|
|
|
|
// only overwrite color if it isn't black
|
|
if (msg.color.r != 0 || msg.color.g != 0 || msg.color.b != 0) {
|
|
newState.color = utils.RGB_to_HSL({
|
|
red: msg.color.r,
|
|
green: msg.color.g,
|
|
blue: msg.color.b
|
|
});
|
|
}
|
|
|
|
let speeds = {};
|
|
for (let eff of this.effects) {
|
|
const matches = eff.id.match(/(\d+)-(\d+)/);
|
|
|
|
if (speeds[matches[1]] == undefined) {
|
|
speeds[matches[1]] = [];
|
|
}
|
|
speeds[matches[1]].push({speed: Number(matches[2]), id: eff.id });
|
|
}
|
|
|
|
// try to match the effect as best as possible
|
|
if (speeds[msg.effect] != undefined) {
|
|
if (speeds[msg.effect].length == 1) {
|
|
newState.effect = speeds[msg.effect][0].id;
|
|
} else {
|
|
let lowestDist = Infinity;
|
|
let lowestDistId = null;
|
|
|
|
for (let speed of speeds[msg.effect]) {
|
|
if (Math.abs(speed.speed - msg.speed) < lowestDist) {
|
|
lowestDist = Math.abs(speed.speed - msg.speed);
|
|
lowestDistId = speed.id;
|
|
}
|
|
}
|
|
|
|
newState.effect = lowestDistId;
|
|
}
|
|
} else if (msg.effect == 0) {
|
|
newState.effect = "none";
|
|
}
|
|
|
|
this._sumanager.insertConfirmedState(newState);
|
|
this.emit("state change", this.state);
|
|
});
|
|
|
|
await this._client.subscribe(this._configuration.state_topic);
|
|
}
|
|
|
|
async deinit() {
|
|
await this._client.end();
|
|
}
|
|
|
|
turnOn() {
|
|
let futureState = this.state;
|
|
futureState.on = true;
|
|
futureState.brightness = (128 / this._configuration.max_brightness) * 100;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
const prevColor = utils.HSL_to_RGB(this.state.color);
|
|
|
|
return Promise.all([
|
|
this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "color",
|
|
r: prevColor.red,
|
|
g: prevColor.green,
|
|
b: prevColor.blue,
|
|
exclusive: true,
|
|
})),
|
|
this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "brightness",
|
|
brightness: 128,
|
|
})),
|
|
]).then(() => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return true;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
turnOff() {
|
|
let futureState = this.state;
|
|
futureState.on = false;
|
|
futureState.brightness = (30 / this._configuration.max_brightness) * 100;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
return Promise.all([
|
|
this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "color",
|
|
r: 0,
|
|
g: 0,
|
|
b: 0,
|
|
exclusive: false,
|
|
})),
|
|
this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "clock",
|
|
})),
|
|
this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "brightness",
|
|
brightness: 30,
|
|
})),
|
|
]).then(() => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return true;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
setBrightness(brightness) {
|
|
let futureState = this.state;
|
|
futureState.brightness = brightness;
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
return this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "brightness",
|
|
brightness: Math.round(brightness / 100 * this._configuration.max_brightness),
|
|
})).then(() => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return true;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
setColor(color) {
|
|
let futureState = this.state;
|
|
futureState.color = utils.fillPartialHSL(color, futureState.color);
|
|
futureState.effect = "none";
|
|
let suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
let { red, green, blue } = utils.HSL_to_RGB(futureState.color);
|
|
|
|
return this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "color",
|
|
r: red,
|
|
g: green,
|
|
b: blue,
|
|
exclusive: true,
|
|
})).then(() => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return true;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
setEffect(id) {
|
|
const matches = id.match(/(\d+)-(\d+)/);
|
|
|
|
let effect = null;
|
|
let speed = null;
|
|
if (matches != null) {
|
|
effect = matches[1];
|
|
speed = matches[2];
|
|
}
|
|
|
|
const futureState = this.state;
|
|
let promise;
|
|
|
|
if (id == "none" || effect == null || speed == null) {
|
|
futureState.effect = "none";
|
|
let { red, green, blue } = utils.HSL_to_RGB(futureState.color);
|
|
|
|
promise = this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "color",
|
|
r: red,
|
|
g: green,
|
|
b: blue,
|
|
exclusive: true,
|
|
}));
|
|
} else {
|
|
futureState.effect = id; // id is "valid" / can be parsed
|
|
|
|
promise = this._client.publish(this._configuration.control_topic, JSON.stringify({
|
|
type: "effect",
|
|
effect: effect,
|
|
speed: speed,
|
|
exclusive: true,
|
|
}));
|
|
}
|
|
|
|
const suid = this._sumanager.registerUpdate(futureState);
|
|
|
|
return promise.then(() => {
|
|
this._sumanager.confirmUpdate(suid);
|
|
|
|
if (this._sumanager.highestConfirmedId == suid) {
|
|
this.emit("state change", this.state);
|
|
}
|
|
return true;
|
|
}).catch(err => {
|
|
this._sumanager.rejectUpdate(suid);
|
|
throw err;
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = HCschalkematrix;
|
|
|
|
function state_isOn(state) {
|
|
return (state.color.r != 0 || state.color.g != 0 || state.color.b != 0 || state.color.overlay == 0 || state.effect != 0);
|
|
} |