PS:很早就听说BBB板,但是在我看来一直没有树莓派热度高,手上有树莓派,也从未有过BBB,借此机会接触一下BBB板。虽然是很古老的板子了,但是出于学习研究的目的,主打一个资料多,易于学习,还是值得一看。
1、项目需求
在系统中建立一个网页,并且与LED联动,使用网线连接到设备上时,可以从网页中控制LED开关。
2、完成的功能及达到的性能
2.1 制作快速对插测试板实现功能展示
- 洞洞板焊接了LED和轻触按键用于完成此项目。将P9_12接到了按钮;将红色和蓝色LED分别接入P9_14和P9_16。
2.2 实现远程控制LED灯
- 可以通过网页上的拨动开关按钮来控制两个LED灯的开关状态。
- 每个LED的开关按钮颜色与其物理颜色相匹配(红色和蓝色)。
2.3 实现物理按钮同步控制
- 读取物理按钮(P9_12引脚)可以同时控制两个LED灯的开关状态。
- 每次单击物理按钮时,会控制两个LED灯的状态统一到开/关切换(如果当前是开,则关上;如果当前是关,则打开)
2.4 实时状态反馈
- 网页会实时显示两个LED灯的当前状态(开或关)。
- 状态每秒更新一次,确保网页上的状态与物理状态同步。
2.5 能够自定义灯的名称
- 使用LED模拟实际的不同位置的灯,用户可以在网页上点击对应的灯名称并进行编辑。
- 编辑后的名称会自动保存,并在网页上显示。
2.6 主题网页
- 网页背景使用了一张主题图片,并对背景图片进行了模糊处理,以突出前景的文字和控件。刚好最近快要圣诞节,于是使用圣诞主题背景以及整体颜色搭配。
- 所有文字和控件都进行了适当的放大,以便更好地显示。
- 文字和控件添加了圣诞主题的发光特效,使页面更加生动。
3、实现思路
主要思路即实现三大部分即可:
- 硬件连接:LED灯和按钮连接到BeagleBone Black开发板的GPIO引脚。
- 后台业务:使用Flask框架创建一个Web应用,提供控制LED灯的接口和网页。
- 前端网页:创建一个友好的用户界面,允许用户通过网页控制LED灯并显示实时状态。
4、实现过程
4.1 研习官方示例——主要参考资料:BeagleBone Cookbook
在此跳过下载固件、烧录到卡、开机启动、连接USB数据线、浏览器进入192.168.7.2、进入VScode界面的过程。下图是在浏览器打开的vscode界面。首先我选择在一侧打开命令行终端,这样输入指令无需另外通过putty之类的软件了。实现在vscode编辑代码;再在vscode终端敲命令执行测试,非常合理。
(如果没有终端Terminal界面,可点击左上角的导航栏Terminal新增。)
(使用的账号:debian 密码:temppwd)
根据教程资料的内容对系列例程进行效果观察,快速上手获得乐趣,才会有更多动力继续研究。其中主要包含两种例程文件:JavaScript和python。那么好,我们选择python,点击即可运行。快速观察实验现象,对板子有更进一步了解。
4.2 硬件连接
1、电路设计部分
使用一小块洞洞板,找来LED,按钮,贴片电阻和导线。按照P9的引脚定义,快速实现对应的测试电路连接:红色LED接到P9_14引脚,蓝色LED接到P9_16引脚,按钮接到P9_12引脚,焊接排针测试无短路,插上板子使用例程里面的 03displays/externalLED.py 例程,稍加修改引脚即可测试LED是否正确点亮。
2、程序配置部分
配置GPIO引脚,对IO编号进行定义功能名称,button_pin
为按钮引脚,led1_pin
和led2_pin
分别为两个LED的引脚。
button_pin = "P9_12"
led1_pin = "P9_14"
led2_pin = "P9_16"
初始化GPIO引脚函数,GPIO.setup(pin, direction, pull_up_down):配置GPIO引脚方向和内部上拉电阻。button_pin设置为输入引脚并启用上拉电阻,led1_pin和led2_pin设置为输出引脚。
GPIO.setup(button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(led1_pin, GPIO.OUT)
GPIO.setup(led2_pin, GPIO.OUT)
初始化LED状态,将两个LED灯设为关闭状态
GPIO.output(led1_pin, GPIO.HIGH)
GPIO.output(led2_pin, GPIO.HIGH)
读取配置文件led_config.json初始化LED名称,如果存在,读取文件并加载LED名称,如果不存在,使用默认名称初始化,并创建配置文件保存名称。
config_file = 'led_config.json'
if os.path.exists(config_file):
with open(config_file, 'r') as file:
led_names = json.load(file)
else:
led_names = {"led1": "LED 1", "led2": "LED 2"}
with open(config_file, 'w') as file:
json.dump(led_names, file)
物理按钮状态初始化一次。
last_button_state = GPIO.input(button_pin)
3、程序业务部分
函数实现,0.1s轮询物理按钮状态并更新LED状态,如果检测到按钮从未按下变为按下状态,则切换LED状态(打开或关闭)
def button_polling():
global last_button_state
while True:
current_button_state = GPIO.input(button_pin)
if current_button_state == GPIO.LOW and last_button_state == GPIO.HIGH:
# 切换LED状态
led1_state = GPIO.input(led1_pin)
new_state = GPIO.LOW if led1_state == GPIO.HIGH else GPIO.HIGH
GPIO.output(led1_pin, new_state)
GPIO.output(led2_pin, new_state)
last_button_state = current_button_state
time.sleep(0.1)
启动按钮轮询线程用于轮询上述函数,新线程运行button_polling
函数。
threading.Thread(target=button_polling, daemon=True).start()
4.3 后台业务Flask了解与使用
1、简介(参考GitHub Copilot)
Flask是一个轻量级的Web框架,用于构建Web应用程序和API。它使用Python语言编写,设计上追求简单和易于扩展。
特点:
- 轻量级:Flask本身只包含最基本的功能,没有默认的数据库集成或表单处理工具,保持框架的轻量级特点。
- 易于扩展:通过Flask的扩展机制,可以选择并集成第三方工具和库,如SQLAlchemy(数据库)、WTForms(表单处理)、Flask-RESTful(API构建)等。
- 灵活性:开发者可以根据需求自由选择工具和库,避免了不必要的功能堆积。
- 简单易用:Flask的API设计简洁明了,适合初学者快速上手,同时也能满足高级用户的复杂需求。
2、软件实现部分
创建Flask应用实例:
Flask(__name__)
:创建Flask应用实例。__name__
参数用于确定应用根目录,方便定位资源文件。
app = Flask(__name__)
定义根路由/,返回渲染的模板index.html,并传递LED名称。
@app.route('/')
def index():
return render_template('index.html', led_names=led_names)
定义路由/led_state
,返回LED1和LED2的当前状态,以JSON格式返回。
@app.route('/led_state')
def led_state():
led1_state = GPIO.input(led1_pin)
led2_state = GPIO.input(led2_pin)
return jsonify(led1_state=led1_state, led2_state=led2_state)
定义路由/set_name,处理POST请求,更新LED名称并保存到配置文件。
@app.route('/set_name', methods=['POST'])
def set_name():
data = request.json
led_names[data['led']] = data['name']
with open(config_file, 'w') as file:
json.dump(led_names, file)
return '', 204
定义路由/led/<int:led_id>/<string:action>,处理POST请求,根据LED ID和操作(开或关)控制相应的LED
@app.route('/led/<int:led_id>/<string:action>', methods=['POST'])
def control_led(led_id, action):
pin = led1_pin if led_id == 1 else led2_pin
if action == "on":
GPIO.output(pin, GPIO.LOW)
elif action == "off":
GPIO.output(pin, GPIO.HIGH)
return '', 204
3、主函数运行
如果脚本被直接运行,则启动Flask开发服务器,监听所有IP地址(0.0.0.0)的5000端口。
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
4.4 前端html文件分解(参考GitHub Copilot)
1. HTML基础结构
<!DOCTYPE html>
<html>
<head>
<title>Light Control Center</title>
<!DOCTYPE html>
:声明文档类型,告知浏览器这是一个HTML5文档。<html>
:HTML文档的根元素。<head>
:包含元数据(如文档标题、样式表等)。<title>
:定义网页标题,显示在浏览器标签上。
2. 内联样式
<style>
body {
background-image: url('https://images.pexels.com/photos/1303098/pexels-photo-1303098.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940');
background-size: cover;
background-position: center;
font-family: Arial, sans-serif;
text-align: center;
color: rgb(43, 170, 187);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: inherit;
background-size: inherit;
background-position: inherit;
filter: blur(3px); /* 模糊背景图片 */
z-index: -1;
}
background-image
:设置背景图片。background-size
:使背景图片覆盖整个背景区域。background-position
:将背景图片居中。font-family
:设置字体。text-align
:将文本居中。color
:设置文本颜色。display: flex
:使用Flexbox布局。flex-direction: column
:将元素垂直排列。justify-content: center
:在主轴方向上居中对齐。align-items: center
:在交叉轴方向上居中对齐。height: 100vh
:设置元素高度为视口高度。margin: 0
:移除默认外边距。position: relative
:设定相对定位,为伪元素定位提供基础。body::before
:使用伪元素在背景图片上添加模糊效果,使前景文字更加突出。
3. 标题样式和发光特效
h1 {
color: #d8c22e;
font-size: 3em; /* 放大文字 */
text-shadow: 0 0 20px rgb(242, 255, 0), 0 0 30px rgba(255, 251, 0, 0.714), 0 0 40px rgba(242, 255, 0, 0.232); /* 发光特效 */
}
h1
:设置标题的样式。color
:设置标题颜色。font-size
:放大标题文字。text-shadow
:添加多个阴影层,形成发光特效。
4. LED控制区域样式
.led-wrapper {
display: flex;
align-items: center;
margin: 10px;
}
.led-name {
margin-right: 10px;
font-size: 1.5em; /* 放大文字 */
cursor: pointer; /* 鼠标指针变为手型 */
text-shadow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 20px rgba(255, 255, 255, 0.6), 0 0 30px rgba(255, 255, 255, 0.4); /* 发光特效 */
}
.switch {
position: relative;
display: inline-block;
width: 80px; /* 增加开关大小 */
height: 44px; /* 增加开关大小 */
margin: 10px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 44px;
}
.slider:before {
position: absolute;
content: "";
height: 36px;
width: 36px;
left: 4px;
bottom: 4px;
background-color: rgb(255, 242, 242);
transition: .4s;
border-radius: 50%;
}
.led-wrapper
:设置LED控制区域的样式,使用Flexbox布局将LED名称和开关按钮水平排列。.led-name
:设置LED名称的样式,包括放大文字、鼠标指针样式和发光特效。.switch
:设置开关按钮的容器样式,包括大小和位置。.switch input
:隐藏实际的复选框,只显示自定义样式的开关按钮。.slider
:设置自定义开关按钮的样式,包括位置、背景颜色和圆角。.slider:before
:设置开关按钮滑块的样式,包括大小、位置和圆角。
5. 开关按钮颜色
/* 红色LED开关颜色 */
.switch.red input:checked + .slider {
background-color: rgb(194, 37, 37);
}
.switch.red input:checked + .slider:before {
transform: translateX(36px);
}
/* 蓝色LED开关颜色 */
.switch.blue input:checked + .slider {
background-color: rgb(33, 33, 185);
}
.switch.blue input:checked + .slider:before {
transform: translateX(36px);
}
.switch.red input:checked + .slider
:当红色LED开关被选中时,设置滑块背景颜色为红色。.switch.red input:checked + .slider:before
:当红色LED开关被选中时,滑块向右移动。.switch.blue input:checked + .slider
:当蓝色LED开关被选中时,设置滑块背景颜色为蓝色。.switch.blue input:checked + .slider:before
:当蓝色LED开关被选中时,滑块向右移动。
6. 签名样式
.signature {
position: absolute;
bottom: 10px;
width: 100%;
text-align: center;
font-size: 1.2em; /* 放大文字 */
text-shadow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 20px rgba(255, 255, 255, 0.6), 0 0 30px rgba(255, 255, 255, 0.4); /* 发光特效 */
}
</style>
</head>
<body>
<h1>Light Control Center</h1>
.signature
:设置签名的样式,包括位置、对齐方式、文字大小和发光特效。
7. LED控制界面
<div class="led-wrapper">
<!-- 使用contenteditable属性使名称可编辑 -->
<span class="led-name" id="led1-name" contenteditable="true">{{ led_names['led1'] }}</span>
<label class="switch red">
<!-- 增加开关控件 -->
<input type="checkbox" id="led1" onclick="toggleLed(1)">
<span class="slider"></span>
</label>
</div>
<div class="led-wrapper">
<span class="led-name" id="led2-name" contenteditable="true">{{ led_names['led2'] }}</span>
<label class="switch blue">
<input type="checkbox" id="led2" onclick="toggleLed(2)">
<span class="slider"></span>
</label>
</div>
<div class="signature">
Wishing you a Merry Christmas and a Happy New Year! - FmiL
</div>
<div class="led-wrapper">
:每个LED控制区域的容器。<span class="led-name" contenteditable="true">
:可编辑的LED名称,用户可以点击并编辑名称。<label class="switch">
:自定义开关按钮的容器。<input type="checkbox">
:实际的复选框,被隐藏。<span class="slider"></span>
:自定义的开关按钮。
8. JavaScript代码
<script>
// 获取LED名称并保存到服务器
function saveLedName(led, name) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/set_name", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({ led: led, name: name }));
}
// 增加事件监听器,当名称编辑完成后保存
document.getElementById('led1-name').addEventListener('blur', function() {
saveLedName('led1', this.innerText);
});
document.getElementById('led2-name').addEventListener('blur', function() {
saveLedName('led2', this.innerText);
});
// 更新LED状态
function updateLedStates() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/led_state", true);
xhr.onload = function() {
if (xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
document.getElementById('led1').checked = response.led1_state === 0;
document.getElementById('led2').checked = response.led2_state === 0;
}
};
xhr.send();
}
// 切换LED状态
function toggleLed(ledId) {
var checkBox = document.getElementById('led' + ledId);
var action = checkBox.checked ? 'on' : 'off';
var xhr = new XMLHttpRequest();
xhr.open("POST", `/led/${ledId}/${action}`, true);
xhr.send();
}
// 每秒更新一次LED状态
setInterval(updateLedStates, 1000);
</script>
</body>
</html>
saveLedName(led, name)
:发送POST请求,将LED名称保存到服务器。- 事件监听器:监听
blur
事件,当用户编辑完名称并失去焦点时,调用saveLedName
保存名称。 updateLedStates()
:发送GET请求获取LED状态,并更新网页上的开关状态。toggleLed(ledId)
:发送POST请求切换LED状态。setInterval(updateLedStates, 1000)
:每秒调用一次updateLedStates
,实时更新LED状态。
5 遇到的主要难题
5.1 PWM测试失败
本来构思为一个LED是反转开关,另一个是PWM亮度调节,特意将LED放在两个标记PWM的引脚,从网上搜也是支持这两个引脚输出PWM的。但是使用例程一直报错。包括使用AI生成的代码测试PWM也是一直报错,遂放弃,先选用LED开关控制,确保任务能够顺利按时完成。
6 实物展示
使用的各个部分如下图所示:
实物特写如下:
项目详情、以及演奏演示请移步视频观看,欢迎点赞投币🙂🙂
7 未来的计划建议
该项目已经成功实现了本地IOT控制灯的功能,实现局域网控制,并达到了预期指标。但是目前来看,依然还有许多可以提升与扩展的地方。
- 实现PWM输出,让LED 能够实现亮度调节。
- 增加IIC,SPI等协议通讯,实现更多IOT输入、输出类型,丰富系统。
- 增加自定义域名注册,并将网页托管在域名上,来代替IP地址,更好记录。
- 制作更加生动有趣的页面,比如增加背景音乐