能否通过WebSocket在指定时间触发树莓派GPIO引脚?Node.js求助
解决树莓派Node.js闹钟触发GPIO的问题
看起来你现在的代码逻辑有点错位——前端的socket事件发送时机不对,后端也没处理定时触发GPIO的核心逻辑,而是一收到事件就直接改GPIO,这肯定没法实现闹钟到点触发的效果。我来帮你一步步修正:
先梳理核心流程
我们要实现的是:
- 前端选好闹钟时间,点击「Set Alarm」把时间发给后端
- 后端计算当前时间到闹钟时间的延迟,设置定时器
- 时间到后,后端触发GPIO输出高电平,同时通知前端触发闹钟(播放声音、显示控制按钮)
- 前端点击「Stop Alarm」或「Snooze」时,通知后端关闭GPIO,snooze的话还要重新设置定时器
第一步:修复前端代码
首先,你需要在HTML里引入socket.io的客户端脚本(后端的socket.io会自动提供这个文件,不用额外下载),然后修正socket事件的发送逻辑:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Alarm Clock</title> <!-- 引入socket.io客户端 --> <script src="/socket.io/socket.io.js"></script> </head> <body> <input type="datetime-local" id="alarmTime" /> <button onclick="setAlarm(this);" id="alarmButton">Set Alarm</button> <div id="alarmOptions" style="display: none;"> <button onclick="snooze();">Snooze 5 minutes</button> <button onclick="stopAlarm();">Stop Alarm</button> </div> <script> var alarmSound = new Audio('alarm.mp3'); var alarmTimer; // 初始化socket连接 var socket = io(); // 监听后端发来的闹钟触发通知 socket.on('alarmTriggered', function() { initAlarm(); }); function setAlarm(button) { var ms = document.getElementById('alarmTime').valueAsNumber; if(isNaN(ms)) { alert('Invalid Date'); return; } var alarmTime = new Date(ms); var differenceInMs = alarmTime.getTime() - (new Date()).getTime(); if(differenceInMs < 0) { alert('Specified time is already passed'); return; } // 把闹钟时间戳发给后端 socket.emit('setAlarm', ms); // 更新按钮状态 button.innerText = 'Cancel Alarm'; button.setAttribute('onclick', 'cancelAlarm(this);'); }; function cancelAlarm(button) { // 通知后端取消闹钟 socket.emit('cancelAlarm'); // 重置前端状态 button.innerText = 'Set Alarm'; button.setAttribute('onclick', 'setAlarm(this);'); }; function initAlarm() { alarmSound.play(); document.getElementById('alarmOptions').style.display = ''; console.log("ALAAAARM"); }; function stopAlarm() { alarmSound.pause(); alarmSound.currentTime = 0; document.getElementById('alarmOptions').style.display = 'none'; // 通知后端关闭GPIO并取消闹钟 socket.emit('stopAlarm'); cancelAlarm(document.getElementById('alarmButton')); }; function snooze() { // 计算5分钟后的时间戳 var snoozeTime = new Date().getTime() + 5 * 60 * 1000; // 发给后端设置新的闹钟 socket.emit('setAlarm', snoozeTime); // 停止当前声音,隐藏控制按钮 alarmSound.pause(); alarmSound.currentTime = 0; document.getElementById('alarmOptions').style.display = 'none'; // 更新按钮状态 document.getElementById('alarmButton').innerText = 'Cancel Alarm'; document.getElementById('alarmButton').setAttribute('onclick', 'cancelAlarm(this);'); }; </script> </body> </html>
第二步:修复后端代码
后端需要维护一个定时器,处理闹钟的定时触发,同时监听前端的各种事件来控制GPIO和定时器:
var http = require('http').createServer(handler); var fs = require('fs'); var io = require('socket.io')(http); var Gpio = require('onoff').Gpio; var LED = new Gpio(4, 'out'); // 假设你控制的是GPIO4 var alarmTimer = null; // 用于存储闹钟定时器 http.listen(8080, () => { console.log('Server running on http://raspberrypi.local:8080'); }); function handler(req, res) { fs.readFile(__dirname + '/test.html', function(err, data) { if (err) { res.writeHead(404, {'Content-Type': 'text/html'}); return res.end("404 Not Found"); } res.writeHead(200, {'Content-Type': 'text/html'}); res.write(data); return res.end(); }); } io.sockets.on('connection', function(socket) { console.log('A client connected'); // 监听前端设置闹钟的事件 socket.on('setAlarm', function(alarmTimestamp) { // 如果已有定时器,先取消 if (alarmTimer) { clearTimeout(alarmTimer); } var now = new Date().getTime(); var delay = alarmTimestamp - now; if (delay > 0) { alarmTimer = setTimeout(() => { // 时间到,触发GPIO为高电平 LED.writeSync(1); // 通知前端触发闹钟 socket.emit('alarmTriggered'); }, delay); console.log(`Alarm set for ${new Date(alarmTimestamp)}`); } }); // 监听取消闹钟事件 socket.on('cancelAlarm', function() { if (alarmTimer) { clearTimeout(alarmTimer); alarmTimer = null; // 确保GPIO处于低电平 LED.writeSync(0); console.log('Alarm cancelled'); } }); // 监听停止闹钟事件 socket.on('stopAlarm', function() { if (alarmTimer) { clearTimeout(alarmTimer); alarmTimer = null; } // 关闭GPIO LED.writeSync(0); console.log('Alarm stopped'); }); // 客户端断开连接时清理资源 socket.on('disconnect', function() { if (alarmTimer) { clearTimeout(alarmTimer); alarmTimer = null; } LED.writeSync(0); console.log('Client disconnected'); }); }); // 程序退出时清理GPIO process.on('SIGINT', function() { LED.writeSync(0); LED.unexport(); if (alarmTimer) { clearTimeout(alarmTimer); } process.exit(); });
关键修正点说明
前端socket逻辑:
- 引入了socket.io客户端脚本,确保能和后端建立连接
- 设置闹钟时把时间戳发给后端,由后端处理定时逻辑(后端的时间更可靠,避免前端页面关闭或刷新导致定时器失效)
- 监听后端的
alarmTriggered事件,触发前端的闹钟音效和UI变化 - 停止/ snooze时通知后端控制GPIO并管理定时器
后端逻辑:
- 新增了
alarmTimer变量来维护闹钟定时器,确保同一时间只有一个闹钟在运行 - 收到
setAlarm事件后计算延迟,设置定时器,到点后触发GPIO并通知前端 - 处理取消、停止闹钟的事件,清理定时器并重置GPIO状态
- 客户端断开连接时自动清理资源,避免GPIO一直处于高电平
- 新增了
测试步骤
- 确保你已经安装了所需依赖:
npm install onoff socket.io - 把前端代码保存为
test.html,和后端代码放在同一目录 - 运行后端:
node your-server-file.js - 在浏览器访问树莓派的IP:8080,设置闹钟时间,等待触发即可
内容的提问来源于stack exchange,提问作者Onjieee
相关产品推荐
相关产品推荐

