
简介基于物联网技术的智能照明系统是一份面向物联网爱好者与嵌入式初学者的完整源码项目针对传统照明无法远程控制、缺乏智能调度的实际痛点提供了从Arduino硬件端到Android手机App端的全链路参考实现。压缩包内共8个文件以3个功能头文件(h)与主控制程序(ino)为核心配备可直接安装的Android客户端(apk)、项目报告(pdf)、说明文档(md)以及platformio环境配置(ini)整体仅6.8MB体积小巧且模块划分清晰。该资源目前已有71人学习适合希望快速理解智能家居项目架构、通信链路与云端交互逻辑的开发初学者。通过学习读者能够掌握MQTT协议下的设备连接与远程指令收发方法了解温湿度传感器联动调光的设计细节并能结合PDF报告中的思路与代码注释动手搭建出自己的智能照明系统积累从需求分析、硬件配置到移动端控制的全流程开发经验。1. 智能照明为什么要引入物联网你在办公室用App关掉家里客厅的灯或者在出差路上查看办公室照明状态时传统的墙壁开关和红外遥控器已经完全无法满足这类需求。这个基于物联网的智能照明系统项目把ESP32这类WiFi模组、MQTT消息协议和Android客户端串联成一条完整的控制链路让照明设备从「本地手动」进化为「远程可控、环境联动、策略调度」。它解决的核心问题不是单灯控制而是把分布式照明设备纳入统一管理平面通过温湿度传感器和预设时间表让灯光自己「做决定」。这套源码适合正在做嵌入式毕业设计、物联网入门实践或智能家居产品原型的开发者它保留了完整的PlatformIO工程结构和可控的代码分层比买现成智能灯泡更能理解底层通信逻辑。2. MQTT协议选型与系统通信架构2.1 传统照明方案与物联网方案的对比传统照明控制方案主要依赖三种形态墙壁开关、红外遥控器和定时继电器。墙壁开关的问题在于位置固定要改控制点必须重新布线红外遥控器的有效距离通常不超过10米且穿墙能力差定时继电器虽然能按时间表动作却完全无法感知环境变化。用WiFi直连做控制是多数人首先想到的方案但每台设备都直接建立Socket连接会让服务器端维护成本急剧膨胀而且设备重启后IP变化、连接中断恢复都是麻烦事。HTTP轮询模式虽然实现简单但存在时效性不足和无效请求占用带宽的问题。这里引入MQTT协议核心原因在于它采用发布/订阅模型设备与设备之间不需要知道对方的存在只需要和Broker保持一条长连接即可。控制方案通信模型远程能力实时性并发规模实现复杂度墙壁开关硬接线无即时单点低红外遥控单向红外无10米内单点低HTTP轮询请求/响应需公网端口映射秒级延迟受限于服务器并发中WiFi直连Socket点对点长连接需网关转发毫秒级连接数受限于端口高MQTT发布/订阅Broker中转毫秒级单Broker可承载万级连接中2.2 MQTT通信模型与Topic设计MQTT协议的核心角色有三个Broker消息服务器、发布者和订阅者。设备端只需维护一条到Broker的TCP长连接当状态改变时向对应Topic发布消息订阅了该Topic的其他端立即收到推送。这种解耦让增加新设备、新客户端时不用改服务器端代码天然适合照明系统这种设备种类多、控制端多样的场景。Topic的设计直接决定系统的可扩展性。这套项目里的主题命名遵循层级结构smartlight/{device_id}/cmd # 接收控制指令如 ON / OFF / BRIGHTNESS:80 smartlight/{device_id}/state # 上报设备状态 smartlight/{device_id}/sensor # 上报温度、湿度等环境数据 smartlight/{device_id}/log # 运行日志{device_id}使用设备MAC地址后6位作为唯一标识避免不同楼层、不同房间的设备路由冲突。cmd和state分离的设计值得借鉴——指令是下行通道状态是上行通道两边并发互不干扰。如果后面要接入语音助手或自动化平台只需让它订阅state、发布cmd不需要改动设备固件。2.3 系统数据流设计当用户在Android客户端上点击「开灯」按钮数据流实际经过四个环节App向Broker发布一条消息到smartlight/{device_id}/cmd消息内容是ONBroker根据Topic匹配规则将消息推送给已订阅该Topic的ESP32设备端ESP32收到消息并解析后调用LED驱动接口点亮对应GPIO同时设备端向stateTopic回发一条ON状态供App更新按钮状态回显。数据流里容易忽略的是QoS消息服务质量等级的选择。这套系统里控制指令使用QoS 1较为合理确保消息至少到达一次虽然可能重复投递但开灯指令的幂等性足以容忍重复执行。环境传感器数据上报则用QoS 0即可因为温湿度数据实时性强丢弃旧数据反而比收到延迟数据更有价值。如果全部使用QoS 2Broker和客户端都需维护额外状态机对ESP32这种资源受限设备来说负担明显。3. PlatformIO工程配置与Arduino核心实现3.1 platformio.ini配置解析项目使用PlatformIO作为统一构建环境相比Arduino IDE它的依赖管理、多环境编译和库版本锁定机制更适合源码级交付。打开工程目录下的platformio.ini核心配置如下[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 upload_speed 460800 lib_deps knolleary/PubSubClient^2.9 adafruit/DHT sensor library^1.4.4platform指定了ESP32的官方平台包board定义了板载引脚布局和Flash大小这两者决定编译时的链接脚本。framework arduino表示使用Arduino框架封装层能直接复用ESP32的WIFI库和PubSubClientMQTT库。lib_deps用库作者/库名版本号的形式锁定依赖防止上游库更新导致行为不一致。monitor_speed设置串口监视器波特率必须与固件里Serial.begin()的参数保持一致否则日志输出会是乱码。 注意如果你用的是ESP32-S3或ESP32-C3开发板board 需要改为 esp32-s3-devkitc-1 或 esp32-c3-devkitm-1同时确认 lib_deps 里DHT库的兼容性。不同芯片的ADC采样位数和GPIO数量有差异直接沿用esp32dev配置可能导致编译时引脚定义冲突。 ### 3.2 Led.h驱动PWM调光与调色实现 LED驱动层把底层PWM操作封装成独立类上层业务逻辑不直接操作寄存器。查看 Led.h 头文件可以清晰看到这个设计思路 cpp #ifndef LED_H #define LED_H #include Arduino.h class Led { private: uint8_t gpioPin; // 控制LED的GPIO引脚 uint8_t pwmChannel; // ESP32的PWM通道(0-15) uint8_t brightness; // 当前亮度 0-255 bool state; // 开关状态 public: Led(uint8_t pin, uint8_t channel); void begin(); // 初始化引脚PWM配置 void setBrightness(uint8_t value); // 设置亮度 void on(); void off(); void toggle(); }; #endif构造函数接收pin和channel两个参数pin连接LED的正极或MOS管栅极channel则是ESP32内部LEDC外设的PWM通道号。ESP32共有8个PWM通道复用逻辑需要你在main.ino里创建Led对象时手动分配Led led(2, 0); // GPIO2 连接到 LED使用 PWM 通道 0 void setup() { led.begin(); led.setBrightness(128); // 开机设为 50% 亮度 } void loop() { // 主循环里的业务逻辑只调用对象方法不直接接触寄存器 }setBrightness的实现原理是映射到ledcWriteAPI并写入比较寄存器。ESP32的LEDC分辨率可达13位8192级但为了与APP端的0-100百分比数值对应内部换算时做了缩放。调色则是通过红绿蓝三路PWM独立输出实现的改变三路占空比的比例就能混出不同色温的颜色例如暖白光需要红色通道70%、绿色通道80%、蓝色通道50%的组合。这里建议把调色参数拆成独立结构体而不是硬编码三个数组后续扩展彩光模式会方便很多。3.3 网络层WiFi凭证与MQTT连接管理3.3.1 Wifi_credentials.h 硬编码风险与处理打开Wifi_credentials.h可以看到它统一管理网络连接参数#ifndef WIFI_CREDENTIALS_H #define WIFI_CREDENTIALS_H // WiFi 接入点配置出厂时需修改为实际网络信息 const char* WIFI_SSID IoT_Lighting; const char* WIFI_PASSWORD your_wifi_key_here; // MQTT Broker 地址与端口 const char* MQTT_BROKER 192.168.1.100; const uint16_t MQTT_PORT 1883; // 设备唯一标识默认取MAC后6位 const uint8_t DEVICE_ID[3] {0x11, 0x22, 0x33}; #endif把SSID和密码直接写在头文件里的做法适合开发调试阶段方便快速修改。但交付源码时需要注意WiFi密码以明文形式存储在Flash中固件被导出后可以直接读取。量产或正式部署时建议改用SmartConfig方式即设备首次启动进入配网模式由App把WiFi名和密码通过UDP广播发送给设备避免密码长期固化在固件里。DEVICE_ID若固定写在头文件里则每台设备都要单独编译一次固件更合理的方式是开机时读取芯片内部MAC地址自行生成唯一标识。3.3.2 MQTT连接与回调指令解析void callback(char* topic, byte* payload, unsigned int length) { String message; for (unsigned int i 0; i length; i) { message (char)payload[i]; } // 判断主题以 /cmd 结尾避免误处理设备上行消息 if (String(topic).endsWith(/cmd)) { if (message ON) { led.on(); } else if (message OFF) { led.off(); } else if (message.startsWith(BRIGHTNESS:)) { uint8_t value message.substring(11).toInt(); led.setBrightness(map(value, 0, 100, 0, 255)); } else if (message TOGGLE) { led.toggle(); } } // 状态回传保证App端界面实时同步 if (String(topic).endsWith(/cmd)) { mqtt.publish(smartlight/device_01/state, led.isOn() ? ON : OFF); } }这个回调函数在Broker推送消息时被触发topic参数用来区分消息来源payload是实际消息内容。这里的参数说明BRIGHTNESS:80是包含前缀和数值的字符串substring(11)跳过前缀字符取数值部分toInt()将字符串转为整数再用map函数把APP端约定的0-100百分比映射到0-255的PWM原始值。解析逻辑的容错处理值得注意如果App下发BRIGHTNESS:120即超过100的值map函数会产生超出范围的输出所以应在映射前做constrain(value, 0, 100)钳位操作。loop()里还需要调用mqtt.loop()维持心跳确保掉线后能自动重连。典型实现是当mqtt.connected()返回false时调用mqtt.connect(deviceId, MQTT_USER, MQTT_PASS)并重新订阅Topic。这里MQTT_USER和MQTT_PASS是Broker端创建的用户名和密码直接写死在固件里有被追踪的风险建议在Wifi_credentials.h中单独定义并通过编译宏控制。4. 传感器联动与智能调度策略4.1 温湿度采集与数据转换传感器集成部分是这套系统区别于普通遥控灯的关键能力。工程里引入DHT传感器来感知环境温湿度采集代码嵌入在主循环的定时任务中注意不能阻塞主线程#include DHT.h #define DHTPIN 15 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); void readSensorAndPublish() { float temp dht.readTemperature(); // 读取摄氏温度 float humi dht.readHumidity(); // 读取相对湿度 if (isnan(temp) || isnan(humi)) { Serial.println(Sensor read failed, check wiring!); return; // 读取失败直接返回不发布无效数据 } // 拼接 JSON 格式数据上报开发时可用String拼接简化 String sensorPayload {\temperature\: String(temp, 1) ,\humidity\: String(humi, 1) }; mqtt.publish(smartlight/device_01/sensor, sensorPayload.c_str()); }DHT22在温度和湿度读数上分别有±0.5摄氏度和±2%RH的误差范围这在高精度调节场景里需要留意。readTemperature()返回浮点数String(temp, 1)中的第二个参数1表示保留一位小数避免噪声数据占据Payload空间。isnan()检查必须在发布前执行——DHT传感器在接线松脱或供电不稳时会返回NaN不经过滤直接发布会让云端统计页面出现断崖式的异常曲线。首次上电后DHT传感器需要1至2秒稳定时间建议在setup()中延时1秒后再开始读取。采集有传感器数据之后还要处理上报频率的问题。这里推荐使用非阻塞的定时器比如每30秒上报一次环境数据而不是每轮loop()循环都发。MQTT Broker对消息吞吐量有压力高频率上报日志数据还会让APP端图表渲染卡顿。典型实现是用millis()记录时间差unsigned long lastRead 0; const unsigned long interval 30000; void loop() { if (millis() - lastRead interval) { readSensorAndPublish(); lastRead millis(); } mqtt.loop(); }interval单位是毫秒30000即30秒。编译时如果更换DHT11传感器DHTTYPE必须改为DHT11且读取间隔建议放大到2秒以上否则DHT11会因读取频率过高而输出恒定的误差值。4.2 基于环境阈值的自动照明决策自动照明逻辑由传感器读数驱动核心是一个轻量级决策函数。它把温度、湿度和时间作为输入输出灯光策略。一个典型的决策表如下触发条件动作策略适用场景关键阈值温度 32摄氏度切换到冷白光6500K夏季高温环境利用冷色光提升清醒度32摄氏度温度 18摄氏度切换到暖白光3000K冬季寒冷环境暖色光降低冷清感18摄氏度湿度 70%RH亮度自动降为50%减少高湿度环境下灯光吸引昆虫的几率70%RH夜间22点后亮度降到20%色温调暖避免高亮度抑制人体褪黑素分泌22:00决策函数放在main.ino中实现void autoAdjustByEnvironment(float temp, float humi) { if (temp 32.0f) { setColorTemperature(6500); // 冷白光 } else if (temp 18.0f) { setColorTemperature(3000); // 暖白光 } else { setColorTemperature(4000); // 中性自然光 } }setColorTemperature内部通过三路PWM坐标组合分配RGB占空比实现色温切换。阈值处理的两个细节值得展开一是阈值必须带滞回区间即32摄氏度开冷光、回落到30摄氏度才切回自然光避免温度在临界点波动时灯光反复切换二是条件判断的优先级先判断极端温度再回落中性这保证夏季极端高温下灯光仍处于清凉模式。温湿度阈值和滞回参数建议在Wifi_credentials.h同层级的配置头文件中集中定义便于后续通过MQTT下发动态调整。4.3 时间调度与日出日落策略定时调度在传统照明系统中是通过时钟定时器实现的但物联网设备需要用网络时间同步来保证精度。设备连接WiFi后通过NTP协议获取UTC时间再根据唯经度换算本地时间。基础的时间表可以简化为数组配置typedef struct { uint8_t startHour; // 开始小时 uint8_t startMin; // 开始分钟 uint8_t endHour; // 结束小时 uint8_t endMin; // 结束分钟 uint8_t brightness; // 该时间段内目标亮度 } TimeSlot; TimeSlot schedule[] { {6, 30, 8, 30, 80}, // 早晨唤醒模式 {18, 0, 22, 0, 70}, // 傍晚工作阅读模式 {22, 0, 6, 30, 20}, // 夜间夜灯模式 };TimeSlot结构体把时间区间与亮度值绑定在一起loop()中每分钟获取一次时间遍历数组匹配当前时间处于哪个槽位。日出日落算法并不需要内置天文计算更常见的做法是APP端根据经纬度计算出日出日落时刻通过MQTT主题定时下发SUNSET_TIME指令设备端使用这个自定义时间作为边界避免在MCU上实现复杂的太阳赤纬计算。注意时间调度和传感器决策的优先级关系当两者同时触发且目标冲突时应以最近一次有效交互手动/自动作为当前模式的判定依据避免传感器阈值每10秒就把用户的手动选择覆盖掉。5. 智能照明实测调试与安全加固技巧5.1 用MQTT调试工具验证控制链路拿到源码后不需要先编译烧录可以直接用MQTT调试工具验证Broker联通性和Topic规划。MQTT X和MQTT Explorer这类跨平台桌面工具不需要额外授权输入Broker地址和端口即可连接。连接进去后先订阅#通配符主题观察设备上报的所有消息。设备上电后正常的消息序列应当是设备在log主题打印WiFi连接成功、在state主题上报初始开关状态、在sensor主题周期性上报温湿度。如果这三个消息都正常说明网络层和传感器层已就绪。随后向smartlight/device_01/cmd手动发布一条ON观察设备是否回复STATE消息这一条链路验证通过后再在Android端进行测试才能定位问题在APP侧还是设备侧。5.2 常见的编译与运行时坑位排查清单故障现象可能原因检查方法编译报错提示pubsubclient.h找不到PlatformIO未拉取依赖库执行pio lib install knolleary/PubSubClientWiFi连接成功后MQTT连接失败Broker地址写错或防火墙未放行1883端口电脑上用MQTT X连接同一Broker验证灯不亮但代码逻辑看似正确GPIO引脚接错或PWM通道冲突用万用表测LED引脚电压检查begin()里ledcSetup是否重复绑定同一通道温湿度始终显示NaNDHT引脚接触不良或供电不足串口监视器打印三组原始读数判断是否偶发跳变手机App能搜到设备但控制无响应设备订阅Topic与App发布Topic不一致用调试工具订阅#对比实际消息路由5.3 固件安全加固TLS加密与一机一密这套源码在互联网部署时需要考虑通信链路安全。Broker端启用TLS证书后MQTT_PORT从1883改为8883设备端需要预埋CA证书。PubSubClient库的setSocket方法可接入WiFiClientSecure实例。真正有效的加固方案是「一机一密」每台设备在出厂时烧录独立的ClientID和用户名密码即使某个设备被拆解提取出凭证也无法登录其他设备。配网阶段采用动态Token而非固定密码Token在设备重启后即失效。这样保证了照明系统即使部署到公网远程控制链路也不会被轻易劫持。提示如果只是局域网内部调试1883端口裸连接即可性能更好且配置简单。一旦决定走公网TLS加密就是必须项否则用户名密码在局域网内都会被抓包工具直接读取。本文还有配套的精品资源点击获取