Linux环境下Johnny-Five前端控制框架
在Linux环境下构建一个前端控制框架来操作Johnny-Five硬件,可以通过多种方式实现。下面我将提供一个完整的解决方案,使用Web技术栈创建一个直观的硬件控制界面。
方案概述
我将使用以下技术栈:
- 后端: Node.js + Express + Johnny-Five
- 前端: HTML5 + CSS3 + JavaScript (Vanilla JS,但可轻松适配React/Vue)
- 通信: WebSocket (Socket.IO) 实现实时双向通信
- UI: 响应式设计,兼容桌面和移动设备
项目结构
1
2
3
4
5
6
7
8
9
10
| johnny-five-web-control/
├── server.js # Node.js后端服务器
├── package.json # 项目依赖
├── public/ # 静态文件
│ ├── index.html # 主页面
│ ├── css/
│ │ └── style.css # 样式文件
│ └── js/
│ └── app.js # 前端逻辑
└── README.md
|
实现步骤
1. 环境准备
1
2
3
4
5
6
7
8
9
| # 创建项目目录
mkdir johnny-five-web-control
cd johnny-five-web-control
# 初始化npm项目
npm init -y
# 安装依赖
npm install express socket.io johnny-five serialport
|
2. 后端服务器 (server.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const five = require('johnny-five');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// 提供静态文件
app.use(express.static('public'));
// Arduino连接
let board = new five.Board({
port: '/dev/ttyACM0', // 根据实际情况修改
repl: false
});
let led = null;
let servo = null;
board.on('ready', () => {
console.log('Arduino连接成功!');
// 初始化组件
led = new five.Led(13);
servo = new five.Servo(9);
// 监听WebSocket连接
io.on('connection', (socket) => {
console.log('客户端连接');
// 处理LED控制
socket.on('led-control', (data) => {
if (data.state === 'on') {
led.on();
} else if (data.state === 'off') {
led.off();
} else if (data.state === 'blink') {
led.blink(500);
}
io.emit('led-status', {state: data.state});
});
// 处理舵机控制
socket.on('servo-control', (data) => {
servo.to(data.angle);
io.emit('servo-status', {angle: data.angle});
});
// 断开连接
socket.on('disconnect', () => {
console.log('客户端断开连接');
});
});
});
board.on('error', (err) => {
console.log('Arduino连接错误:', err);
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
|
3. 前端界面 (public/index.html)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| <!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Johnny-Five硬件控制面板</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>硬件控制面板</h1>
<p id="connection-status">连接状态: 等待连接...</p>
</header>
<div class="control-section">
<h2>LED控制</h2>
<div class="button-group">
<button id="led-on" class="btn btn-success">打开LED</button>
<button id="led-off" class="btn btn-danger">关闭LED</button>
<button id="led-blink" class="btn btn-warning">闪烁LED</button>
</div>
<div class="status">
<p>LED状态: <span id="led-status">未知</span></p>
</div>
</div>
<div class="control-section">
<h2>舵机控制</h2>
<div class="slider-container">
<input type="range" id="servo-slider" min="0" max="180" value="90" class="slider">
<span id="servo-value">90°</span>
</div>
<div class="status">
<p>舵机角度: <span id="servo-status">90°</span></p>
</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="js/app.js"></script>
</body>
</html>
|
4. 样式设计 (public/css/style.css)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
| * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f4f4f4;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
header {
text-align: center;
margin-bottom: 30px;
}
.control-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e1e1e1;
border-radius: 5px;
}
.button-group {
display: flex;
gap: 10px;
margin: 15px 0;
flex-wrap: wrap;
}
.btn {
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-warning {
background-color: #ffc107;
color: black;
}
.btn:hover {
opacity: 0.9;
}
.slider-container {
display: flex;
align-items: center;
gap: 15px;
margin: 15px 0;
}
.slider {
flex: 1;
height: 10px;
}
.status {
margin-top: 15px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
}
#connection-status {
font-weight: bold;
color: #6c757d;
}
.connected {
color: #28a745 !important;
}
.disconnected {
color: #dc3545 !important;
}
@media (max-width: 600px) {
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
}
|
5. 前端逻辑 (public/js/app.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| // 建立Socket连接
const socket = io();
// 连接状态指示
const connectionStatus = document.getElementById('connection-status');
// 更新连接状态
socket.on('connect', () => {
console.log('已连接到服务器');
connectionStatus.textContent = '连接状态: 已连接';
connectionStatus.classList.add('connected');
connectionStatus.classList.remove('disconnected');
});
socket.on('disconnect', () => {
console.log('与服务器断开连接');
connectionStatus.textContent = '连接状态: 断开连接';
connectionStatus.classList.remove('connected');
connectionStatus.classList.add('disconnected');
});
// LED控制
const ledOnBtn = document.getElementById('led-on');
const ledOffBtn = document.getElementById('led-off');
const ledBlinkBtn = document.getElementById('led-blink');
const ledStatus = document.getElementById('led-status');
ledOnBtn.addEventListener('click', () => {
socket.emit('led-control', {state: 'on'});
});
ledOffBtn.addEventListener('click', () => {
socket.emit('led-control', {state: 'off'});
});
ledBlinkBtn.addEventListener('click', () => {
socket.emit('led-control', {state: 'blink'});
});
// 更新LED状态
socket.on('led-status', (data) => {
ledStatus.textContent = data.state === 'on' ? '开启' :
data.state === 'off' ? '关闭' : '闪烁';
});
// 舵机控制
const servoSlider = document.getElementById('servo-slider');
const servoValue = document.getElementById('servo-value');
const servoStatus = document.getElementById('servo-status');
servoSlider.addEventListener('input', () => {
const angle = servoSlider.value;
servoValue.textContent = `${angle}°`;
socket.emit('servo-control', {angle: parseInt(angle)});
});
// 更新舵机状态
socket.on('servo-status', (data) => {
servoStatus.textContent = `${data.angle}°`;
servoSlider.value = data.angle;
servoValue.textContent = `${data.angle}°`;
});
// 初始化页面
document.addEventListener('DOMContentLoaded', () => {
console.log('控制面板已加载');
});
|
使用说明
- 硬件准备:
- 将Arduino连接到Linux计算机
- 确保已上传StandardFirmata固件
- 确认Arduino设备路径(通常是
/dev/ttyACM0
或/dev/ttyUSB0
)
- 启动应用:
1
2
3
4
5
| # 确保已安装所有依赖
npm install
# 启动服务器
node server.js
|
- 访问控制界面:
- 打开浏览器访问
http://localhost:3000
- 确保浏览器和设备在同一网络下(如果是远程访问)
- 控制硬件:
- 使用按钮控制LED的开关和闪烁
- 使用滑块控制舵机角度
扩展功能
这个基础框架可以轻松扩展以支持更多硬件组件:
- 添加传感器数据显示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 后端
const temperature = new five.Thermometer({
controller: "LM35",
pin: "A0"
});
temperature.on("change", () => {
io.emit('temperature-data', {celsius: temperature.celsius});
});
// 前端
socket.on('temperature-data', (data) => {
document.getElementById('temperature').textContent = `${data.celsius}°C`;
});
|
- 添加更多执行器控制:
- 添加用户认证:
- 数据记录:
故障排除
- 串口权限问题:
1
2
| sudo usermod -a -G dialout $USER
# 然后重新登录
|
- 端口占用:
- Arduino未识别:
这个框架提供了一个完整的起点,您可以根据具体需求进行定制和扩展。它结合了现代Web技术和硬件控制,创建了一个直观的用户界面来控制Johnny-Five硬件。