Jolt.setDriverMode(0, "auto"); Jolt.setDriverMode(1, "auto"); function jolterInit() { let ready = false; let unlocked = true; let activeTimer; let advTimer; const pumps = { 0: H0, 1: H1, 2: H2, 3: H3, 4: H4, 5: H5, 6: H6, 7: H7, }; const nextDay = 86400000; const advConfig = { name: "Jolter", manufacturer: false, connectable: unlocked, scannable: unlocked, interval: 100, }; const bleConfig = { uart: unlocked, }; const voltage = { type: "voltage", v: 0, }; const powerOn = { type: "power_on", v: false, }; const timers = []; NRF.setTxPower(4); unlockUart(unlocked); setWatch( function btnWatcher() { unlocked = !unlocked; unlockUart(unlocked); bleActivate(); }, BTN, { edge: "rising", debounce: 50, repeat: true } ); function setPowerAdv(power) { voltage.v = E.getAnalogVRef(); powerOn.v = power; if (advTimer) clearTimeout(advTimer); advTimer = setTimeout(closeAdv, 10000); bleActivate(); NRF.setAdvertising( require("BTHome").getAdvertisement([voltage, powerOn]), advConfig ); } function closeAdv() { advTimer = undefined; bleActivate(); } function bleActivate() { if (unlocked || advTimer) { NRF.wake(); } else { NRF.sleep(); } setLEDStatus(); } function updateTimerToNext(config) { config.timer.setTime(config.timer.getTime() + nextDay * config.every); } function calcDelay(alarm) { let now = Date.now(); return alarm.getTime() - now; } function schedulerResolver(resolve, val) { activeTimer = undefined; resolve(val); } function schedule(delay, val) { return new Promise((resolve) => { activeTimer = setTimeout(schedulerResolver, delay, resolve, val); }); } function scheduleOn(date, val) { return schedule(calcDelay(date), val); } function unlockUart(isUnlocked) { bleConfig.uart = advConfig.connectable = advConfig.scannable = isUnlocked; NRF.setServices(undefined, bleConfig); } function sortTime(a, b) { return a.timer.getTime() - b.timer.getTime() || a.pump - b.pump; } function scheduleTimers() { setPowerAdv(false); let when; let next_timers = timers.sort(sortTime).filter((t) => { if (!when) { when = t.timer; return true; } else { return when === t.timer; } }); return when ? scheduleOn(when, next_timers).then(executeTimers) : Promise.resolve(); } function executeTimers(next) { setPowerAdv(true); return next.reduce(loadSequence, Promise.resolve()).then(scheduleTimers); } function loadSequence(sequence, timer) { return sequence .then(startSequence.bind(undefined, timer)) .then(finishSequence); } function startSequence(config) { pumps[config.pump].set(); return schedule(config.duration, config); } function finishSequence(config) { pumps[config.pump].read(); updateTimerToNext(config); } function setLEDStatus() { if (unlocked) { LED1.write(1); LED2.write(0); } else { LED1.write(0); LED2.write(!!advTimer); } } function configureTimers(config) { if (config.pump < 0 || config.pump > 7) { throw new Error("Invalid pump config: " + config.pump); } let now = new Date(); let time = new Date(); time.setHours(config.hour, config.minute, 0, 0); config.timer = time; config.duration = config.duration * 1000; if (config.timer < now) { updateTimerToNext(config); } return config; } return { clear: function () { if (activeTimer) clearTimeout(activeTimer); ready = false; timers.length = 0; return this; }, load: function (timer) { timers.push(configureTimers(timer)); return this; }, loadTimers: function (loaded) { for (let i = 0; i < loaded.length; i += 1) { this.load(loaded[i]); } return this; }, loadJSON: function (objString) { let obj = JSON.parse(objString); if (obj.timers) { this.loadTimers(obj.timers); } if (obj.datetime) { this.setTime(obj.datetime); } if (obj.tz) { this.setTz(tz); } return this; }, setTime: function (datetime) { if (activeTimer) clearTimeout(activeTimer); if (typeof datetime === "number") setTime(new Date(datetime)); return this; }, setTz: function (tz) { if (activeTimer) clearTimeout(activeTimer); if (typeof tz === "number" && E.setTimeZone) E.setTimeZone(tz); return this; }, ready: function () { ready = true; return this; }, start: function () { if (ready) { scheduleTimers(); } else { throw new Error("Jolter is not properly configured and can't run"); } }, }; } var Jolter = jolterInit(); Jolter.clear() .loadTimers([ { pump: 0, hour: 17, minute: 46, duration: 15, every: 1 }, { pump: 0, hour: 17, minute: 47, duration: 15, every: 2 }, ]) .ready() .start();