Compare commits

...

3 Commits

Author SHA1 Message Date
jxh
ecb27790ff # V1.0 功能基本完善 2025-03-21 03:51:20 +08:00
jxh
c388164720 # V0.3 调用网络API查询温湿度数据 2025-03-21 00:43:57 +08:00
jxh
578dcd482a # V0.2 实现数据上传HA 2025-03-20 21:38:43 +08:00

View File

@ -3,6 +3,29 @@
#include <ESP8266WiFiMulti.h> // 多WiFi连接支持 #include <ESP8266WiFiMulti.h> // 多WiFi连接支持
#include <PubSubClient.h> // MQTT客户端 #include <PubSubClient.h> // MQTT客户端
// 定义调试宏
#define DEBUG
#ifdef DEBUG
#define debugPrint(x) Serial.print (x)
#define debugPrintln(x) Serial.println (x)
#else
#define debugPrint(x)
#define debugPrintln(x)
#endif
// 定义SensorData结构体 声明一个SensorData类型的全局变量
struct SensorData
{
float voltage;
float current;
float power;
float temp;
float humidity;
} sensorData;
// 配置
ESP8266WiFiMulti wifiMulti; // 多WiFi连接实例 ESP8266WiFiMulti wifiMulti; // 多WiFi连接实例
WiFiClient espClient; WiFiClient espClient;
@ -15,6 +38,17 @@ void mqtt_callback (char *topic, byte *payload,
void publishSensorData (); // MQTT发布函数 void publishSensorData (); // MQTT发布函数
void reconnectMQTT (); // MQTT重连函数 void reconnectMQTT (); // MQTT重连函数
void checkWifiConnection (); // WiFi连接检查函数 void checkWifiConnection (); // WiFi连接检查函数
void checkMqttConnection (); // MQTT连接检查函数
// bool parseHexJson (String jsonStr); // 解析16进制字符串并返回数据
bool parseCompactData (const char *data); // 解析紧凑型数据并返回数据
bool getNanchangWeather (); // 获取天气信息并解析
// 天气API配置
const char *weatherHost = "restapi.amap.com";
const String API_KEY = "95f833b07d781a2531f5e1516f466515"; // 替换为实际API密钥
const String LOCATION_ADCODE = "360113"; // 城市代码
// MQTT相关配置信息 // MQTT相关配置信息
const char *mqtt_broker_addr = "mqtt.lovelyqi.cn"; // 服务器地址 const char *mqtt_broker_addr = "mqtt.lovelyqi.cn"; // 服务器地址
@ -23,20 +57,8 @@ const char *mqtt_username = "wireless"; // 账号(非必须)
const char *mqtt_password = "charge"; // 密码(非必须) const char *mqtt_password = "charge"; // 密码(非必须)
const uint16_t mqtt_client_buff_size = 4096; // 客户端缓存大小(非必须) const uint16_t mqtt_client_buff_size = 4096; // 客户端缓存大小(非必须)
String mqtt_client_id = "wireless_charge_client"; // 客户端ID String mqtt_client_id = "wireless_charge_client"; // 客户端ID
const char *mqtt_topic_pub = "esp32/wireless_charge/pub"; // 需要发布到的主题 const char *mqtt_topic_pub = "wireless_charger/status"; // 需要发布到的主题
const char *mqtt_topic_sub = "esp32/wireless_charge/sub"; // 需要订阅的主题 const char *mqtt_topic_sub = "wireless_charger/control"; // 需要订阅的主题
// 定义SensorData结构体
struct SensorData
{
float voltage;
float current;
float power;
float temp;
float humidity;
};
SensorData sensorData; // 声明一个SensorData类型的全局变量
// 初始化函数 // 初始化函数
void void
@ -60,40 +82,52 @@ setup ()
mqttClient.setServer (mqtt_broker_addr, mqtt_broker_port); mqttClient.setServer (mqtt_broker_addr, mqtt_broker_port);
// mqttClient.setBufferSize (mqtt_client_buff_size); // mqttClient.setBufferSize (mqtt_client_buff_size);
mqttClient.setCallback (mqtt_callback); mqttClient.setCallback (mqtt_callback);
getNanchangWeather (); // 获取天气信息并解析
} }
void void
loop () loop ()
{ {
unsigned long currentMillis = millis (); // 读取当前时间 // unsigned long currentMillis = millis (); // 读取当前时间
// 1. 处理串口数据接收 // 处理串口数据接收
if (Serial.available () > 0) if (Serial.available () > 0)
{ {
String jsonStr = Serial.readStringUntil ('\n'); String jsonStr = Serial.readStringUntil ('\n');
if (jsonStr.startsWith ("{")) if (jsonStr.startsWith ("{"))
{ {
parseJSON (jsonStr); // parseHexJson (jsonStr);
parseCompactData (jsonStr.c_str ());
}
else
{
Serial.println ("Invalid JSON format");
} }
} }
Serial.println ("Serial read Done");
// 2. 维护MQTT连接 // 维护WiFi连接
if (!mqttClient.connected ()) checkWifiConnection ();
{ // 维护MQTT连接
reconnectMQTT (); // MQTT重连函数 checkMqttConnection ();
}
// mqttClient.loop (); // 维持MQTT心跳
Serial.println ("mqttClient.connected()");
// 3. 定时上传数据(10秒间隔) // 定时上传数据( xxx ms间隔
static unsigned long lastUpload = 0; static unsigned long lastUpload = 0;
if (millis () - lastUpload > 10000) if (millis () - lastUpload > 500)
{ {
publishSensorData (); publishSensorData ();
mqttClient.loop (); // 维持MQTT心跳
lastUpload = millis (); lastUpload = millis ();
} }
// 定时获取温湿度信息( 5 s间隔
static unsigned long lastGetTemp = 0;
if (millis () - lastGetTemp > 5000)
{
getNanchangWeather ();
lastGetTemp = millis ();
}
// 4. 处理低功耗需求 // 4. 处理低功耗需求
#ifdef LOW_POWER_MODE #ifdef LOW_POWER_MODE
ESP.deepSleep (30e6); // 30秒深度睡眠 ESP.deepSleep (30e6); // 30秒深度睡眠
@ -105,7 +139,7 @@ loop ()
void void
parseJSON (String jsonStr) parseJSON (String jsonStr)
{ {
StaticJsonDocument<256> doc; DynamicJsonDocument doc (256); // 动态分配内存
DeserializationError error = deserializeJson (doc, jsonStr); DeserializationError error = deserializeJson (doc, jsonStr);
if (!error) if (!error)
@ -120,6 +154,10 @@ parseJSON (String jsonStr)
if (sensorData.voltage < 4.5) if (sensorData.voltage < 4.5)
Serial.println ("电压异常!"); Serial.println ("电压异常!");
} }
else
{
Serial.println ("JSON解析失败");
}
} }
// 串口中断接收 // 串口中断接收
@ -128,7 +166,7 @@ serialEvent ()
{ {
String jsonData = Serial.readStringUntil ('\n'); String jsonData = Serial.readStringUntil ('\n');
if (jsonData.startsWith ("{")) if (jsonData.startsWith ("{"))
parseJSON (jsonData); parseCompactData (jsonData.c_str ());
} }
// MQTT消息回调函数该函数会在PubSubClient对象的loop方法中被调用 // MQTT消息回调函数该函数会在PubSubClient对象的loop方法中被调用
@ -148,7 +186,8 @@ mqtt_callback (char *topic, byte *payload, unsigned int length)
void void
publishSensorData () publishSensorData ()
{ {
StaticJsonDocument<200> doc; DynamicJsonDocument doc (256); // 动态分配内存
doc["voltage"] = sensorData.voltage; doc["voltage"] = sensorData.voltage;
doc["current"] = sensorData.current; doc["current"] = sensorData.current;
doc["power"] = sensorData.power; doc["power"] = sensorData.power;
@ -159,7 +198,7 @@ publishSensorData ()
char payload[256]; char payload[256];
serializeJson (doc, payload); serializeJson (doc, payload);
if (mqttClient.publish ("homeassistant/charger/state", payload)) if (mqttClient.publish (mqtt_topic_pub, payload))
{ {
Serial.println ("Data published"); Serial.println ("Data published");
} }
@ -179,7 +218,8 @@ reconnectMQTT ()
delay (100); delay (100);
if (mqttClient.connected ()) // 每个客户端需要有唯一的ID不然上线时会把其他相同ID的客户端踢下线 if (mqttClient
.connected ()) // 每个客户端需要有唯一的ID不然上线时会把其他相同ID的客户端踢下线
{ {
Serial.println ("MQTT Connected"); Serial.println ("MQTT Connected");
mqttClient.publish (mqtt_topic_pub, mqttClient.publish (mqtt_topic_pub,
@ -201,3 +241,190 @@ checkWifiConnection ()
WiFi.reconnect (); WiFi.reconnect ();
} }
} }
// MQTT连接检查
void
checkMqttConnection ()
{
if (!mqttClient.connected ())
{
reconnectMQTT (); // MQTT重连函数
}
}
// 非标准JSON解析函数
// 十六进制转换器
uint16_t
hex4ToUint (const char *p)
{
uint16_t val = 0;
for (uint8_t i = 0; i < 4; i++)
{
val <<= 4;
if (*p >= '0' && *p <= '9')
{
val |= *p - '0';
}
else if (*p >= 'A' && *p <= 'F')
{
val |= *p - 'A' + 10;
}
p++;
}
return val;
}
// // 输入示例:{"vol":1069,"cur":0037,"pwr":00E7,"tmp":0CA2,"hum":0BF6}
// bool
// parseHexJson (String jsonStr)
// {
// // 键名位置查找(基于固定结构优化)
// const char *key_pos[5] = {
// strstr (jsonStr.c_str (), "\"vol\":"), // 电压位置
// strstr (jsonStr.c_str (), "\"cur\":"), // 电流位置
// strstr (jsonStr.c_str (), "\"pwr\":"), // 功率位置
// strstr (jsonStr.c_str (), "\"tmp\":"), // 温度位置
// strstr (jsonStr.c_str (), "\"hum\":") // 湿度位置
// };
// // 校验键名完整性
// for (uint8_t i = 0; i < 5; i++)
// {
// if (!key_pos[i])
// return false;
// key_pos[i] += 5; // 跳过键名部分(如"vol":
// }
// // 逐字段解析固定4字节长度
// sensorData.voltage = hex4ToUint (key_pos[0]) / 100;
// Serial.println (key_pos[0]);
// Serial.println (hex4ToUint ("109A"));
// sensorData.current = hex4ToUint (key_pos[1]) / 100;
// sensorData.power = hex4ToUint (key_pos[2]) / 100;
// Serial.println (sensorData.power);
// sensorData.temp = hex4ToUint (key_pos[3]) / 100;
// sensorData.humidity = hex4ToUint (key_pos[4]) / 100;
// return true;
// }
// 接收端解析函数(返回解析成功状态)
bool
parseCompactData (const char *data)
{
// 格式验证长度26字节{xxxx,xxxx,xxxx,xxxx,xxxx}
if (data[0] != '{' || data[25] != '}')
{
Serial.println ("Failed to parse data, invalid format.");
return false;
}
// 分割缓冲区初始化
char segment[5] = { 0 }; // 4字符+终止符
uint16_t params[5]; // 存储解析出的五个参数
uint8_t paramIndex = 0;
uint8_t charIndex = 0;
// 逐字符解析1-24字符区间
for (uint16_t i = 1; i < 25; i++)
{
if (data[i] == ',' || i == 24)
{ // 分隔符或结尾触发转换
params[paramIndex++] = strtoul (segment, NULL, 16);
memset (segment, 0, 5);
charIndex = 0;
}
else
{
segment[charIndex++] = data[i];
}
}
// 检查是否成功解析了五个参数
if (paramIndex != 5)
{
Serial.println ("Failed to parse data, not enough parameters.");
return false;
}
// 将解析出的数据按顺序赋值给全局的sensorData结构体
sensorData.voltage = params[0] / 100.0f;
sensorData.current = params[1] / 100.0f;
sensorData.power = params[2] / 100.0f;
// sensorData.temp = params[3] / 100.0f;
// sensorData.humidity = params[4] / 100.0f;
// Serial.println (sensorData.voltage);
// Serial.println (sensorData.current);
// Serial.println (sensorData.power);
// Serial.println (sensorData.temp);
// Serial.println (sensorData.humidity);
return true;
}
// 封装函数:获取南昌温湿度
bool
getNanchangWeather ()
{
WiFiClient client;
const int httpPort = 80;
if (!client.connect (weatherHost, httpPort))
{
Serial.println ("Connection failed");
return false;
}
// 构建API请求URL
String url = "/v3/weather/weatherInfo?key=" + API_KEY
+ "&city=" + LOCATION_ADCODE + "&extensions=base";
// 发送HTTP请求
client.print (String ("GET ") + url + " HTTP/1.1\r\n" + "Host: "
+ weatherHost + "\r\n" + "Connection: close\r\n\r\n");
// 等待响应
unsigned long timeout = millis ();
while (client.available () == 0)
{
if (millis () - timeout > 500)
{
client.stop ();
return false;
}
}
// 读取HTTP响应头
while (client.available ())
{
String line = client.readStringUntil ('\n');
if (line == "\r")
{
break;
}
}
// client.readStringUntil ('{');
// String src = client.readStringUntil ('}');
// Serial.println (src);
// 解析JSON数据
DynamicJsonDocument doc (1024);
DeserializationError error = deserializeJson (doc, client);
if (error)
{
Serial.print ("JSON解析失败: ");
Serial.println (error.c_str ());
return false;
}
// 提取温湿度数据
JsonObject results_0 = doc["lives"][0];
sensorData.temp = results_0["temperature_float"];
sensorData.humidity = results_0["humidity_float"];
client.stop ();
return true;
}