7.22
【写在前面】
上一个作业中写了用小智AI和炫酷灯环互动的事,有AI的加持,项目就有趣多了。然后改写了一个不用小智版本,直接用屏幕按钮来控制灯环的灯效,不用联网,可以从行空板上启动,也很方便。

材料清单
- M10 X1
- M10扩展板 X1
- RGB灯环 X1
灯环接P23。
这个帖子主要就是放代码。
功能说明
UI界面设计:
采用两列按钮布局(10个灯效按钮 + 2个速度控制)
顶部状态栏显示当前灯效状态和速度级别
背景图片支持(需准备back.png)
灯效控制:
彩虹旋转 🌈
单色闪烁(红色默认)🔴
呼吸效果(蓝色默认)💙
跑马灯(绿色默认)💚
火焰效果 🔥
流星效果(橙色默认)☄️
彩虹呼吸 🌈💨
音乐同步(模拟)🎵
灯效序列(随机组合)🔄
熄灭灯环 ⚫
速度控制:
速度+/-按钮调节效果速度(1-10级)
实时显示当前速度级别
使用说明:
将代码保存为local_light_control.py
准备背景图片back.png(可选)
在行空板M10上直接运行
点击按钮切换不同灯效
速度+/-按钮实时调节效果速度
代码
# local_light_control.py
from unihiker import GUI
import threading
import time
import random
import sys
from pinpong.board import Board, Pin, NeoPixel
import math
# 初始化硬件
Board().begin()
np = NeoPixel(Pin(Pin.P23), 24)
np.brightness(100) # 固定亮度设置
# 全局变量
current_effect_thread = None
stop_event = threading.Event()
effect_speed = 1.0 # 效果速度系数 (1.0为正常速度)
speed_level = 5 # 速度级别 (1-10)
# 灯效函数 (同步版)
def rainbow_rotate():
"""彩虹色循环旋转效果"""
speed = 0.1 / effect_speed
np.rainbow(0, np.num - 1, 1, 360)
while not stop_event.is_set():
np.rotate(1)
time.sleep(speed)
def blink(color):
"""闪烁效果"""
interval = 0.5 / effect_speed
while not stop_event.is_set():
np.range_color(0, np.num, color)
time.sleep(interval)
np.clear()
time.sleep(interval)
def breathe(color):
"""呼吸灯效果"""
interval = 0.03 / effect_speed
min_brightness = 30
max_brightness = 100
try:
np.range_color(0, np.num, color)
current_bright = min_brightness
direction = 1
while not stop_event.is_set():
np.brightness(current_bright)
time.sleep(interval)
current_bright += direction * 2
if current_bright >= max_brightness:
current_bright = max_brightness
direction = -1
elif current_bright <= min_brightness:
current_bright = min_brightness
direction = 1
except Exception as e:
print(f"呼吸灯错误: {e}")
def running_lights(color):
"""跑马灯效果"""
interval = 0.1 / effect_speed
try:
np.clear()
while not stop_event.is_set():
for i in range(np.num):
if stop_event.is_set(): break
np[i] = color
if i > 0:
np[i-1] = (color >> 1) & 0x7F7F7F # 半亮
if i > 1:
np[i-2] = 0 # 熄灭
time.sleep(interval)
except Exception as e:
print(f"跑马灯错误: {e}")
def fire_effect():
"""火焰效果"""
interval = 0.05 / effect_speed
heat = [0] * np.num
while not stop_event.is_set():
# 冷却
for i in range(np.num):
heat[i] = max(0, heat[i] - random.randint(0, 5))
# 传播热量
for i in range(np.num - 3, 0, -1):
heat[i] = (heat[i-1] + heat[i-2] + heat[i-2]) // 3
# 添加火花
if random.randint(0, 255) < 120:
y = random.randint(0, 7)
heat[y] = min(255, heat[y] + random.randint(160, 255))
# 转换为颜色
for i in range(np.num):
# 热色映射 (黑->红->黄->白)
t = heat[i]
if t < 85:
r = t * 3
g = 0
b = 0
elif t < 170:
r = 255
g = (t - 85) * 3
b = 0
else:
r = 255
g = 255
b = (t - 170) * 3
np[i] = (min(255, r) << 16) | (min(255, g) << 8) | min(255, b)
time.sleep(interval)
def meteor_effect(color=0xFFA500):
"""流星效果"""
interval = 0.05 / effect_speed
tail_length = 10
decay = 0.7
while not stop_event.is_set():
# 创建新流星
meteor_pos = 0
meteor_brightness = 1.0
meteor_pixels = [0] * np.num
while meteor_pos < np.num + tail_length and not stop_event.is_set():
# 清除上一帧
np.clear()
# 绘制流星
for i in range(tail_length):
pos = meteor_pos - i
if 0 <= pos < np.num:
# 计算亮度衰减
brightness = meteor_brightness * (decay ** i)
r = int(((color >> 16) & 0xFF) * brightness)
g = int(((color >> 8) & 0xFF) * brightness)
b = int((color & 0xFF) * brightness)
meteor_pixels[pos] = (r << 16) | (g << 8) | b
np[pos] = meteor_pixels[pos]
meteor_pos += 1
time.sleep(interval)
# 流星消失后添加一些星星
for _ in range(5):
if stop_event.is_set(): break
for i in range(5):
pos = random.randint(0, np.num-1)
np[pos] = 0xFFFFFF
time.sleep(0.2)
np.clear()
time.sleep(0.1)
def rainbow_breathe():
"""彩虹呼吸效果"""
interval = 0.03 / effect_speed
min_brightness = 30
max_brightness = 100
hue = 0
try:
current_bright = min_brightness
direction = 1
while not stop_event.is_set():
# 计算当前颜色
r, g, b = hsv_to_rgb(hue, 1.0, 1.0)
color = (int(r*255) << 16) | (int(g*255) << 8) | int(b*255)
# 设置所有灯珠
np.range_color(0, np.num, color)
np.brightness(current_bright)
# 更新亮度和色相
current_bright += direction * 2
if current_bright >= max_brightness:
current_bright = max_brightness
direction = -1
elif current_bright <= min_brightness:
current_bright = min_brightness
direction = 1
hue = (hue + 30) % 360 # 每次呼吸周期改变颜色
time.sleep(interval)
except Exception as e:
print(f"彩虹呼吸错误: {e}")
def music_sync_effect():
"""音乐同步效果 (模拟)"""
try:
# 创建频谱数据 (模拟音乐频谱)
spectrum = [0.1] * 8
decay = 0.9
while not stop_event.is_set():
# 随机添加"音乐节拍"
if random.random() < 0.3:
band = random.randint(0, 7)
spectrum[band] = min(1.0, spectrum[band] + random.uniform(0.2, 0.8))
# 将频谱映射到灯环
for i in range(np.num):
# 将灯珠分组对应频谱带
band_idx = i % 8
value = spectrum[band_idx]
# 计算颜色 (从蓝到红)
r = int(255 * value)
g = int(100 * value)
b = int(255 * (1 - value))
np[i] = (r << 16) | (g << 8) | b
# 频谱衰减
for i in range(8):
spectrum[i] = max(0.1, spectrum[i] * decay)
time.sleep(0.05 / effect_speed)
except Exception as e:
print(f"音乐同步错误: {e}")
def effect_sequence():
"""灯效组合序列"""
try:
# 序列1: 派对模式
sequences = {
"派对模式": [
(rainbow_rotate, 5.0),
(lambda: blink(0xFF0000), 2.0),
(lambda: blink(0x00FF00), 2.0),
(lambda: blink(0x0000FF), 2.0),
(meteor_effect, 8.0)
],
"浪漫氛围": [
(lambda: breathe(0xFF69B4), 4.0), # 粉色
(lambda: breathe(0x800080), 4.0), # 紫色
(rainbow_breathe, 8.0)
],
"火焰派对": [
(fire_effect, 6.0),
(lambda: meteor_effect(0xFF4500), 6.0), # 橙红色
(fire_effect, 6.0),
(lambda: meteor_effect(0xFFD700), 6.0) # 金色
],
"彩虹狂欢": [
(rainbow_rotate, 8.0),
(rainbow_breathe, 8.0),
(lambda: running_lights(0x00FF00), 4.0),
(lambda: running_lights(0xFF0000), 4.0),
(lambda: running_lights(0x0000FF), 4.0)
]
}
# 随机选择一个序列
seq_name, seq = random.choice(list(sequences.items()))
update_status(f"序列: {seq_name}")
# 执行序列
for effect_func, duration in seq:
if stop_event.is_set(): return
stop_current_effect()
threading.Thread(target=effect_func, daemon=True).start()
time.sleep(duration)
# 序列结束后恢复彩虹旋转
stop_current_effect()
threading.Thread(target=rainbow_rotate, daemon=True).start()
update_status("彩虹旋转")
except Exception as e:
print(f"灯效序列错误: {e}")
def hsv_to_rgb(h, s, v):
"""HSV转RGB颜色"""
h = h % 360
c = v * s
x = c * (1 - abs((h / 60) % 2 - 1))
m = v - c
if 0 <= h < 60:
r, g, b = c, x, 0
elif 60 <= h < 120:
r, g, b = x, c, 0
elif 120 <= h < 180:
r, g, b = 0, c, x
elif 180 <= h < 240:
r, g, b = 0, x, c
elif 240 <= h < 300:
r, g, b = x, 0, c
else: # 300 <= h < 360
r, g, b = c, 0, x
return r + m, g + m, b + m
def stop_current_effect():
"""停止当前效果"""
global stop_event
stop_event.set()
time.sleep(0.1) # 给线程一点时间退出
stop_event.clear()
np.clear()
def get_color_by_name(color_str):
"""根据名称获取颜色值"""
color_map = {
"红色": 0xFF0000, "绿色": 0x00FF00, "蓝色": 0x0000FF,
"黄色": 0xFFFF00, "紫色": 0x800080, "青色": 0x00FFFF,
"白色": 0xFFFFFF, "橙色": 0xFFA500, "粉色": 0xFFC0CB,
"金色": 0xFFD700, "品红": 0xFF00FF
}
return color_map.get(color_str, 0xFF0000) # 默认为红色
def set_speed(level):
"""设置速度 (1-10)"""
global effect_speed, speed_level
speed_level = level
# 1-10 映射到 0.5-2.0
effect_speed = 0.5 + (speed_level - 1) * 0.15
update_status(f"速度: {speed_level}级")
# 按钮事件处理函数
def start_rainbow():
stop_current_effect()
threading.Thread(target=rainbow_rotate, daemon=True).start()
update_status("彩虹旋转")
def start_blink():
stop_current_effect()
threading.Thread(target=blink, args=(get_color_by_name("红色"),), daemon=True).start()
update_status("红色闪烁")
def start_breathe():
stop_current_effect()
threading.Thread(target=breathe, args=(get_color_by_name("蓝色"),), daemon=True).start()
update_status("蓝色呼吸")
def start_running_lights():
stop_current_effect()
threading.Thread(target=running_lights, args=(get_color_by_name("绿色"),), daemon=True).start()
update_status("绿色跑马灯")
def start_fire():
stop_current_effect()
threading.Thread(target=fire_effect, daemon=True).start()
update_status("火焰效果")
def start_meteor():
stop_current_effect()
threading.Thread(target=meteor_effect, args=(get_color_by_name("橙色"),), daemon=True).start()
update_status("橙色流星")
def start_rainbow_breathe():
stop_current_effect()
threading.Thread(target=rainbow_breathe, daemon=True).start()
update_status("彩虹呼吸")
def start_music_sync():
stop_current_effect()
threading.Thread(target=music_sync_effect, daemon=True).start()
update_status("音乐同步")
def start_sequence():
stop_current_effect()
threading.Thread(target=effect_sequence, daemon=True).start()
update_status("灯效序列")
def turn_off():
stop_current_effect()
update_status("灯环已熄灭")
def speed_up():
set_speed(min(10, speed_level + 1))
def speed_down():
set_speed(max(1, speed_level - 1))
def update_status(text):
"""更新状态显示"""
global status_label
if status_label:
status_label.config(text=f"状态: {text}")
else:
print(f"状态: {text}")
# 初始化GUI
u_gui = GUI()
u_gui.draw_image(image="back.png", x=0, y=0)
u_gui.draw_text(text="M10灯效控制器", x=10, y=5, font_size=20, color="#FFFFFF")
# 添加状态显示标签
status_label = u_gui.draw_text(text="状态: 就绪", x=10, y=30, font_size=12, color="#FFFFFF")
# 创建灯效控制按钮 (两列布局)
# 第一列
rainbow_btn = u_gui.add_button(text="彩虹", x=20, y=60, w=80, h=30, onclick=start_rainbow)
blink_btn = u_gui.add_button(text="闪烁", x=20, y=100, w=80, h=30, onclick=start_blink)
breathe_btn = u_gui.add_button(text="呼吸", x=20, y=140, w=80, h=30, onclick=start_breathe)
running_btn = u_gui.add_button(text="跑马灯", x=20, y=180, w=80, h=30, onclick=start_running_lights)
fire_btn = u_gui.add_button(text="火焰", x=20, y=220, w=80, h=30, onclick=start_fire)
# 第二列
meteor_btn = u_gui.add_button(text="流星", x=120, y=60, w=80, h=30, onclick=start_meteor)
rainbow_b_btn = u_gui.add_button(text="彩虹呼吸", x=120, y=100, w=80, h=30, onclick=start_rainbow_breathe)
music_btn = u_gui.add_button(text="音乐同步", x=120, y=140, w=80, h=30, onclick=start_music_sync)
sequence_btn = u_gui.add_button(text="灯效序列", x=120, y=180, w=80, h=30, onclick=start_sequence)
off_btn = u_gui.add_button(text="熄灭", x=120, y=220, w=80, h=30, onclick=turn_off)
# 速度控制按钮
speed_up_btn = u_gui.add_button(text="速度+", x=20, y=270, w=80, h=30, onclick=speed_up)
speed_down_btn = u_gui.add_button(text="速度-", x=120, y=270, w=80, h=30, onclick=speed_down)
# 设置初始速度
set_speed(speed_level)
update_status("就绪")
# 保持程序运行
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
stop_current_effect()
print("\n程序退出")
本项目代码由DeepSeek协助完成,致谢。
社区的帖子一是记笔记,二是做备份,以后想玩这个项目了来找代码,不会丢。
评论