// 导入所有所需库
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include <WiFi.h>
#include <PubSubClient.h>
// ===================== 引脚定义(和你的接线100%对应,不用修改!)=====================
#define LIGHT_LED_PIN 2 // 灯光LED控制引脚
#define AC_LED_PIN 4 // 空调LED控制引脚
#define PIR_PIN 13 // PIR人体红外传感器引脚
#define LIGHT_PIN 35 // 光敏模块AO引脚
#define NTC_OUT_PIN 34 // NTC模块OUT引脚
#define DHT_DATA_PIN 25 // DHT22的SDA数据引脚
// ===================== 设备参数配置 =====================
#define DHT_TYPE DHT22 // 适配你的DHT22传感器
#define LCD_ADDR 0x27 // LCD无显示可替换为0x3F
#define LCD_COL 20 // LCD列数
#define LCD_ROW 4 // LCD行数
// WiFi配置(Wokwi内置测试WiFi,直接用,无需修改)
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// ThingsBoard MQTT配置(已替换为你的专属设备令牌)
const char* TB_MQTT_SERVER = "thingsboard.cloud";
const int TB_MQTT_PORT = 1883;
const char* TB_ACCESS_TOKEN = "r8gHomHwNhug5GVwVMKX";
// ===================== 全局对象初始化 =====================
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COL, LCD_ROW);
DHT dht(DHT_DATA_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);
// ===================== 全局变量 =====================
bool isOccupied = false; // 办公室人员存在状态
float ntcTemperature = 0.0; // NTC模块采集的环境温度(℃)
float humidity = 0.0; // DHT22采集的环境湿度(%RH)
int lightIntensity = 0; // 光敏模块采集的光照强度(0-4095)
unsigned long lastPublishTime = 0;
const long PUBLISH_INTERVAL = 1000; // 数据上报间隔1秒
// ===================== 【新增1】PIR触发保持逻辑变量 =====================
unsigned long pirTriggerTime = 0;
const long PIR_HOLD_TIME = 5000; // 点击一次PIR,保持5秒有人状态
// ===================== 【新增2】远程控制优先逻辑变量 =====================
bool remoteControlOverride = false; // 远程控制覆盖标志位
bool remoteLightState = false; // 远程控制的灯光状态
bool remoteACState = false; // 远程控制的空调状态
// ===================== NTC温度转换函数 =====================
float readNtcTemperature() {
int adcValue = analogRead(NTC_OUT_PIN);
float voltage = adcValue * 3.3 / 4095.0;
float ntcResistance = 10000.0 * (3.3 - voltage) / voltage;
const float B_VALUE = 3950;
const float ROOM_TEMP_K = 25.0 + 273.15;
float tempKelvin = 1 / ( (log(ntcResistance / 10000.0) / B_VALUE) + (1 / ROOM_TEMP_K) );
return tempKelvin - 273.15;
}
// ===================== 边缘AI推理函数:人员存在预测 =====================
bool predictOccupancy(float temp, float humi, int light) {
if (light < 800) {
if (22.0 < temp && temp < 28.0 && 30.0 < humi && humi < 70.0) {
return true;
}
}
return false;
}
// ===================== WiFi连接函数 =====================
void connectWiFi() {
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected successfully");
Serial.println("Device IP address: ");
Serial.println(WiFi.localIP());
}
// ===================== ThingsBoard MQTT重连函数 =====================
void reconnectTB() {
while (!client.connected()) {
Serial.print("Connecting to ThingsBoard MQTT server...");
if (client.connect("ESP32-SmartOffice", TB_ACCESS_TOKEN, NULL)) {
Serial.println("connected successfully");
client.subscribe("v1/devices/me/rpc/request/+");
} else {
Serial.print("failed, error code=");
Serial.print(client.state());
Serial.println(" , retry after 5 seconds");
delay(5000);
}
}
}
// ===================== 【已完善】MQTT消息回调函数(接收平台远程控制指令)=====================
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Received message from topic [");
Serial.print(topic);
Serial.print("]: ");
// 把payload转换成字符串
payload[length] = '\0';
String message = String((char*)payload);
Serial.println(message);
// 解析RPC指令,控制灯光开关
if (message.indexOf("lightState") != -1) {
remoteControlOverride = true; // 【关键】开启远程控制覆盖,锁定本地逻辑
if (message.indexOf("ON") != -1) {
remoteLightState = true;
digitalWrite(LIGHT_LED_PIN, HIGH);
Serial.println(" 灯光已打开(远程控制)");
} else if (message.indexOf("OFF") != -1) {
remoteLightState = false;
digitalWrite(LIGHT_LED_PIN, LOW);
Serial.println(" 灯光已关闭(远程控制)");
}
}
// 解析RPC指令,控制空调开关
if (message.indexOf("acState") != -1) {
remoteControlOverride = true; // 【关键】开启远程控制覆盖,锁定本地逻辑
if (message.indexOf("ON") != -1) {
remoteACState = true;
digitalWrite(AC_LED_PIN, HIGH);
Serial.println(" 空调已打开(远程控制)");
} else if (message.indexOf("OFF") != -1) {
remoteACState = false;
digitalWrite(AC_LED_PIN, LOW);
Serial.println(" 空调已关闭(远程控制)");
}
}
// 执行指令后,上报最新状态
char telemetryJson[256];
snprintf(telemetryJson, sizeof(telemetryJson),
"{\"lightState\":%s,\"acState\":%s}",
digitalRead(LIGHT_LED_PIN) == HIGH ? "\"ON\"" : "\"OFF\"",
digitalRead(AC_LED_PIN) == HIGH ? "\"ON\"" : "\"OFF\""
);
client.publish("v1/devices/me/telemetry", telemetryJson);
}
// ===================== setup初始化函数(开机仅运行一次)=====================
void setup() {
// 串口初始化,波特率115200,用于调试
Serial.begin(115200);
// GPIO引脚模式配置
pinMode(LIGHT_LED_PIN, OUTPUT);
pinMode(AC_LED_PIN, OUTPUT);
pinMode(PIR_PIN, INPUT);
pinMode(LIGHT_PIN, INPUT);
pinMode(NTC_OUT_PIN, INPUT);
// 外设初始化
lcd.init();
lcd.backlight(); // 打开LCD背光
dht.begin(); // 初始化DHT22传感器
// 初始状态关闭所有设备
digitalWrite(LIGHT_LED_PIN, LOW);
digitalWrite(AC_LED_PIN, LOW);
// 连接WiFi与ThingsBoard MQTT服务器
connectWiFi();
client.setServer(TB_MQTT_SERVER, TB_MQTT_PORT);
client.setCallback(callback);
// 开机LCD欢迎界面
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Smart Office System");
lcd.setCursor(0, 1);
lcd.print("System Initialized");
delay(2000);
}
// ===================== loop主循环函数(开机后循环运行)=====================
void loop() {
// 保持MQTT连接,断开自动重连
if (!client.connected()) {
reconnectTB();
}
client.loop();
// 1. 所有传感器数据采集(【已修改】PIR读取逻辑,增加保持时间)
int pirCurrentState = digitalRead(PIR_PIN);
// 如果PIR检测到运动,更新触发时间
if (pirCurrentState == HIGH) {
pirTriggerTime = millis();
// 【新增】PIR再次触发时,切回自动控制模式
if (remoteControlOverride) {
remoteControlOverride = false;
Serial.println("PIR触发,切回自动控制模式");
}
}
// 5秒内都认为有人,避免瞬时触发抓不到
bool pirState = (millis() - pirTriggerTime) < PIR_HOLD_TIME;
lightIntensity = analogRead(LIGHT_PIN);
ntcTemperature = readNtcTemperature();
humidity = dht.readHumidity();
// 2. 边缘AI预测,融合PIR检测结果,提升人员存在判断准确性
bool aiPredictOccupied = predictOccupancy(ntcTemperature, humidity, lightIntensity);
isOccupied = pirState || aiPredictOccupied;
// 3. 【核心已完善】本地闭环控制逻辑(远程控制优先)
if (remoteControlOverride) {
// 【远程控制模式】完全保持远程控制的状态,本地逻辑不覆盖
digitalWrite(LIGHT_LED_PIN, remoteLightState ? HIGH : LOW);
digitalWrite(AC_LED_PIN, remoteACState ? HIGH : LOW);
} else {
// 【自动控制模式】原来的本地逻辑
if (isOccupied) {
// 有人时:光照低于阈值自动开灯,温度高于26℃自动开空调
if (lightIntensity < 1000) {
digitalWrite(LIGHT_LED_PIN, HIGH);
} else {
digitalWrite(LIGHT_LED_PIN, LOW);
}
if (ntcTemperature > 26.0) {
digitalWrite(AC_LED_PIN, HIGH);
} else {
digitalWrite(AC_LED_PIN, LOW);
}
} else {
// 无人时:强制关闭所有设备,实现节能
digitalWrite(LIGHT_LED_PIN, LOW);
digitalWrite(AC_LED_PIN, LOW);
}
}
// 4. LCD本地实时显示
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(ntcTemperature, 1);
lcd.print(" C");
lcd.setCursor(0, 1);
lcd.print("Humi: ");
lcd.print(humidity, 1);
lcd.print(" %");
lcd.setCursor(0, 2);
lcd.print("Light: ");
lcd.print(lightIntensity);
lcd.setCursor(0, 3);
lcd.print("Status: ");
if (remoteControlOverride) {
lcd.print("Remote Ctrl"); // 远程控制模式时显示
} else {
lcd.print(isOccupied ? "Occupied" : "Empty");
}
// 5. 定时向ThingsBoard上报遥测数据
unsigned long now = millis();
if (now - lastPublishTime >= PUBLISH_INTERVAL) {
lastPublishTime = now;
// 构建符合ThingsBoard要求的JSON格式遥测数据
char telemetryJson[256];
snprintf(telemetryJson, sizeof(telemetryJson),
"{\"temperature\":%.2f,\"humidity\":%.2f,\"light\":%d,\"isOccupied\":%s,\"aiPredictOccupied\":%s,\"lightState\":%s,\"acState\":%s}",
ntcTemperature, humidity, lightIntensity,
isOccupied ? "true" : "false",
aiPredictOccupied ? "true" : "false",
digitalRead(LIGHT_LED_PIN) == HIGH ? "\"ON\"" : "\"OFF\"",
digitalRead(AC_LED_PIN) == HIGH ? "\"ON\"" : "\"OFF\""
);
// 发布数据到ThingsBoard MQTT主题
Serial.print("Publish telemetry: ");
Serial.println(telemetryJson);
client.publish("v1/devices/me/telemetry", telemetryJson);
}
delay(100);
}