1. 项目概述用代码点亮你的智能生活如果你手头有几盏Wiz智能灯又恰好对嵌入式开发或者物联网IoT有点兴趣那么今天这个项目可能就是为你准备的。我们不再依赖手机App上的那几个固定按钮而是要用一块小小的开发板通过编写Python代码来完全掌控灯光的开关、颜色、亮度和场景。这听起来可能有点极客但实际操作起来你会发现它比想象中要简单和有趣得多。这个项目的核心是使用CircuitPython来控制Wiz智能灯。CircuitPython是运行在微控制器比如ESP32、RP2040上的Python 3的一个实现它的语法和标准Python几乎一样但提供了直接操作硬件引脚、连接WiFi等嵌入式功能。而Wiz灯作为一种基于WiFi的智能灯具它内部有一个小型的网络服务器监听特定的UDP端口默认是38899等待接收JSON格式的指令。我们的任务就是用CircuitPython写一个“客户端”通过WiFi网络向这个端口发送正确的指令。整个过程可以拆解为几个清晰的步骤首先让你的开发板比如ESP32-S2 Feather连上家里的WiFi然后找到智能灯在局域网内的IP地址接着利用一个现成的adafruit_wiz库通过几行代码就能发送控制命令最后你还可以加上几个物理按钮做一个实体的遥控器或者写个脚本让灯光根据时间、天气自动变化。这不仅仅是简单的开关灯而是打开了智能家居自动化的一扇门让你能根据自己的想法定制灯光逻辑。无论你是想做一个会根据日落时间自动调暗的阅读灯还是一个能随着音乐节奏变化的氛围灯这个项目都提供了坚实的基础。2. 硬件与软件环境准备在开始写代码之前我们需要把“舞台”搭好。这包括选择合适的硬件设备以及配置好对应的软件开发环境。2.1 硬件选型你的微型物联网中枢你需要一块支持WiFi且能运行CircuitPython的开发板。这就像是整个项目的大脑和网络模块。原文中提到了好几款我这里结合自己的使用体验帮你分析一下怎么选Adafruit ESP32-S2/S3 Feather系列这是我的首选推荐尤其是对于初学者。Feather板型设计优秀引脚布局合理并且自带锂电池管理芯片方便做成便携设备。ESP32-S2和S3性能足够WiFi稳定社区支持也好。如果你还想有个小屏幕显示状态ESP32-S3 TFT Feather是个很酷的选择。Raspberry Pi Pico W / 2W这两款板子以其极高的性价比著称。RP2040芯片性能强劲Pico W的WiFi也完全够用。它们的优势在于极其丰富的GPIO和更低的功耗。不过相比Feather它们需要额外的电路才能方便地连接电池。ESP32-C6 Feather这是较新的选择支持WiFi 6。对于这个控制智能灯的项目来说WiFi 6的优势并不明显但它代表了更新的技术。如果你手头正好有或者想体验新硬件完全可以使用。我的实操心得对于第一个项目我强烈建议从Adafruit ESP32-S2 Feather开始。它的CircuitPython固件和库支持最为成熟遇到问题也最容易找到解决方案。而且Feather生态有大量配套的“翅膀”扩展板和Stemma QT/Qwiic传感器未来扩展性极佳。除了主控板你还需要Wiz智能灯任何型号都可以确保它已经通过手机App成功接入你的家庭WiFi网络。USB数据线一定要是数据线而不是只能充电的线。这是给开发板刷固件和传输代码的生命线。面包板、跳线和按钮可选如果你想做后面提到的实体按钮遥控器就需要这些。2.2 软件环境搭建给开发板注入灵魂硬件准备好了接下来是软件部分。这里没有复杂的IDE安装一切都在“文件管理器”中完成。第一步安装CircuitPython固件访问CircuitPython官网circuitpython.org或Adafruit的对应产品页面找到你的开发板型号下载最新的.uf2固件文件。用USB线连接开发板和电脑。大多数板子如Pico在首次连接时会以一个名为RPI-RP2或类似的U盘形式出现。将下载的.uf2文件拖进去。板子会自动重启之后电脑上会出现一个名为CIRCUITPY的新U盘。如果没出现可能需要按一下板子上的复位RESET按钮。第二步准备代码编辑器你可以使用任何纯文本编辑器来编写code.py比如VS Code、Thonny、甚至系统自带的记事本保存时需注意编码。我推荐使用Mu Editor或VS Code with CircuitPython插件它们有代码高亮、串口监视器等针对CircuitPython的便利功能。第三步获取必要的库文件CircuitPython的强大在于其丰富的库。我们需要两个核心库adafruit_wiz这是控制Wiz灯的专用库。adafruit_bus_device/adafruit_register一些底层支持库通常adafruit_wiz会依赖它们。获取库文件最简单的方法是下载Adafruit的CircuitPython库包Bundle。前往Adafruit的CircuitPython库页面下载与你固件版本匹配的完整库包是一个zip文件。解压后在lib文件夹中找到上述库的.mpy或文件夹将它们复制到你的CIRCUITPY盘符下的lib文件夹中如果没有lib文件夹就新建一个。至此你的开发板已经是一个可以执行Python代码的智能设备了。CIRCUITPY盘根目录下的code.py文件就是主程序板子每次启动或复位后都会自动运行它。3. 网络配置与设备发现让开发板和灯“对上话”要让开发板控制灯前提是它们必须在同一个WiFi网络下并且彼此知道如何通信。这里有两个关键点一是让开发板联网二是找到灯的地址。3.1 安全存储WiFi凭证settings.toml的妙用早期CircuitPython项目会把WiFi密码直接写在code.py里这既不安全也不便于分享代码。现在我们使用settings.toml文件来管理所有敏感信息。在你的CIRCUITPY盘根目录下新建一个名为settings.toml的纯文本文件。它的内容格式如下# 你的WiFi网络名称和密码 CIRCUITPY_WIFI_SSID 你的WiFi名称 CIRCUITPY_WIFI_PASSWORD 你的WiFi密码 # 以下是其他可能用到的服务密钥示例本项目暂时不需要 # ADAFRUIT_AIO_USERNAME 你的Adafruit IO用户名 # ADAFRUIT_AIO_KEY 你的Adafruit IO密钥重要细节与避坑指南格式严格等号两边有空格字符串必须用双引号括起来。这是TOML格式的要求。文件位置必须放在CIRCUITPY的根目录不能放在任何文件夹里。编码保存时确保编码是UTF-8 without BOM。大多数现代编辑器默认即是但Windows记事本可能需要另存为时特别选择。变量名CIRCUITPY_WIFI_SSID和CIRCUITPY_WIFI_PASSWORD是CircuitPython WiFi库识别的固定变量名不要自己改动。其他服务如Adafruit IO的变量名则需要根据对应库的要求来写。创建并保存好settings.toml后重启开发板按复位键或重新插拔USB。CircuitPython启动时会自动读取这个文件并尝试连接WiFi。你可以在code.py里写一段测试代码来验证import wifi import os # 从settings.toml读取网络配置实际上wifi.radio会自动读取这里演示如何手动获取 ssid os.getenv(CIRCUITPY_WIFI_SSID) print(尝试连接SSID:, ssid) # 连接WiFi wifi.radio.connect(os.getenv(CIRCUITPY_WIFI_SSID), os.getenv(CIRCUITPY_WIFI_PASSWORD)) print(已连接到, wifi.radio.ap_info.ssid) print(IP地址:, wifi.radio.ipv4_address)如果看到打印出了IP地址恭喜你开发板已经成功入网。3.2 定位智能灯IP地址与动态发现接下来我们需要知道灯的“门牌号”——IP地址。最直接的方法是通过Wiz手机App查看打开Wiz App进入灯所在的房间。长按灯的设备图标。在弹出的菜单中选择“设置”或类似选项。找到“设备信息”或“关于”里面会列出灯的IP地址。记下这个IP地址比如192.168.1.105。在后续代码中我们会用它来初始化灯的控制对象。但是这里有一个潜在的麻烦大多数家庭路由器默认使用DHCP动态分配IP地址。这意味着灯的IP地址可能会变比如路由器重启后。每次IP变化都去修改代码显然不现实。因此adafruit_wiz库提供了一个更优雅的解决方案网络扫描。扫描功能利用了UDP协议的广播特性。开发板会向局域网内所有设备发送一个特定的询问报文只有Wiz灯会识别并回复在回复中携带自己的MAC地址和当前IP地址。由于MAC地址是硬件唯一且不变的我们可以用它来唯一标识一盏灯即使它的IP变了我们也能通过再次扫描找到它。import wifi from adafruit_wiz import scan # 扫描网络中的所有Wiz灯超时时间设为2秒 found_lights scan(wifi.radio, timeout2) print(f找到了 {len(found_lights)} 盏灯) for light_info in found_lights: ip light_info[ip] mac light_info[result][mac] print(fMAC: {mac}, IP: {ip})运行这段代码你会看到所有在线的Wiz灯信息。把你的目标灯的MAC地址记下来。在自动化脚本中你可以先扫描然后通过MAC地址找到对应的IP再进行控制这样就完全避免了IP变化的困扰。注意事项扫描功能依赖于网络广播某些企业级路由器或设置了复杂VLAN的网络环境可能会禁止广播包导致扫描失败。在普通家庭网络下通常没有问题。如果扫描不到请检查防火墙设置并确保开发板和灯在同一个子网内。4. 核心控制逻辑与代码实现掌握了联网和寻址我们就可以进入最核心的部分发送控制指令。adafruit_wiz库将底层UDP通信和JSON协议封装成了非常直观的Python对象和属性让我们可以用“对话”的方式控制灯。4.1 初始化与基础控制首先你需要通过灯的IP地址和端口创建一个WizConnectedLight对象。端口默认是38899除非你在灯的高级设置里改过。import time import wifi from adafruit_wiz import WizConnectedLight # 替换成你灯的IP地址 LIGHT_IP 192.168.1.105 UDP_PORT 38899 # 初始化灯对象需要传入wifi.radio对象用于网络通信 my_light WizConnectedLight(LIGHT_IP, UDP_PORT, wifi.radio) # 获取灯的当前状态返回一个字典包含开关、亮度、颜色等信息 current_status my_light.status print(当前状态:, current_status) # 基础控制开关、亮度、色温、颜色 my_light.state True # 开灯 time.sleep(1) my_light.state False # 关灯 time.sleep(1) my_light.state True # 再次打开 my_light.brightness 50 # 设置亮度为50%范围1-100 my_light.temperature 2700 # 设置色温为2700K暖黄光范围2200-6500 my_light.rgb_color (0, 255, 0) # 设置RGB颜色为绿色格式为(R, G, B)每个值0-255属性操作的精髓你会发现控制灯就像给一个对象的属性赋值一样简单。库内部帮你处理了所有网络通信和数据格式转换。当你执行my_light.brightness 50时库会构造一个类似{method:setPilot,params:{dimming: 50}}的JSON命令通过UDP发送给灯。4.2 场景模式与高级功能除了基本的颜色和亮度Wiz灯还内置了许多预设场景比如“阅读”、“休闲”、“派对”等。这些场景可能包含了动态的光效。from adafruit_wiz import SCENE_IDS # 查看所有可用的场景名称 print(可用场景:, list(SCENE_IDS.keys())) # 设置场景 my_light.scene Romance # 设置为“浪漫”场景 # 或者使用场景ID my_light.scene_id 14 # 快速切换你可以创建一个场景列表进行轮换 scenes [Party, Ocean, Romance, Reading] current_scene_index 0 # 假设每10秒切换一个场景 while True: my_light.scene scenes[current_scene_index] current_scene_index (current_scene_index 1) % len(scenes) time.sleep(10)SCENE_IDS是一个字典映射了场景名称和其对应的数字ID。不同型号、不同固件版本的灯支持的场景可能略有差异最好先打印出来看看。4.3 构建一个实体按钮遥控器将代码与物理世界交互是嵌入式开发乐趣的来源。我们可以用几个按钮和一个开发板做一个专属的硬件遥控器。硬件连接以ESP32-S2 Feather为例准备4个常开型轻触开关。将每个开关的一端连接到开发板的GND地线。将每个开关的另一端分别连接到开发板的四个GPIO引脚例如D11,D12,A1,A0。在代码中我们需要启用这些引脚的上拉电阻这样当按钮未按下时引脚读到的就是高电平True或1按下时变为低电平False或0。代码实现我们使用CircuitPython的keypad库来高效地管理按钮输入。import board import keypad import wifi from adafruit_wiz import WizConnectedLight # ... WiFi连接和灯光初始化代码同上 ... # 初始化按钮连接到四个GPIO引脚。value_when_pressedFalse表示按下时为低电平。 buttons keypad.Keys( (board.D11, board.D12, board.A1, board.A0), value_when_pressedFalse, pullTrue # 启用内部上拉电阻 ) # 定义颜色和色温的循环列表 colors [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)] # 红、绿、蓝、白 temperatures [2200, 3200, 4500, 6000] # 从暖到冷的色温 color_index 0 temp_index 0 while True: event buttons.events.get() # 获取按钮事件 if event and event.pressed: # 如果事件存在且是按下动作 if event.key_number 0: # 第一个按钮开关切换 my_light.state not my_light.state print(切换开关状态) elif event.key_number 1: # 第二个按钮循环切换颜色 my_light.rgb_color colors[color_index] color_index (color_index 1) % len(colors) print(f切换颜色到 {colors[color_index]}) elif event.key_number 2: # 第三个按钮循环切换色温 my_light.temperature temperatures[temp_index] temp_index (temp_index 1) % len(temperatures) print(f切换色温到 {temperatures[temp_index]}K) elif event.key_number 3: # 第四个按钮激活派对场景 my_light.scene Party print(启动派对模式)keypad库的优势在于它采用非阻塞的事件驱动方式不会在等待按钮时卡住整个程序。buttons.events.get()会立即返回如果有按键事件就处理没有就继续循环这样你可以在主循环里同时做其他事情比如读取传感器。实操心得防抖与长按机械按钮在按下和弹起时会产生物理抖动可能导致一次按压被误判为多次。keypad库内部已经做了基本的防抖处理对于大多数应用足够了。如果你需要实现“长按”功能比如按住2秒关灯可以在代码中记录按钮被按下的时间并在event.released事件中判断按压时长。5. 项目扩展与自动化思路基础控制实现后这个项目的想象力边界才真正被打开。你可以让它从“遥控器”进化成“智能灯光管家”。5.1 基于时间的自动化模拟日出日落让灯光在早晨逐渐亮起模拟日出晚上逐渐变暗模拟日落可以极大地提升生活舒适度。import time import wifi from adafruit_wiz import WizConnectedLight import alarm # 用于深度睡眠和定时唤醒如果硬件支持 # 初始化灯光... def simulate_sunrise(duration_minutes30): 在指定分钟内模拟日出亮度从1%到100%色温从2200K到4500K steps duration_minutes * 60 # 假设每秒调整一次 for i in range(steps): # 线性增加亮度 brightness int(1 (i / steps) * 99) # 线性调整色温从暖黄到自然白 temperature int(2200 (i / steps) * (4500 - 2200)) my_light.brightness brightness my_light.temperature temperature time.sleep(1) # 每秒调整一次 print(日出模拟完成) def simulate_sunset(duration_minutes30): 在指定分钟内模拟日落过程与日出相反 steps duration_minutes * 60 for i in range(steps, 0, -1): brightness int(1 (i / steps) * 99) temperature int(2200 (i / steps) * (4500 - 2200)) my_light.brightness brightness my_light.temperature temperature time.sleep(1) my_light.state False # 日落结束关灯 print(日落模拟完成灯光已关闭) # 在主循环中你可以根据实时时钟RTC来判断当前该执行日出还是日落。 # 需要为开发板配置网络时间NTP或使用外部RTC模块来获取准确时间。5.2 环境响应照明连接传感器结合传感器让灯光成为环境的一部分。光线传感器当环境光变暗时自动打开或调亮灯光。人体红外PIR传感器检测到人移动时开灯一段时间无人后关灯非常适合走廊、卫生间。温湿度传感器用灯光颜色表示当前温度或湿度例如蓝色表示凉爽红色表示温暖。import board import busio import adafruit_apds9960.adafruit_apds9960 as APDS9960 # 接近与光感传感器 from adafruit_wiz import WizConnectedLight # 初始化I2C和传感器 i2c busio.I2C(board.SCL, board.SDA) sensor APDS9960.APDS9960(i2c) sensor.enable_proximity True sensor.enable_light True # 初始化灯光... LIGHT_THRESHOLD 100 # 环境光阈值低于此值则开灯 while True: # 读取环境光强度 ambient_light sensor.light_data print(f环境光: {ambient_light}) if ambient_light LIGHT_THRESHOLD and not my_light.state: print(环境太暗开灯) my_light.state True my_light.brightness 70 my_light.temperature 2700 elif ambient_light LIGHT_THRESHOLD and my_light.state: print(环境足够亮关灯) my_light.state False time.sleep(5) # 每5秒检查一次5.3 集成网络API让灯光知晓世界CircuitPython可以发起HTTP请求这意味着你的灯光可以响应互联网上的数据。天气API根据室外天气改变室内灯光颜色下雨天用蓝白光晴天用暖黄光。日历API在会议开始前5分钟让灯光闪烁提醒你。Webhook接收来自IFTTT、Zapier或自家服务器的指令实现更复杂的联动。import ssl import wifi import socketpool import adafruit_requests # 初始化网络和requests会话 pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool, ssl.create_default_context()) # 获取日落时间示例使用一个公开的API def get_sunset_time(lat, lon): url fhttps://api.sunrise-sunset.org/json?lat{lat}lng{lon}formatted0 try: response requests.get(url) data response.json() sunset_utc data[results][sunset] # 返回ISO格式时间 # 这里需要将UTC时间转换为你所在的时区时间 # ... (时间转换逻辑) return converted_time except Exception as e: print(获取日落时间失败:, e) return None # 在主逻辑中比较当前时间和日落时间决定是否开启夜晚模式。6. 故障排除与优化建议在实际操作中你可能会遇到一些问题。这里整理了一些常见的情况和解决方法。6.1 常见问题速查表问题现象可能原因排查步骤与解决方案开发板无法连接WiFi1.settings.toml格式错误或位置不对。2. WiFi密码错误。3. 网络隐藏了SSID。4. 路由器设置了MAC过滤。1. 检查settings.toml文件名、路径、引号、等号空格。2. 用print(os.getenv(“CIRCUITPY_WIFI_SSID”))确认读取正确。3. 在settings.toml中尝试添加CIRCUITPY_WIFI_SSID b”\x00″仅当SSID隐藏时。4. 检查路由器后台将开发板的MAC地址加入允许列表。代码报错ImportError必需的库文件缺失或版本不匹配。1. 确认lib文件夹内有所需的.mpy文件。2. 从Adafruit官方下载与你的CircuitPython固件版本匹配的库包。控制指令发送但灯无反应1. IP地址错误。2. 开发板和灯不在同一网络。3. 防火墙/路由器设置阻止了UDP端口38899。1. 用手机App重新确认灯的IP或使用scan()函数扫描。2. 确保它们连接到同一个WiFi网络2.4GHz/5GHz需注意有些灯只支持2.4GHz。3. 在路由器设置中检查是否有客户端隔离或AP隔离功能将其关闭。扫描(scan)功能找不到灯1. 网络广播被阻止。2. 扫描超时时间太短。3. 灯处于离线状态。1. 这是企业网络常见问题家庭网络少见。可尝试暂时关闭路由器防火墙测试。2. 增加timeout参数例如scan(wifi.radio, timeout5)。3. 检查灯是否已上电并成功连接WiFi手机App能控制。灯光控制有延迟或卡顿1. WiFi信号弱。2. 代码中time.sleep()阻塞太久。3. 网络拥堵。1. 让开发板和灯离路由器近一些。2. 优化代码避免长时间睡眠。对于按钮检测使用keypad等非阻塞方式。3. 检查网络中是否有大量设备在传输数据。6.2 代码优化与稳定性建议异常处理网络操作是不稳定的一定要添加try-except块。try: my_light.brightness new_value except OSError as e: print(f网络错误设置亮度失败: {e}) # 可以在这里加入重试逻辑状态缓存与减少通信频繁地获取灯的状态my_light.status会产生网络请求。如果不是必要可以在本地缓存一个状态变量只在需要同步时更新。使用asyncio进行并发高级对于需要同时处理传感器、网络请求和灯光控制的复杂项目可以考虑使用CircuitPython的asyncio库。它允许你在单线程内“同时”运行多个任务提高响应效率。电源管理如果是电池供电的项目务必注意功耗。在不需要控制时可以将WiFi radio休眠wifi.radio.enabled False甚至让整个芯片进入深度睡眠alarm.sleep_memory和定时唤醒。这个项目最吸引人的地方在于它用一个简单的协议UDPJSON和一门易学的语言Python将物理硬件开发板、按钮、传感器与智能设备灯连接了起来。它不仅仅是一个灯光控制器更是一个物联网应用的微型样板。当你成功让第一盏灯随着你的代码明灭变色时那种创造和掌控的成就感正是嵌入式开发和硬件编程的魅力所在。从这里出发你可以尝试控制更多品牌的智能设备或者搭建更复杂的家庭自动化中枢乐趣才刚刚开始。