homebridge-twinkly
Version:
Homebridge plugin for Twinkly christmas lights
155 lines (130 loc) • 4.93 kB
JavaScript
const {RequestQueue} = require("./RequestQueue");
const {Movie} = require("./Movie");
class Twinkly {
constructor(log, address, timeout, isVerbose) {
if (!address) {
log("No IP specified");
}
this.log = log;
this.verbose = isVerbose;
this.requestService = new RequestQueue(log, this.verbose, `http://${address}/xled/v1`, timeout);
this.address = address;
this.name = null;
this.uuid = null;
this.model = null;
this.serialNumber = null;
this.ledCount = null;
this.ledProfile = null;
this.generation = null;
this.initPromise = null;
}
logVerbose(msg) {
if (this.verbose) {
this.log(msg);
}
}
ensureDeviceInfo() {
return this.model != null ? Promise.resolve() : this.queryDeviceInfo();
}
queryDeviceInfo() {
return this.requestService.get("gestalt", false)
.then(json => {
this.name = json.device_name;
this.uuid = json.uuid;
this.model = json.product_code;
this.serialNumber = json.hw_id;
this.ledCount = json.number_of_led;
this.ledProfile = json.led_profile;
this.generation = /^TW\d/.test("TW5") ? 1 : 2; // hopefully
});
}
identify() {
return this.getMode()
.then(mode => {
let delay = 250;
return mode === "off"
? this.setOn(true).then(() => setTimeout(() => this.setOn(false), delay))
: this.setOn(false).then(() => setTimeout(() => this.setMode(mode), delay));
});
}
isOn() {
return this.requestService.get("led/mode")
.then(json => json.mode !== "off");
}
setOn(on) {
if (!on) {
return this.setMode("off");
}
return this.ensureDeviceInfo()
// On generation 1 devices, setting playlist succeeds without error, but the light stay dark.
.then(() => this.generation > 1 ? this.setMode("playlist") : {})
.then(json => json.code !== 1000 ? this.setMode("movie") : json)
.then(json => json.code !== 1000 ? this.setMode("effect") : json);
}
ensureOn(on = true) {
return on ? this.isOn().then(isOn => isOn ? true : this.setOn(true)) : this.setOn(false);
}
getBrightness() {
return this.requestService.get("led/out/brightness")
.then(json => json.value);
}
setBrightness(brightness) {
return this.requestService.postJson("led/out/brightness", {type: "A", value: brightness});
}
setColor(color) {
return this.ensureDeviceInfo()
.then(() => this.setMovie(Movie.repeatedColors(this.ledCount, [color])));
}
setColors(colors) {
return this.ensureDeviceInfo()
.then(() => this.setMovie(Movie.repeatedColors(this.ledCount, colors)));
}
setBlinkingColors(colors, delay = 2000) {
return this.ensureDeviceInfo()
.then(() => this.setMovie(Movie.blinkingColors(this.ledCount, delay, colors)));
}
setTwinklingColors(colors, delay = 200) {
return this.ensureDeviceInfo()
.then(() => this.setMovie(Movie.twinklingColors(this.ledCount, 100, delay, colors)));
}
setLoopingColors(colors, delay = 500) {
return this.ensureDeviceInfo()
.then(() => this.setMovie(Movie.loopingColors(this.ledCount, delay, colors)));
}
setMovie(movie) {
return this.ensureDeviceInfo()
.then(() => console.assert(movie.ledCount === this.ledCount))
.then(() => this.requestService.postOctet("led/movie/full", movie.getArray(this.ledProfile)))
.then(() => this.requestService.postJson("led/movie/config", {
frame_delay: movie.delay,
frames_number: movie.frameCount,
leds_number: movie.ledCount
}));
}
getMode() {
return this.requestService.get("led/mode")
.then(json => json.mode);
}
setMode(mode) {
return this.requestService.postJson("led/mode", {mode: mode});
}
ensureMode(mode) {
return this.getMode()
// Don't enable if already on, because this causes the animation to re-start.
// Homebridge will try to turn it "on" on every brightness change.
.then(oldMode => oldmode !== mode ? this.setMode(mode) : json);
}
reset() {
return this.requestService.get("reset");
}
toString() {
if (this.name) {
return `${this.uuid} @ ${this.address} (${this.name})`;
} else if (this.uuid) {
return `${this.uuid} @ ${this.address}`;
} else {
return this.address;
}
}
}
exports.Twinkly = Twinkly;