From 07646b952aee5e8e1df4eeec8ccc14321ee79fea Mon Sep 17 00:00:00 2001 From: Jan Scheiper Date: Thu, 20 Feb 2020 22:58:51 +0100 Subject: [PATCH] added some effects and fixed a lot of bugs --- HCTasmota.js | 235 ++++++++++++++++++++++++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 216 insertions(+), 21 deletions(-) diff --git a/HCTasmota.js b/HCTasmota.js index 2496279..a891b2a 100644 --- a/HCTasmota.js +++ b/HCTasmota.js @@ -7,6 +7,56 @@ const { const axios = require('axios'); const { URL } = require('url'); +class ColorJumpEffect { + constructor(colors, delay, sendCommand) { + this._colors = colors; + this._delay = delay; + this._next = 0; + + this.sendCommand = sendCommand; + } + + get delay() { + return this._delay; + } + + step() { + let color = this._colors[this._next]; + let cmd = `Color ${color[0]},${color[1]},${color[2]}`; + + this._next = (this._next + 1) % this._colors.length; + + // console.log(cmd); + + return this.sendCommand(cmd); + } + + init() { + return Promise.resolve(); + } + + deinit() { + return Promise.resolve(); + } +} + +class SchemeEffect { + constructor(scheme, speed, sendCommand) { + this._scheme = scheme; + this._speed = speed; + + this.sendCommand = sendCommand; + } + + init() { + return this.sendCommand(`Backlog Speed ${this._speed}; Scheme ${this._scheme}`); + } + + deinit() { + return this.sendCommand(`Scheme 0`); + } +} + class HCTasmota extends HCColorLamp { constructor(config) { super(config); @@ -15,7 +65,10 @@ class HCTasmota extends HCColorLamp { throw new Error(`Required configuration field "address" is missing"`); } - this._sumanager = new StateUpdateManager(this._state); + this._sumanager = new StateUpdateManager(this._state); + + this._effectInterval = null; + this._effect = null; } // overwrite to make use of the SUManager @@ -23,52 +76,176 @@ class HCTasmota extends HCColorLamp { return this._sumanager.state.clone(); } + get effects() { + return [ + { name: "Color Jump 3 SLOW", id: "jump3-slow" }, + { name: "Color Jump 3 FAST", id: "jump3-fast" }, + { name: "Color Cycle Up SLOW", id: "scheme-2-slow" }, + { name: "Color Cycle Up FAST", id: "scheme-2-fast" }, + { name: "Color Cycle Down SLOW", id: "scheme-3-slow" }, + { name: "Color Cycle Down FAST", id: "scheme-3-fast" }, + { name: "Color Cycle Random SLOW", id: "scheme-4-slow" }, + { name: "Color Cycle Random FAST", id: "scheme-4-fast" }, + ]; + } + + _sendCommand(cmd) { + let url = new URL("/cm", this._configuration.address); + url.searchParams.append("cmnd", cmd); + + return axios.post(url.toString()); + } + + init() { + return this._sendCommand("State").then(res => { + if (res.data.Scheme != 0) { + return this._sendCommand("Scheme 0"); + } + }); + } + turnOn() { + if (this.state.effect != "none") { + return this.setEffect("none").then(() => this.turnOn()); + } + let futureState = this.state; futureState.on = true; let suid = this._sumanager.registerUpdate(futureState); - let url = new URL("/cm", this._configuration.address); - url.searchParams.append("cmnd", "Power on"); - - return axios.post(url.toString()).then(resolveHelper(this, suid), rejectHelper(this, suid)); + return this._sendCommand("Power on").then(resolveHelper(this, suid), rejectHelper(this, suid)); } turnOff() { + if (this.state.effect != "none") { + return this.setEffect("none").then(() => this.turnOff()); + } + let futureState = this.state; futureState.on = false; let suid = this._sumanager.registerUpdate(futureState); - let url = new URL("/cm", this._configuration.address); - url.searchParams.append("cmnd", "Power off"); - - return axios.post(url.toString()).then(resolveHelper(this, suid), rejectHelper(this, suid)); + return this._sendCommand("Power off").then(resolveHelper(this, suid), rejectHelper(this, suid)); } setBrightness(brightness) { - let futureState = this.state; - futureState.brightness = brightness; - let suid = this._sumanager.registerUpdate(futureState); - - let url = new URL("/cm", this._configuration.address); - url.searchParams.append("cmnd", `HsbColor3 ${Math.round(brightness)}`); + if (this.state.effect != "none") { + return this.setEffect("none").then(() => this.setBrightness(brightness)); + } - return axios.post(url.toString()).then(resolveHelper(this, suid), rejectHelper(this, suid)); + let futureState = this.state; + futureState.brightness = Math.round(brightness); + futureState.on = true; + let suid = this._sumanager.registerUpdate(futureState); + + return this._sendCommand(`HsbColor3 ${futureState.brightness}`).then(resolveHelper(this, suid), rejectHelper(this, suid)); } setColor(color) { + if (this.state.effect != "none") { + return this.setEffect("none").then(() => this.setColor(color)); + } + let futureState = this.state; - futureState.color = utils.fillPartialHSL(color, futureState.color); + futureState.on = true; + futureState.color = utils.fillPartialHSL(color, futureState.color); + + futureState.color.hue = Math.round(futureState.color.hue); + futureState.color.sat = Math.round(futureState.color.sat); + futureState.color.l = Math.round(futureState.color.l); + let suid = this._sumanager.registerUpdate(futureState); let url = new URL("/cm", this._configuration.address); - url.searchParams.append("cmnd", `HsbColor ${Math.round(color.hue)},${Math.round(color.sat)},${futureState.brightness}`); + url.searchParams.append("cmnd", `HsbColor ${futureState.color.hue},${futureState.color.sat},${futureState.brightness}`); return axios.post(url.toString()).then(resolveHelper(this, suid), rejectHelper(this, suid)); } setEffect(id) { + let futureState = this.state; + let promise; + if (this._effectInterval != null) { + clearTimeout(this._effectInterval); + } + + if (this._effect != null) { + promise = this._effect.deinit(); + } else { + promise = Promise.resolve(); + } + + if (id == "none") { + promise.then(() => { + this._effectInterval = null; + this._effect = null; + + futureState.effect = "none"; + + let url = new URL("/cm", this._configuration.address); + url.searchParams.append("cmnd", `HsbColor ${Math.round(futureState.color.hue)},${Math.round(futureState.color.sat)},${futureState.brightness}`); + + return axios.post(url.toString()); + }); + } else { + promise.then(() => { + switch(id) { + case "jump3-fast": + futureState.effect = id; + this._effect = new ColorJumpEffect([ + [255, 0, 0], + [0, 255, 0], + [0, 0, 255], + ], 200, this._sendCommand.bind(this)); + + return this._effect.init().then(() => { + return this._stepEffect(); + }); + case "jump3-slow": + futureState.effect = id; + this._effect = new ColorJumpEffect([ + [255, 0, 0], + [0, 255, 0], + [0, 0, 255], + ], 800, this._sendCommand.bind(this)); + + return this._effect.init().then(() => { + return this._stepEffect(); + }); + case "scheme-2-slow": + futureState.effect = id; + this._effect = new SchemeEffect(2, 30, this._sendCommand.bind(this)); + return this._effect.init(); + case "scheme-3-slow": + futureState.effect = id; + this._effect = new SchemeEffect(3, 30, this._sendCommand.bind(this)); + return this._effect.init(); + case "scheme-4-slow": + futureState.effect = id; + this._effect = new SchemeEffect(4, 30, this._sendCommand.bind(this)); + return this._effect.init(); + case "scheme-2-fast": + futureState.effect = id; + this._effect = new SchemeEffect(2, 1, this._sendCommand.bind(this)); + return this._effect.init(); + case "scheme-3-fast": + futureState.effect = id; + this._effect = new SchemeEffect(3, 1, this._sendCommand.bind(this)); + return this._effect.init(); + case "scheme-4-fast": + futureState.effect = id; + this._effect = new SchemeEffect(4, 1, this._sendCommand.bind(this)); + return this._effect.init(); + default: + return Promise.reject(new Error("Invalid effect id")); + } + }); + } + + let suid = this._sumanager.registerUpdate(futureState); + + return promise.then(resolveHelper(this, suid), rejectHelper(this, suid)); } pullState() { @@ -82,8 +259,16 @@ class HCTasmota extends HCColorLamp { let { hsl, brightness } = parseHsbString(res.data.HSBColor); + // only update color info if we're not currently changing it constantly with an effect + if (currentState.effect == "none") { + futureState.color = hsl; + + if (res.data.Scheme != 0) { + this.sendCommand("Scheme 0"); + } + } + futureState.on = res.data.POWER == "ON"; - futureState.color = hsl; futureState.brightness = brightness; if (currentState.hash != futureState.hash) { @@ -95,7 +280,17 @@ class HCTasmota extends HCColorLamp { throw new Error(`Got HTTP ${res.status}: ${res.data}`); } }); - } + } + + _stepEffect() { + if (this._effect != null) { + return this._effect.step().then(() => { + this._effectInterval = setTimeout(() => this._stepEffect(), this._effect.delay); + }); + } { + return Promise.resolve(); + } + } } module.exports = HCTasmota; diff --git a/package.json b/package.json index 86b043f..89301ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hc-tasmota", - "version": "1.0.1", + "version": "1.1.0", "description": "A plugin to support the communication with Tasmota devices via http", "main": "HCTasmota.js", "scripts": {