在这个智能家居普及的时代,你是否也想亲手打造一个属于自己的智能灯光系统?今天,我将带你使用ESP32-C5开发板和WS2812B灯带,制作一个功能丰富的智能灯带控制器。无需复杂的电路知识,只需跟着本文一步步操作,你就能拥有一个完全自定义的彩虹灯带!
项目概述
这个智能灯带控制器基于ESP32-C5开发板,通过WiFi连接,提供了一个美观的网页控制界面。你可以通过任何连接同一WiFi的设备(手机、电脑、平板)来控制灯带的颜色、亮度和多种光效模式。
主要功能特点:
6种光效模式:静态光、彩虹循环、呼吸效果、波浪效果、火焰效果和关闭
实时颜色选择与亮度调节
响应式网页界面,支持移动端和桌面端
一键快捷颜色选择
流畅的动画过渡效果
所需材料
ESP32-C5开发板 ×1
WS2812B RGB灯带(60颗LED) ×1
5V电源适配器(根据灯带长度选择合适功率) ×1
杜邦线若干
面包板(可选) ×1
硬件连接
连接非常简单,只需要三根线:
ESP32-C5 | WS2812B灯带 |
---|---|
3.3V | VCC(注意:需要外部5V供电) |
GND | GND |
D6 | DIN |
重要提示:WS2812B灯带需要单独的5V供电,切勿直接从ESP32-C5的3.3V引脚取电,否则可能导致开发板损坏或灯带工作不稳定。
代码解析
我们的代码主要分为几个关键部分:
1. 初始化设置
2. 光效算法
3. Web服务器与控制界面
软件部署
将完整代码复制到MicroPython开发环境中(如Thonny)
修改WiFi名称和密码
运行代码
查看串口输出,获取ESP32-C5的IP地址
在浏览器中输入该IP地址,即可打开控制界面
使用指南
基本操作
连接设备:确保你的手机/电脑与ESP32-C5连接同一WiFi网络
打开控制页面:在浏览器中输入ESP32-C5的IP地址
选择模式:点击相应的模式按钮切换光效
高级功能
自定义颜色:使用颜色选择器或快捷颜色按钮
亮度调节:使用滑块实时调整亮度
模式切换:在不同光效模式间无缝切换
效果演示:
原理解析
WiFi通信
ESP32-C5作为Web服务器,监听80端口的HTTP请求。当你在网页上操作时,浏览器会发送相应的HTTP请求到ESP32-C5,从而改变灯带的显示状态。
多线程处理
项目使用了多线程技术,主线程处理HTTP请求,另一个线程专门处理光效渲染,确保界面响应流畅,光效动画不卡顿。
颜色空间转换
代码中实现了RGB颜色空间与亮度的实时计算,确保颜色显示准确且亮度调节平滑。
创意扩展
这个项目有巨大的扩展潜力,你可以尝试:
添加音乐同步:通过麦克风模块实现音乐节奏同步光效
定时任务:添加定时开关和场景切换功能
语音控制:集成语音助手(如天猫精灵、小爱同学)
传感器联动:添加人体传感器实现人来灯亮、人走灯灭
多区域控制:使用多个ESP32-C5控制不同区域的灯带
故障排除
常见问题:
灯带不亮:检查电源连接,确保5V供电充足
无法连接WiFi:检查SSID和密码是否正确,信号强度是否足够
网页无法打开:检查IP地址是否正确,设备是否在同一网络
光效卡顿:减少LED数量或降低动画刷新率
结语
通过这个项目,你不仅制作了一个实用的智能灯带控制器,还学习了ESP32-C5编程、Web服务器搭建和RGB灯带控制等宝贵技能。智能硬件的魅力在于它的无限可能性,希望这个项目能激发你更多的创作灵感!
创客格言:最好的项目,永远是下一个。保持好奇,持续创造!
import network
import socket
import time
import json
from machine import Pin
import neopixel
# 配置参数
LED_PIN = 6 # D6口
LED_COUNT = 60
WIFI_SSID = "您的WiFi名称"
WIFI_PASSWORD = "您的WiFi密码"
# 初始化NeoPixel
np = neopixel.NeoPixel(Pin(LED_PIN), LED_COUNT)
# 全局变量
current_mode = "off" # 默认改为关闭状态
current_color = (255, 255, 255)
current_brightness = 50
is_running = True
def connect_wifi():
"""连接WiFi"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('正在连接WiFi...')
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
for i in range(20):
if wlan.isconnected():
break
time.sleep(1)
if wlan.isconnected():
print('WiFi连接成功!')
print('网络配置:', wlan.ifconfig())
return wlan.ifconfig()[0]
else:
print('WiFi连接失败!')
return None
def apply_brightness(color, brightness):
"""应用亮度调整"""
r, g, b = color
brightness_factor = brightness / 100.0
return (int(r * brightness_factor), int(g * brightness_factor), int(b * brightness_factor))
def set_all_pixels(color):
"""设置所有LED为指定颜色"""
adjusted_color = apply_brightness(color, current_brightness)
for i in range(LED_COUNT):
np[i] = adjusted_color
np.write()
def rainbow_cycle(wait=0.05):
"""彩虹循环效果"""
for j in range(255):
if current_mode != "rainbow":
break
for i in range(LED_COUNT):
rc_index = (i * 256 // LED_COUNT) + j
r = int((abs(rc_index % 256 - 128) * 2) % 256)
g = int((abs((rc_index + 85) % 256 - 128) * 2) % 256)
b = int((abs((rc_index + 170) % 256 - 128) * 2) % 256)
np[i] = apply_brightness((r, g, b), current_brightness)
np.write()
time.sleep(wait)
def breathing_effect(color, speed=0.02):
"""呼吸灯效果"""
while current_mode == "breathing":
for brightness in range(0, 101, 2):
if current_mode != "breathing":
break
adjusted_color = apply_brightness(color, brightness)
set_all_pixels(adjusted_color)
time.sleep(speed)
for brightness in range(100, -1, -2):
if current_mode != "breathing":
break
adjusted_color = apply_brightness(color, brightness)
set_all_pixels(adjusted_color)
time.sleep(speed)
def wave_effect(color1, color2, speed=0.05):
"""波浪效果"""
while current_mode == "wave":
for pos in range(LED_COUNT * 2):
if current_mode != "wave":
break
for i in range(LED_COUNT):
distance = abs(i - pos % LED_COUNT)
intensity = max(0, 1 - distance / (LED_COUNT / 3))
if pos // LED_COUNT % 2 == 0:
r = int(color1[0] * intensity + color2[0] * (1 - intensity))
g = int(color1[1] * intensity + color2[1] * (1 - intensity))
b = int(color1[2] * intensity + color2[2] * (1 - intensity))
else:
r = int(color2[0] * intensity + color1[0] * (1 - intensity))
g = int(color2[1] * intensity + color1[1] * (1 - intensity))
b = int(color2[2] * intensity + color1[2] * (1 - intensity))
np[i] = apply_brightness((r, g, b), current_brightness)
np.write()
time.sleep(speed)
def fire_effect():
"""火焰效果"""
fire_colors = [(255, 0, 0), (255, 69, 0), (255, 140, 0), (255, 165, 0)]
while current_mode == "fire":
for i in range(LED_COUNT):
# 随机选择火焰颜色并添加一些随机性
import random
base_color = random.choice(fire_colors)
flicker = random.randint(-30, 30)
r = max(0, min(255, base_color[0] + flicker))
g = max(0, min(255, base_color[1] + flicker))
b = max(0, min(255, base_color[2] + flicker // 2))
np[i] = apply_brightness((r, g, b), current_brightness)
np.write()
time.sleep(0.1)
def handle_effects():
"""处理各种光效"""
global current_mode, current_color
while is_running:
if current_mode == "off":
# 关闭模式:保持所有LED关闭
set_all_pixels((0, 0, 0))
time.sleep(0.5) # 降低CPU使用率
elif current_mode == "static":
set_all_pixels(current_color)
time.sleep(1)
elif current_mode == "rainbow":
rainbow_cycle()
elif current_mode == "breathing":
breathing_effect(current_color)
elif current_mode == "wave":
wave_effect(current_color, (255 - current_color[0], 255 - current_color[1], 255 - current_color[2]))
elif current_mode == "fire":
fire_effect()
else:
time.sleep(0.1)
def parse_request(request):
"""解析HTTP请求"""
lines = request.split('\n')
if len(lines) > 0:
first_line = lines[0].split()
if len(first_line) > 1:
return first_line[1]
return '/'
def generate_html():
"""生成控制页面HTML"""
return """<!DOCTYPE html>
<html>
<head>
<title>ESP32-C5 LED 灯带控制器</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
color: white;
min-height: 100vh;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255,255,255,0.1);
padding: 30px;
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
margin-bottom: 30px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
.control-group {
margin-bottom: 25px;
padding: 20px;
background: rgba(255,255,255,0.1);
border-radius: 15px;
}
.color-preview {
width: 100px;
height: 100px;
border-radius: 10px;
margin: 10px auto;
border: 3px solid white;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
}
input[type="color"] {
width: 100%;
height: 50px;
border: none;
border-radius: 10px;
cursor: pointer;
}
input[type="range"] {
width: 100%;
margin: 10px 0;
}
.btn-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin: 20px 0;
}
button {
padding: 15px;
border: none;
border-radius: 10px;
background: linear-gradient(45deg, #FF6B6B, #EE5A24);
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.status {
text-align: center;
padding: 15px;
background: rgba(255,255,255,0.2);
border-radius: 10px;
margin: 20px 0;
font-weight: bold;
}
.off-mode {
background: linear-gradient(45deg, #636e72, #2d3436) !important;
}
</style>
</head>
<body>
<div class="container">
<h1>🎨 ESP32-C5 智能灯带控制器</h1>
<div class="status" id="status">
系统就绪 - 灯带当前关闭
</div>
<div class="control-group">
<h3>🎯 颜色选择</h3>
<input type="color" id="colorPicker" value="#ffffff" onchange="setColor(this.value)">
<div class="color-preview" id="colorPreview"></div>
</div>
<div class="control-group">
<h3>💡 亮度控制</h3>
<input type="range" id="brightness" min="0" max="100" value="50" onchange="setBrightness(this.value)">
<div>亮度: <span id="brightnessValue">50</span>%</div>
</div>
<div class="control-group">
<h3>✨ 光效模式</h3>
<div class="btn-group">
<button onclick="setMode('static')">静态光</button>
<button onclick="setMode('rainbow')">彩虹循环</button>
<button onclick="setMode('breathing')">呼吸效果</button>
<button onclick="setMode('wave')">波浪效果</button>
<button onclick="setMode('fire')">火焰效果</button>
<button class="off-mode" onclick="setMode('off')">关闭灯带</button>
</div>
</div>
<div class="control-group">
<h3>⚡ 快捷颜色</h3>
<div class="btn-group">
<button style="background:linear-gradient(45deg,#FF6B6B,#EE5A24)" onclick="setQuickColor(255,0,0)">红色</button>
<button style="background:linear-gradient(45deg,#00b894,#00a085)" onclick="setQuickColor(0,255,0)">绿色</button>
<button style="background:linear-gradient(45deg,#74b9ff,#0984e3)" onclick="setQuickColor(0,0,255)">蓝色</button>
<button style="background:linear-gradient(45deg,#fdcb6e,#e17055)" onclick="setQuickColor(255,255,0)">黄色</button>
<button style="background:linear-gradient(45deg,#a29bfe,#6c5ce7)" onclick="setQuickColor(255,0,255)">紫色</button>
<button style="background:linear-gradient(45deg,#ffffff,#b2bec3)" onclick="setQuickColor(255,255,255)">白色</button>
</div>
</div>
</div>
<script>
function setColor(color) {
const hex = color.substring(1);
const r = parseInt(hex.substr(0,2), 16);
const g = parseInt(hex.substr(2,2), 16);
const b = parseInt(hex.substr(4,2), 16);
document.getElementById('colorPreview').style.background = color;
fetch('/color?r=' + r + '&g=' + g + '&b=' + b);
}
function setQuickColor(r, g, b) {
const hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
document.getElementById('colorPicker').value = hex;
document.getElementById('colorPreview').style.background = hex;
fetch('/color?r=' + r + '&g=' + g + '&b=' + b);
}
function setBrightness(value) {
document.getElementById('brightnessValue').textContent = value;
fetch('/brightness?value=' + value);
}
function setMode(mode) {
fetch('/mode?value=' + mode);
document.getElementById('status').textContent = '模式: ' + getModeName(mode);
// 更新按钮样式
document.querySelectorAll('.btn-group button').forEach(btn => {
btn.classList.remove('off-mode');
});
if (mode === 'off') {
event.target.classList.add('off-mode');
}
}
function getModeName(mode) {
const modes = {
'static': '静态光',
'rainbow': '彩虹循环',
'breathing': '呼吸效果',
'wave': '波浪效果',
'fire': '火焰效果',
'off': '关闭'
};
return modes[mode] || mode;
}
// 初始化颜色预览
document.getElementById('colorPreview').style.background = '#ffffff';
</script>
</body>
</html>"""
def start_http_server():
"""启动HTTP服务器"""
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(5)
print('HTTP服务器启动在: http://{}:80'.format(connect_wifi()))
return s
def handle_client(client):
"""处理客户端请求"""
global current_mode, current_color, current_brightness
request = client.recv(1024).decode('utf-8')
path = parse_request(request)
response_body = ""
content_type = "text/html"
try:
if path == '/':
response_body = generate_html()
elif path.startswith('/color'):
# 只有在非关闭模式下才允许改变颜色
if current_mode != "off":
# 解析颜色参数
params = path.split('?')[1].split('&')
color_params = {}
for param in params:
key, value = param.split('=')
color_params[key] = int(value)
current_color = (color_params.get('r', 255),
color_params.get('g', 255),
color_params.get('b', 255))
response_body = json.dumps({"status": "success", "color": current_color})
content_type = "application/json"
elif path.startswith('/brightness'):
params = path.split('?')[1].split('&')
for param in params:
if param.startswith('value='):
current_brightness = int(param.split('=')[1])
break
response_body = json.dumps({"status": "success", "brightness": current_brightness})
content_type = "application/json"
elif path.startswith('/mode'):
params = path.split('?')[1].split('&')
for param in params:
if param.startswith('value='):
new_mode = param.split('=')[1]
current_mode = new_mode # 直接设置新模式,包括"off"
if new_mode == 'off':
# 立即关闭所有LED
set_all_pixels((0, 0, 0))
break
response_body = json.dumps({"status": "success", "mode": current_mode})
content_type = "application/json"
else:
response_body = "404 Not Found"
except Exception as e:
response_body = json.dumps({"status": "error", "message": str(e)})
content_type = "application/json"
# 发送HTTP响应
response = f"""HTTP/1.1 200 OK
Content-Type: {content_type}
Access-Control-Allow-Origin: *
Connection: close
{response_body}"""
client.send(response.encode('utf-8'))
client.close()
def main():
"""主函数"""
# 初始化灯带为关闭状态
set_all_pixels((0, 0, 0))
# 启动HTTP服务器
server_socket = start_http_server()
# 启动光效处理线程
import _thread
_thread.start_new_thread(handle_effects, ())
print("系统启动完成!等待客户端连接...")
print("初始模式:关闭")
# 主循环:处理HTTP请求
while True:
try:
client, addr = server_socket.accept()
print('客户端连接来自:', addr)
handle_client(client)
except Exception as e:
print('处理客户端时出错:', e)
try:
client.close()
except:
pass
if __name__ == "__main__":
main()
评论