import ws281x from 'rpi-ws281x'; import * as fs from 'fs'; import Logger, { levels } from './logger.js'; const cfg = JSON.parse(fs.readFileSync('./config.json')); const log = new Logger("lights", cfg.log_level ? levels[cfg.log_level] : levels.INFO); const fade_ticks = cfg.fade_ticks || 20; var pixels = new Uint32Array(cfg.leds); var pixel_cache = new Uint32Array(cfg.leds); var next_pattern = new Uint32Array(cfg.leds); const targets = {} var pattern = {} function rgb_to_int(r, g, b) { let rgb = r; rgb = (rgb << 8) + g; rgb = (rgb << 8) + b; return rgb; } function int_to_rgb(int) { var r = (int >> 16) & 0xFF; var g = (int >> 8) & 0xFF; var b = (int) & 0xFF; return { r: r, g: g, b: b }; } ws281x.configure({ leds: cfg.leds || 300, brightness: cfg.brightness || 200, gpio: cfg.gpio || 18, stripType: cfg.type || 'grb' }); export function set_pattern(pat) { log.debug("pattern set"); pattern = pat; } export const functions = { random: (index, args, prev) => { return Math.floor(Math.random() * 256) }, constant: (index, args, prev) => { return args; }, modulo: (index, args, prev) => { return prev % args; }, move: (index, args, prev) => { targets[args] = prev; return prev; }, swap: (index, args, prev) => { let temp = targets[args]; targets[args] = prev; return temp; }, add: (index, args, prev) => { return prev + args; }, subtract: (index, args, prev) => { return prev - args; } } function tick_pattern() { // do the parsing stuff here log.debug("TICKING PATTERN") if (Object.keys(pattern).length > 0) { for (let i = 0; i < cfg.leds; i++) { for (let command of pattern) { if (command.valid) { let instruction = command.instruction; let name = instruction.name.toLowerCase(); if (functions[name]) { for (let target of instruction.args) { let channel = target.channel; if (!targets[channel]) { targets[channel] = 0; } log.debug("targets " + name + " " + channel + " " + target.arg); targets[channel] = functions[name](i, target.arg, targets[channel]); } } } } log.debug(`next: ${targets["r"]}, ${targets["g"]}, ${targets["b"]}`); next_pattern[i] = rgb_to_int(targets["r"] || 0, targets["g"] || 0, targets["b"] || 0); targets["r"] = 0; targets["g"] = 0; targets["b"] = 0; } } else { getRandom() } } function getRandom() { for (let i = 0; i < cfg.leds; i++) { var r = Math.floor(Math.random() * 256) var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); next_pattern[i] = rgb_to_int(r, g, b); } } export function tick() { var changed = false; for (let i = 0; i < cfg.leds; i++) { if (next_pattern[i] != pixels[i]) { if (next_pattern[i] == pixel_cache[i]) { log.debug("INCONGRUENCE WITH " + i); pixels[i] = next_pattern[i]; } else { changed = true; fade(i); } } else if (pixel_cache[i] != pixels[i]) { log.debug("PATTERN NOT STORED " + i); pixel_cache[i] = pixels[i]; } } if (!changed) { tick_pattern(); } else { ws281x.render(pixels); } //ws281x.sleep(cfg.sleep_time || 500); } function fade(index) { var original = int_to_rgb(pixel_cache[index]); var current = int_to_rgb(pixels[index]); var final = int_to_rgb(next_pattern[index]); var diff_r = final.r - original.r; var diff_cr = current.r - original.r; var diff_g = final.g - original.g; var diff_cg = current.g - original.g; var diff_b = final.b - original.b; var diff_cb = current.b - original.b; var sign_r = diff_r === Math.abs(diff_r) ? 1 : -1; var sign_g = diff_g === Math.abs(diff_g) ? 1 : -1; var sign_b = diff_b === Math.abs(diff_b) ? 1 : -1; log.debug(`${diff_r} ${sign_r} ${diff_g} ${sign_g} ${diff_b} ${sign_b}`); var interval_r = sign_r * Math.ceil(Math.abs(diff_r) / fade_ticks); var interval_g = sign_g * Math.ceil(Math.abs(diff_g) / fade_ticks); var interval_b = sign_b * Math.ceil(Math.abs(diff_b) / fade_ticks); log.debug(`r${Math.abs(diff_r) / fade_ticks} ${Math.ceil(Math.abs(diff_r) / fade_ticks)}` + `g${Math.abs(diff_g) / fade_ticks} ${Math.ceil(Math.abs(diff_g) / fade_ticks)}` + `b${Math.abs(diff_b) / fade_ticks} ${Math.ceil(Math.abs(diff_b) / fade_ticks)}`); var current_tick_r = Math.floor(Math.abs(diff_cr / diff_r) * fade_ticks); var current_tick_g = Math.floor(Math.abs(diff_cg / diff_g) * fade_ticks); var current_tick_b = Math.floor(Math.abs(diff_cb / diff_b) * fade_ticks); if (diff_r == 0 || (sign_r == 1 && current.r + interval_r >= final.r) || (sign_r == -1 && current.r + interval_r <= final.r) || current_tick_r + 1 >= fade_ticks) { current.r = final.r; interval_r = 0; current_tick_r = fade_ticks; } if (diff_g == 0 || (sign_g == 1 && current.g + interval_g >= final.g) || (sign_g == -1 && current.g + interval_g <= final.g) || current_tick_g + 1 >= fade_ticks) { current.g = final.g; interval_g = 0; current_tick_g = fade_ticks; } if (diff_b == 0 || (sign_b == 1 && current.b + interval_b >= final.b) || (sign_b == -1 && current.b + interval_b <= final.b) || current_tick_b + 1 >= fade_ticks) { current.b = final.b; interval_b = 0; current_tick_b = fade_ticks; } if (current_tick_r + 1 >= fade_ticks && current_tick_g + 1 >= fade_ticks && current_tick_b + 1 >= fade_ticks) { log.debug("FINISHED"); pixel_cache[index] = next_pattern[index]; } else { pixels[index] = rgb_to_int(current.r + interval_r, current.g + interval_g, current.b + interval_b); let prev = int_to_rgb(pixel_cache[index]); log.debug(`${current_tick_r} ${current_tick_g} ${current_tick_b}: ` + `CURRENT COLOR: ${current.r} ${current.g} ${current.b} NEW COLOR: ${current.r + interval_r} ${current.g + interval_g} ${current.b + interval_b} ` + `FINAL: ${final.r} ${final.g} ${final.b} ` + `\nINTERVAL: ${interval_r} ${interval_g} ${interval_b} ` + `PREVIOUS: ${prev.r} ${prev.g} ${prev.b}`); } }