回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

ESP32-C5 试用报告二:基于mpy的灯带物联服务 简单

头像 kylinpoet 2025.10.19 14 0

在这个智能家居普及的时代,你是否也想亲手打造一个属于自己的智能灯光系统?今天,我将带你使用ESP32-C5开发板和WS2812B灯带,制作一个功能丰富的智能灯带控制器。无需复杂的电路知识,只需跟着本文一步步操作,你就能拥有一个完全自定义的彩虹灯带!


项目概述

这个智能灯带控制器基于ESP32-C5开发板,通过WiFi连接,提供了一个美观的网页控制界面。你可以通过任何连接同一WiFi的设备(手机、电脑、平板)来控制灯带的颜色、亮度和多种光效模式。

主要功能特点:

6种光效模式:静态光、彩虹循环、呼吸效果、波浪效果、火焰效果和关闭

实时颜色选择与亮度调节

响应式网页界面,支持移动端和桌面端

一键快捷颜色选择

流畅的动画过渡效果

所需材料

ESP32-C5开发板 ×1

WS2812B RGB灯带(60颗LED) ×1

5V电源适配器(根据灯带长度选择合适功率) ×1

杜邦线若干

面包板(可选) ×1

硬件连接

连接非常简单,只需要三根线:

ESP32-C5WS2812B灯带
3.3VVCC(注意:需要外部5V供电)
GNDGND
D6DIN


image.png

重要提示:WS2812B灯带需要单独的5V供电,切勿直接从ESP32-C5的3.3V引脚取电,否则可能导致开发板损坏或灯带工作不稳定。

代码解析

我们的代码主要分为几个关键部分:

1. 初始化设置

image.png

2. 光效算法

image.png

3. Web服务器与控制界面

image.png

image.png

软件部署

将完整代码复制到MicroPython开发环境中(如Thonny)

修改WiFi名称和密码

运行代码

查看串口输出,获取ESP32-C5的IP地址

在浏览器中输入该IP地址,即可打开控制界面

使用指南

基本操作

连接设备:确保你的手机/电脑与ESP32-C5连接同一WiFi网络

打开控制页面:在浏览器中输入ESP32-C5的IP地址

选择模式:点击相应的模式按钮切换光效

image.png

高级功能

自定义颜色:使用颜色选择器或快捷颜色按钮

亮度调节:使用滑块实时调整亮度

模式切换:在不同光效模式间无缝切换

image.png

效果演示:

image.png

原理解析

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()

评论

user-avatar