Last active
August 7, 2025 09:08
-
-
Save joshtwist/7a13ad0d5967a351de303c984770ac71 to your computer and use it in GitHub Desktop.
puckJS toilet seat code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| let monitoring = false; | |
| let seatDownAccel = null; | |
| let flashInterval = null; | |
| let movedSince = null; | |
| let flashTimerTimeout = null; | |
| let accelHandler = null; | |
| // === Configuration Constants === | |
| const ANGLE_THRESHOLD = 25; // degrees | |
| const SEAT_UP_WARNING_DELAY_MS = 15000; | |
| const ALERT_PHASE_1_END = 60; | |
| const ALERT_PHASE_2_END = 3600; | |
| function angleBetween(v1, v2) { | |
| const dot = v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; | |
| const mag1 = Math.sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); | |
| const mag2 = Math.sqrt(v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); | |
| const cosTheta = dot / (mag1 * mag2); | |
| return Math.acos(Math.min(Math.max(cosTheta, -1), 1)) * (180 / Math.PI); | |
| } | |
| function flashLED(led, onTime, offTime) { | |
| if (flashInterval) clearInterval(flashInterval); | |
| LED1.write(0); LED2.write(0); LED3.write(0); | |
| let state = false; | |
| flashInterval = setInterval(() => { | |
| state = !state; | |
| led.write(state); | |
| }, onTime + offTime); | |
| } | |
| function stopFlashing() { | |
| if (flashInterval) clearInterval(flashInterval); | |
| flashInterval = null; | |
| LED1.write(0); LED2.write(0); LED3.write(0); | |
| } | |
| function bluePulse() { | |
| LED3.write(1); | |
| setTimeout(() => LED3.write(0), 100); | |
| } | |
| function startFlashingTimer() { | |
| if (flashTimerTimeout) return; | |
| flashTimerTimeout = setTimeout(() => { | |
| const flashLogic = () => { | |
| const elapsed = (Date.now() - movedSince) / 1000; | |
| if (elapsed < ALERT_PHASE_1_END) { | |
| flashLED(LED1, 100, 400); | |
| } else if (elapsed < ALERT_PHASE_2_END) { | |
| flashLED(LED1, 100, 900); | |
| } else { | |
| flashLED(LED1, 100, 1900); | |
| } | |
| }; | |
| flashLogic(); | |
| flashInterval = setInterval(flashLogic, 10000); | |
| }, SEAT_UP_WARNING_DELAY_MS); | |
| } | |
| function startMonitoring() { | |
| seatDownAccel = Puck.accel().acc; | |
| console.log("Calibrated: seat is CLOSED"); | |
| movedSince = null; | |
| // Blink green 3x | |
| let flashes = 0; | |
| let flash = setInterval(() => { | |
| LED2.write(1); | |
| setTimeout(() => LED2.write(0), 100); | |
| flashes++; | |
| if (flashes === 3) clearInterval(flash); | |
| }, 300); | |
| accelHandler = (data) => { | |
| if (!data || !seatDownAccel) return; | |
| const angle = angleBetween(data, seatDownAccel); | |
| if (angle > ANGLE_THRESHOLD) { | |
| if (!movedSince) { | |
| movedSince = Date.now(); | |
| console.log("Seat is UP - movement detected (angle: " + angle.toFixed(1) + ")"); | |
| bluePulse(); | |
| startFlashingTimer(); | |
| } | |
| } else { | |
| if (movedSince) { | |
| console.log("Seat is back DOWN - resetting timer"); | |
| movedSince = null; | |
| stopFlashing(); | |
| if (flashTimerTimeout) clearTimeout(flashTimerTimeout); | |
| flashTimerTimeout = null; | |
| } | |
| } | |
| }; | |
| Puck.on('accel', accelHandler); // Just start the handler; sensor wakes automatically | |
| } | |
| function stopMonitoring() { | |
| console.log("Monitoring stopped"); | |
| monitoring = false; | |
| if (flashInterval) clearInterval(flashInterval); | |
| if (flashTimerTimeout) clearTimeout(flashTimerTimeout); | |
| flashInterval = null; | |
| flashTimerTimeout = null; | |
| LED1.write(0); LED2.write(0); LED3.write(0); | |
| seatDownAccel = null; | |
| movedSince = null; | |
| Puck.removeListener('accel', accelHandler); // stop receiving accel events | |
| accelHandler = null; | |
| } | |
| function ledDancePowerOn(callback) { | |
| let rounds = 3; | |
| let delay = 300; | |
| function runRound(round) { | |
| if (round > rounds) return callback && callback(); | |
| LED3.write(1); | |
| setTimeout(() => { | |
| LED3.write(0); | |
| delay *= 0.7; | |
| setTimeout(() => runRound(round + 1), delay); | |
| }, delay / 2); | |
| } | |
| runRound(1); | |
| } | |
| function ledDancePowerOff(callback) { | |
| let rounds = 3; | |
| let delay = 100; | |
| function runRound(round) { | |
| if (round > rounds) return callback && callback(); | |
| LED1.write(1); LED2.write(1); | |
| setTimeout(() => { | |
| LED1.write(0); LED2.write(0); | |
| delay *= 1.5; | |
| setTimeout(() => runRound(round + 1), delay); | |
| }, delay / 2); | |
| } | |
| runRound(1); | |
| } | |
| // Button toggle | |
| setWatch(() => { | |
| if (!monitoring) { | |
| monitoring = true; | |
| ledDancePowerOn(startMonitoring); | |
| } else { | |
| ledDancePowerOff(stopMonitoring); | |
| } | |
| }, BTN, { edge: "rising", debounce: 50, repeat: true }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment