const Gpio = require("pigpio").Gpio; const schedule = require("./schedule.json"); const HZ = 40000; const NAP_DURATION = 100 * 60 * 1000; const DEBUG = !!process.env["DEBUG"]; //Let's setup our connections to our physical hardware devices. let ledRed = new Gpio(12, {mode: Gpio.OUTPUT}); ledRed.hardwarePwmWrite(HZ, 0); let ledGreen = new Gpio(13, {mode: Gpio.OUTPUT}); let napButton = new Gpio(6, {mode: Gpio.INPUT, pullUpDown: Gpio.PUD_UP, alert: true }); napButton.glitchFilter(10000); //Make an easily searchable array so we can find what color it should //be using at any given time. let timings = schedule.times.reduce((result, time) => { result.push({start: time.time, color: time.color}); return result; }, []).sort((a, b) => a.start - b.start).map((timing, i, arr) => { timing.end = arr[(i+1) % arr.length].start; return timing; }); //We need to split the last scheduled interval at midnight //(and fill it in for the start of the day) splitAtMidnight(timings); //Initialize State let state = { color: "red", napTime: false, } //Make sure the color is set correctly in state for the current time before we start. //It will automatically update every seconds, but it might be wrong for a second. updateColorState(); //Set intervals to update LED lights. //******** let updateColorStateInterval = setInterval(updateColorState, 1000); let renderInterval = setInterval(render, 50); let debugInterval = DEBUG && setInterval(logLEDStatus, 2000); //******** napButton.on("alert", startNap); DEBUG && process.on('SIGUSR2', () => { console.debug("Simulating button press!"); startNap(); }); function startNap(level, tick) { console.log("Button alert recieved. Starting nap time!"); state = { ...state, napTime: true, napEnd: Date.now() + NAP_DURATION } } function updateColorState() { let newColor = state.color; switch(state.napTime) { case true: console.log("Nap time until :", state.napEnd); if(Date.now() < state.napEnd) { newColor = "red"; break; } else { state = { ...state, napTime: false, napEnd: 0, }; } case false: console.log("NO NAP!"); let time = getTime(); let timing = timings.find(x => x.start <= time && x.end > time); newColor = timing.color } if(newColor !== state.color) { console.log("setting color state to", newcolor); state = { ...state, color: newColor, }; } } function render() { switch(state.color) { case "red": if(ledGreen.digitalRead()) { DEBUG && console.log("Turning off Green LED."); return ledGreen.digitalWrite(0); } DEBUG && !ledRed.getPwmDutyCycle() && console.log("Turning on Red LED"); return ledRed.hardwarePwmWrite(HZ, getBrightness(Date.now())); break; case "green": if(ledRed.getPwmDutyCycle()) { DEBUG && console.log("Turning off Red LED."); return ledRed.hardwarePwmWrite(0); } if(!ledGreen.digitalRead()) { DEBUG && console.log("Turning on Green LED."); return ledGreen.digitalWrite(1); } //Commenting out. Probably don't need breathing on green leds //return ledGreen.hardwarePwmWrite(HZ, getBrightness(Date.now())); break; } } function logLEDStatus() { console.log({ red: ledRed.getPwmDutyCycle(), green: ledGreen.digitalRead(), }); } //get brightness for time value for LED pulse breathing function getBrightness(time) { //These values should be configurable, maybe? return Math.floor(breathingCurve(time, 6500, 200000, 800000)); } //algorithm for pulse breathing LEDs function breathingCurve(x, interval = 3000, min, max) { let t = x * Math.PI/(interval/2); let y = Math.sin(t + Math.sin(t) * 0.2); let untranslatedY = Math.sin(t + Math.sin(t) * 0.25); y = (untranslatedY + 1)/2; return y * (max - min) + min; } //split the last time interval at midnight and add the remaining to the //beginning of the the array. function splitAtMidnight(arr) { let last = arr[arr.length -1]; arr.unshift({ ...last, start: 0}); arr[arr.length - 1].end = 2400; } //get current time in military style time. This format is used for ease of //human input in conviguration file. I should have converted to Date when read //from configuration.. oh well, I'll fix that later. function getTime() { let date = new Date(Date.now()); return (date.getHours() * 100) + date.getMinutes() + 1 }