feat: init

This commit is contained in:
yangzhijia
2026-07-22 11:27:37 +08:00
commit 49e3abbb34
23 changed files with 4388 additions and 0 deletions

85
include/alarm_manager.h Normal file
View File

@@ -0,0 +1,85 @@
/**
* @file alarm_manager.h
* @brief 报警管理器 - 双模式 (本地判定 + 服务端指令)
*
* 本地模式: 离线也能用, 跌倒/水浸/心率异常/呼吸异常 本地判定触发
* 服务端模式: 收到 cmd/{gateid}/alarm, alarm/restore 执行联动
*/
#ifndef ALARM_MANAGER_H
#define ALARM_MANAGER_H
#include <Arduino.h>
#include "config.h"
class AlarmManager {
public:
// 告警类型 (本地 + 服务端共用)
enum AlarmType {
ALARM_NONE = 0,
ALARM_FALL, // 跌倒 (本地 + 服务端)
ALARM_WATER_LEAK, // 水浸 (本地 + 服务端)
ALARM_HEART_ABNORMAL, // 心率异常 (本地)
ALARM_BREATH_ABNORMAL, // 呼吸异常 (本地)
ALARM_SERVER, // 服务端下发 (通用)
};
AlarmManager();
~AlarmManager();
bool begin();
void loop();
// ============================================================
// 本地报警接口 (离线可用)
// ============================================================
/** 触发本地报警 */
void triggerLocal(AlarmType type);
/** 清除本地报警 */
void clearLocal(AlarmType type);
/** 清除所有本地报警 */
void clearAllLocal();
// ============================================================
// 服务端指令接口 (协议规范 §5.1, §5.2)
// ============================================================
/** 执行服务端告警指令 */
void executeAlarm(const AlarmCommand& cmd);
/** 执行服务端恢复指令 */
void executeRestore(const AlarmCommand& cmd);
/** 蜂鸣器测试 */
void buzzerTest(uint32_t durationSec);
// ============================================================
// 状态查询 (上报用)
// ============================================================
bool isBuzzerOn() const { return _buzzerOn; }
bool hasActiveAlarm() const { return _activeCount > 0; }
const char* getActiveAlarmType() const;
private:
void _setBuzzer(bool on);
void _setLED(bool on);
void _updateBuzzer();
void _updateLED();
// 蜂鸣器/LED 状态
bool _buzzerOn;
bool _ledOn;
uint32_t _buzzerOffTime; // 定时关闭时刻 (0=手动关)
uint32_t _buzzerToggleTime; // 间歇切换时刻
bool _buzzerBeepState; // 间歇状态 (响/停)
uint8_t _beepCount; // 当前周期哔了几声
// 活动报警列表 (最多 4 个同时)
AlarmType _activeAlarms[4];
uint8_t _activeCount;
// LED 闪烁
uint32_t _lastLEDToggle;
};
#endif // ALARM_MANAGER_H

54
include/button_manager.h Normal file
View File

@@ -0,0 +1,54 @@
/**
* @file button_manager.h
* @brief 三按钮管理器 - 去抖 + 长按/短按识别
*
* 按钮1 (GPIO14): 长按3秒 → 配网
* 按钮2 (GPIO15): 短按 → 解除报警
* 按钮3 (GPIO16): 短按 → 翻页
*/
#ifndef BUTTON_MANAGER_H
#define BUTTON_MANAGER_H
#include <Arduino.h>
#include "config.h"
typedef void (*ButtonCallback)(void);
class ButtonManager {
public:
ButtonManager();
/** 初始化三个按钮引脚 */
void begin();
/** 主循环轮询 (每 10ms 调用) */
void loop();
/** 注册回调 */
void onProvisionLongPress(ButtonCallback cb) { _cbProvision = cb; }
void onAlarmDismiss(ButtonCallback cb) { _cbDismiss = cb; }
void onPageSwitch(ButtonCallback cb) { _cbPage = cb; }
private:
struct ButtonState {
uint8_t pin;
bool pressed; // 当前是否在按下状态
uint32_t pressTime; // 按下时刻 (ms)
bool longTriggered; // 防止长按时重复触发短按
};
/** 处理单个按钮的状态机 */
void _processButton(ButtonState& btn, bool hasShort, bool hasLong);
bool _readPin(uint8_t pin);
ButtonState _btnProvision;
ButtonState _btnDismiss;
ButtonState _btnPage;
ButtonCallback _cbProvision;
ButtonCallback _cbDismiss;
ButtonCallback _cbPage;
};
#endif // BUTTON_MANAGER_H

246
include/config.h Normal file
View File

@@ -0,0 +1,246 @@
/**
* @file config.h
* @brief 全局配置文件 - 五劳无感康养监测系统
*
* 协议版本: V0.08 (2026-07-17)
* 包含: 硬件引脚定义、WiFi/MQTT 默认参数、
* 雷达协议常量、告警阈值、任务配置等
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
// ============================================================
// 1. 硬件引脚定义 (ESP32-S3)
// ============================================================
// --- R60ABD1 毫米波雷达 ---
#define RADAR_UART_PORT Serial2
#define RADAR_RX_PIN 17
#define RADAR_TX_PIN 1 // UART 发送 (从18挪到1)
#define RADAR_BAUD_RATE 115200
// --- 雷达备用 GPIO ---
#define RADAR_GPIO1_PIN 2 // 活跃/静止指示 (从10挪到2)
#define RADAR_GPIO2_PIN 11 // 有人/无人指示
// --- DHT11 温湿度传感器 ---
#define DHT11_PIN 4 // 温湿度传感器
// --- 水浸传感器 ---
#define WATER_SENSOR_PIN 6 // 数字输入, 低电平=有水
// --- 蜂鸣器 (PWM) ---
#define BUZZER_PIN 7
#define BUZZER_PWM_CHANNEL 0
#define BUZZER_PWM_FREQ 2000
#define BUZZER_PWM_RESOLUTION 8
// --- 配网按钮 ---
#define CONFIG_BUTTON_PIN 0 // 长按 3 秒复位 WiFi
#define BUTTON_LONG_PRESS_MS 3000
// --- 状态指示灯 ---
#define LED_WIFI_PIN 8 // 蓝色 - WiFi 状态
#define LED_ALARM_PIN 9 // 红色 - 报警指示
// --- OLED 显示屏 (SSD1306 128x64 I2C) ---
#define OLED_SDA_PIN 14 // I2C 数据线
#define OLED_SCL_PIN 15 // I2C 时钟线
// --- 交互按钮 ---
#define BTN_PROVISION_PIN 16 // 擦除WiFi配网按钮 (长按3秒)
#define BTN_ALARM_DISMISS_PIN 13 // 解除报警按钮
#define BTN_PAGE_SWITCH_PIN 10 // 翻页按钮
#define BTN_DEBOUNCE_MS 50 // 去抖时间
#define BTN_LONG_PRESS_MS 3000 // 长按判定
// ============================================================
// 2. 设备标识 (协议规范 §2)
// ============================================================
#define SALEID_DEFAULT "10100001" // 站点/系统编号
#define GATEID_DEFAULT "10100001001" // 网关唯一编号
#define METER_ID_DEFAULT "ENV001" // 感知节点ID (卧室=ENV001, 卫生间=ENV002, 客厅=ENV003)
#define METER_NAME_DEFAULT "RadarDHT11" // 设备型号名称
#define FW_VERSION "V1.0.0"
// ============================================================
// 3. WiFi 默认配置 (配网使用)
// ============================================================
#define WIFI_AP_SSID "HealthMonitor-Setup"
#define WIFI_AP_PASSWORD "12345678"
#define WIFI_CONFIG_TIMEOUT 300
#define WIFI_CONNECT_TIMEOUT 15
#define WIFI_RETRY_INTERVAL 10000
// ============================================================
// 4. MQTT 默认配置 (协议规范 §2)
// ============================================================
#define MQTT_DEFAULT_SERVER "202.110.234.102"
#define MQTT_DEFAULT_PORT 8000
#define MQTT_DEFAULT_PORT_STR "8000" // WiFiManager 参数用字符串
#define MQTT_DEFAULT_USER "test"
#define MQTT_DEFAULT_PASS "testtest"
#define MQTT_DEFAULT_DEVICE "10100001001" // 默认网关ID = gateid
#define MQTT_DEFAULT_ENV "主卧" // 默认环境备注
#undef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 30 // 30秒 (协议规范)
#define MQTT_RECONNECT_MIN 1000
#define MQTT_RECONNECT_MAX 60000
// ============================================================
// 5. MQTT 主题定义 (协议规范 §3)
// ============================================================
// --- 上行主题 (设备 → 服务端) ---
#define MQTT_TOPIC_REPORT_ALLPOINTS "report/allpoints" // 全量遥测上报 QoS0
#define MQTT_TOPIC_STATUS_ONLINE "status/%s/online" // 上线通知 QoS1
#define MQTT_TOPIC_STATUS_OFFLINE "status/%s/offline" // 离线遗嘱 QoS1
#define MQTT_TOPIC_STATUS_PROVISION "status/%s/provision" // 配网状态 QoS1
#define MQTT_TOPIC_RESPONSE_CMD "response/%s/cmd" // 指令响应 QoS1
#define MQTT_TOPIC_RESPONSE_PROVISION "response/%s/provision" // 配网响应 QoS1
// --- 下行主题 (服务端 → 设备) ---
#define MQTT_TOPIC_CMD_ALARM "cmd/%s/alarm" // 告警指令 QoS1
#define MQTT_TOPIC_CMD_ALARM_RESTORE "cmd/%s/alarm/restore" // 告警恢复 QoS1
#define MQTT_TOPIC_CMD_CONTROL "cmd/%s/control" // 控制指令 QoS1
#define MQTT_TOPIC_CMD_PROVISION_WIFI "cmd/%s/provision/wifi" // WiFi配网 QoS1
// ============================================================
// 6. 上报配置 (协议规范 §4.1, §7.2)
// ============================================================
#define REPORT_INTERVAL_MS 1000 // 遥测上报周期 1秒
#define REPORT_QOS 0 // 遥测 QoS 0
#define STATUS_QOS 1 // 状态/响应 QoS 1
// ============================================================
// 7. 报警阈值 (协议规范 §6, 服务端判定用)
// ============================================================
#define HEART_RATE_MIN 50
#define HEART_RATE_MAX 120
#define BREATH_RATE_MIN 8
#define BREATH_RATE_MAX 24
#define TEMP_LOW 5.0f
#define TEMP_HIGH 45.0f
#define HUMI_LOW 15.0f
#define HUMI_HIGH 90.0f
#define WATER_DEBOUNCE_MS 50
// ============================================================
// 8. 任务配置
// ============================================================
#define RADAR_TASK_STACK 4096
#define RADAR_TASK_PRIORITY 5
#define RADAR_TASK_CORE 0
#define MQTT_TASK_STACK 8192
#define MQTT_TASK_PRIORITY 4
#define MQTT_TASK_CORE 1
// ============================================================
// 9. NTP 时间同步
// ============================================================
#define NTP_SERVER "ntp.aliyun.com"
#define NTP_GMT_OFFSET_SEC 28800 // UTC+8 (北京时间)
#define NTP_DAYLIGHT_OFFSET 0
// ============================================================
// 10. LittleFS 配置文件名
// ============================================================
#define CONFIG_FILE_MQTT "/mqtt_config.json"
#define CONFIG_FILE_WIFI "/wifi_config.json"
#define CONFIG_FILE_DEVICE "/device_config.json"
// ============================================================
// 11. 结构体: 全量遥测数据 (协议规范 §4.1)
// ============================================================
struct TelemetryData {
// --- 设备标识 ---
char saleid[12]; // 站点编号 "10100001"
char gateid[16]; // 网关编号 "10100001001"
char meterId[8]; // 节点ID "ENV001"
char meterName[16]; // 设备型号 "RadarDHT11"
// --- 环境数据 (DHT11) ---
float temp; // 温度 ℃
float humi; // 湿度 %RH
bool dhtValid; // 温湿度有效性
// --- 雷达: 存在/运动 ---
bool presence; // 有人=1 无人=0
bool motion; // 有运动=1 无运动=0
bool fall; // 跌倒检测 1=触发
bool resident; // 驻留状态 1=驻留
// --- 雷达: 生命体征 ---
uint8_t heartRate; // 心率 bpm (无效=0)
uint8_t breathRate; // 呼吸 次/分 (无效=0)
uint8_t body; // 人体能量/体征值
bool radarValid; // 雷达数据有效性
bool vitalValid; // 心率呼吸有效性
// --- 水浸 ---
bool rainDO; // 1=正常 0=水浸触发
bool rainValid; // 水浸传感器有效性
// --- 设备状态 ---
bool wifiReady; // WiFi/MQTT 连接状态
uint32_t uptimeSec; // 设备累计运行秒数
bool buzzerState; // 蜂鸣器状态 1=开启
// --- 时间戳 ---
uint32_t lastUpdate; // 最后更新 ms
};
// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
struct AlarmCommand {
char cmdId[32]; // 指令唯一ID
char type[20]; // alarm / alarm_restore
char alarmType[24]; // fall/water_leak/heart_abnormal/breath_abnormal
char alarmLevel[8]; // low/medium/high
char alarmDesc[64]; // 告警描述
char meterId[8]; // 触发节点ID
char relatedCmdId[32]; // 关联原始告警ID (恢复用)
// 联动动作
bool buzzerOn; // 蜂鸣器开关
uint16_t buzzerDuration; // 蜂鸣持续秒数 (0=持续到恢复)
bool nightLight; // 夜灯
uint32_t timestamp;
};
// 结构体: 下行控制指令 (协议规范 §5.3)
struct ControlCommand {
char cmdId[32];
char type[20]; // control
char controlType[24]; // buzzer_test/device_restart/report_interval_set/factory_reset
uint32_t params_duration; // 参数: 持续秒数
uint32_t params_interval; // 参数: 上报间隔
uint32_t timestamp;
};
// 结构体: 下行配网指令 (协议规范 §5.4)
struct ProvisionCommand {
char cmdId[32];
char wifi_ssid[33];
char wifi_password[65];
char wifi_encrypt[8]; // OPEN/WPA2/WPA3
char mqtt_broker[64];
uint16_t mqtt_port;
bool mqtt_useTls;
uint32_t timestamp;
};
#endif // CONFIG_H

58
include/display_manager.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* @file display_manager.h
* @brief OLED 显示管理器 - SSD1306 128x64 I2C
*
* 4 个显示页面:
* Page 0 - 环境数据 (温湿度 + WiFi)
* Page 1 - 生命体征 (心率 + 呼吸 + 体动)
* Page 2 - 人体状态 (有人/运动/跌倒/驻留)
* Page 3 - 安全信息 (水浸 + 蜂鸣器 + 运行时间)
*/
#ifndef DISPLAY_MANAGER_H
#define DISPLAY_MANAGER_H
#include <Arduino.h>
#include <U8g2lib.h>
#include "config.h"
class DisplayManager {
public:
DisplayManager();
~DisplayManager();
/** 初始化 OLED */
bool begin();
/**
* @brief 更新显示内容 (每 5 秒调用一次即可)
* @param data 遥测数据
* @param wifiOk WiFi 是否已连接
* @param buzzerOn 蜂鸣器是否在响
*/
void update(const TelemetryData& data, bool wifiOk, bool buzzerOn);
/** 切换到下一页 (0→1→2→3→0 循环) */
void nextPage();
/** 获取当前页码 */
uint8_t getPage() const { return _pageIndex; }
private:
void _drawPage0(const TelemetryData& data, bool wifiOk, bool buzzerOn);
void _drawPage1(const TelemetryData& data);
void _drawPage2(const TelemetryData& data);
void _drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk);
/** 格式化秒数为 HH:MM:SS */
String _fmtUptime(uint32_t secs) const;
/** 格式化浮点数为一位小数 */
String _fmtFloat(float val) const;
U8G2_SSD1306_128X64_NONAME_F_HW_I2C _u8g2;
uint8_t _pageIndex;
uint32_t _lastDrawTime;
bool _initialized;
};
#endif // DISPLAY_MANAGER_H

131
include/mqtt_manager.h Normal file
View File

@@ -0,0 +1,131 @@
/**
* @file mqtt_manager.h
* @brief MQTT 通信管理器 (协议规范 V0.08)
*
* 上行: report/allpoints, status/{gateid}/online, status/{gateid}/offline,
* status/{gateid}/provision, response/{gateid}/cmd, response/{gateid}/provision
* 下行: cmd/{gateid}/alarm, cmd/{gateid}/alarm/restore,
* cmd/{gateid}/control, cmd/{gateid}/provision/wifi
*/
#ifndef MQTT_MANAGER_H
#define MQTT_MANAGER_H
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "config.h"
// ============================================================
// 回调函数类型
// ============================================================
// 告警指令回调 (alarm / alarm_restore)
typedef void (*MQTTAlarmCallback)(const AlarmCommand& cmd);
// 控制指令回调
typedef void (*MQTTControlCallback)(const ControlCommand& cmd);
// 配网指令回调 (WiFi 凭证下发)
typedef void (*MQTTProvisionCallback)(const ProvisionCommand& cmd);
// ============================================================
// MQTTManager 类
// ============================================================
class MQTTManager {
public:
MQTTManager();
~MQTTManager();
/** 初始化 */
bool begin(const char* gateid);
/** 主循环 */
void loop();
/** 连接/断开 */
bool connect();
void disconnect();
bool isConnected();
/** 设置服务器 */
void setServer(const char* host, uint16_t port,
const char* user, const char* pass);
// ============================================================
// 上行: 发布方法 (协议规范 §4)
// ============================================================
/** 全量遥测上报 report/allpoints (§4.1) */
void publishAllPoints(const TelemetryData& data, uint32_t sequence);
/** 设备上线通知 status/{gateid}/online (§4.2) */
void publishOnline();
/** 设备离线通知 status/{gateid}/offline (§4.3) — 主动调用 */
void publishOffline();
/** 配网状态通知 status/{gateid}/provision (§4.4) */
void publishProvisionStatus(const char* state, const char* bleName);
/** 指令执行响应 response/{gateid}/cmd (§4.5) */
void publishCmdResponse(const char* cmdId, const char* cmdType,
const char* alarmType, bool success,
const char* errorCode, const char* errorMsg);
/** 配网指令响应 response/{gateid}/provision (§4.6) */
void publishProvisionResponse(const char* cmdId, bool success,
const char* errorCode, const char* errorMsg,
const char* wifiSignal = "");
// ============================================================
// 回调注册 (协议规范 §5)
// ============================================================
void onAlarmCommand(MQTTAlarmCallback cb);
void onControlCommand(MQTTControlCallback cb);
void onProvisionCommand(MQTTProvisionCallback cb);
/** 获取 gateid */
const char* getGateId() const { return _gateid; }
private:
static void _mqttCallback(char* topic, byte* payload, unsigned int length);
static MQTTManager* _instance;
// 主题构建
String _buildTopic(const char* tmpl) const;
// 下行报文解析
void _handleMessage(const char* topic, const char* payload);
void _parseAlarm(const char* payload, bool isRestore);
void _parseControl(const char* payload);
void _parseProvision(const char* payload);
// 发布辅助
bool _publish(const char* topicTmpl, const char* payload, uint8_t qos, bool retain = false);
WiFiClient _wifiClient;
PubSubClient _mqtt;
char _gateid[16];
// 服务器配置
char _server[64];
uint16_t _port;
char _user[32];
char _pass[32];
// 重连
uint32_t _reconnectInterval;
uint32_t _lastReconnectAttempt;
bool _connected;
// 回调
MQTTAlarmCallback _alarmCb;
MQTTControlCallback _controlCb;
MQTTProvisionCallback _provisionCb;
};
#endif // MQTT_MANAGER_H

90
include/radar_manager.h Normal file
View File

@@ -0,0 +1,90 @@
/**
* @file radar_manager.h
* @brief R60ABD1 毫米波雷达管理模块
*/
#ifndef RADAR_MANAGER_H
#define RADAR_MANAGER_H
#include <Arduino.h>
#include "config.h"
#include "radar_protocol.h"
// 雷达原始数据结构
struct RadarRawData {
bool presence; // 有人/无人
bool motion; // 运动 (false=静止)
bool fall; // 跌倒
bool resident; // 驻留
uint8_t heartRate; // 心率
uint8_t breathRate; // 呼吸
uint8_t body; // 体动/能量值
uint16_t distance; // 距离 cm
bool radarValid; // 雷达有效性
bool vitalValid; // 生命体征有效性
uint32_t lastUpdate;
};
typedef void (*RadarCallback)(const RadarRawData& data);
class RadarManager {
public:
RadarManager();
~RadarManager();
bool begin();
void loop();
/** 获取最新数据 */
RadarRawData getData() const;
/** 数据更新回调 */
void onDataUpdate(RadarCallback cb);
/** 发送命令 */
void sendCommand(uint8_t control, uint8_t command,
const uint8_t* data = nullptr, uint16_t len = 0);
void queryInitStatus();
void queryDetectRange();
bool isInitialized() const { return _initialized; }
/** 检查雷达是否超时未收到数据 (>5秒无有效帧) */
bool isTimeout() const {
return (_frameCount > 0) && (millis() - _data.lastUpdate > 5000);
}
/** 雷达数据是否有效 */
bool isDataValid() const { return _data.radarValid; }
private:
void _parseByte(uint8_t byte);
void _resetParser();
bool _validateChecksum();
void _processCompleteFrame();
void _extractPresence(const RadarFrame& frame);
void _extractBreathing(const RadarFrame& frame);
void _extractHeartRate(const RadarFrame& frame);
void _extractSleep(const RadarFrame& frame);
void _extractWorkStatus(const RadarFrame& frame);
void _extractDetectRange(const RadarFrame& frame);
uint8_t _calcChecksum(const uint8_t* data, uint16_t len);
void _notifyUpdate();
HardwareSerial* _serial;
FrameState _state;
RadarFrame _currentFrame;
uint16_t _dataIndex;
RadarRawData _data;
bool _initialized;
RadarCallback _callback;
uint32_t _frameCount;
uint32_t _errorCount;
};
#endif // RADAR_MANAGER_H

114
include/radar_protocol.h Normal file
View File

@@ -0,0 +1,114 @@
/**
* @file radar_protocol.h
* @brief R60ABD1 60G 毫米波雷达通信协议定义
*
* 协议: SIP-S v1.0 串口协议 (云帆瑞达 Micradar)
* 帧格式: 帧头(2B) + 控制字(1B) + 命令字(1B) + 长度(2B) + 数据(NB) + 校验(1B) + 帧尾(2B)
*/
#ifndef RADAR_PROTOCOL_H
#define RADAR_PROTOCOL_H
#include <stdint.h>
// ============================================================
// 帧定界符
// ============================================================
#define FRAME_HEADER_1 0x53 // 'S'
#define FRAME_HEADER_2 0x59 // 'Y'
#define FRAME_TAIL_1 0x54 // 'T'
#define FRAME_TAIL_2 0x43 // 'C'
#define FRAME_HEADER_SIZE 2
#define FRAME_TAIL_SIZE 2
#define FRAME_MIN_SIZE 8 // 最小帧: 头2+控制1+命令1+长度2+校验1+尾2 = 9 字节
// ============================================================
// 控制字 (Control Word)
// ============================================================
enum RadarControl : uint8_t {
CTRL_HEARTBEAT = 0x01, // 心跳包
CTRL_PRODUCT_INFO = 0x02, // 产品信息
CTRL_OTA = 0x03, // OTA 升级
CTRL_WORK_STATUS = 0x05, // 工作状态
CTRL_DETECT_RANGE = 0x07, // 探测范围
CTRL_PRESENCE = 0x80, // 人体存在
CTRL_BREATHING = 0x81, // 呼吸检测
CTRL_SLEEP = 0x84, // 睡眠监测
CTRL_HEART_RATE = 0x85, // 心率监测
};
// ============================================================
// 命令字 (Command Word)
// ============================================================
enum RadarCommand : uint8_t {
CMD_QUERY = 0x01, // 查询命令
CMD_REPORT = 0x02, // 主动上报
CMD_SET = 0x03, // 设置命令
CMD_ACK = 0x04, // 应答
};
// ============================================================
// 工作状态命令 (CTRL_WORK_STATUS 下的子命令)
// ============================================================
enum WorkStatusCmd : uint8_t {
WS_INIT_COMPLETE = 0x01, // 初始化完成
WS_INIT_QUERY = 0x81, // 查询初始化状态
WS_POS_OUT_BOUNDS = 0x07, // 位置超出范围
};
// ============================================================
// 呼吸状态
// ============================================================
enum RespirationStatus : uint8_t {
RESPI_NORMAL = 0, // 正常 (10~25 次/分)
RESPI_HIGH = 1, // 过高 (>25)
RESPI_LOW = 2, // 过低 (<10)
};
// ============================================================
// 睡眠状态
// ============================================================
enum SleepStatus : uint8_t {
SLEEP_AWAKE = 0, // 清醒
SLEEP_LIGHT = 1, // 浅睡
SLEEP_DEEP = 2, // 深睡
};
// ============================================================
// 帧解析状态机
// ============================================================
enum FrameState {
STATE_IDLE, // 等待帧头 0x53
STATE_GOT_HEADER1, // 已收到 0x53, 等待 0x59
STATE_GOT_HEADER2, // 已收到帧头, 等待控制字
STATE_GOT_CONTROL, // 已收到控制字, 等待命令字
STATE_GOT_COMMAND, // 已收到命令字, 等待长度高字节
STATE_GOT_LEN_H, // 已收到长度高字节, 等待长度低字节
STATE_GOT_LEN_L, // 已收到长度低字节, 读取数据
STATE_GOT_DATA, // 数据接收完成, 等待校验
STATE_GOT_CHECKSUM, // 已收到校验, 等待帧尾 0x54
STATE_GOT_TAIL1, // 已收到 0x54, 等待 0x43
STATE_FRAME_COMPLETE, // 帧接收完成
};
// ============================================================
// 帧缓冲区大小
// ============================================================
#define RADAR_FRAME_BUF_SIZE 128 // 单帧最大缓冲区
#define RADAR_RING_BUF_SIZE 512 // 环形接收缓冲区
// ============================================================
// 解析后的雷达帧数据 (原始)
// ============================================================
struct RadarFrame {
uint8_t control;
uint8_t command;
uint16_t data_length;
uint8_t data[RADAR_FRAME_BUF_SIZE];
uint8_t checksum;
bool valid; // 校验通过
uint32_t timestamp;
};
#endif // RADAR_PROTOCOL_H

61
include/sensor_manager.h Normal file
View File

@@ -0,0 +1,61 @@
/**
* @file sensor_manager.h
* @brief 传感器管理器 - DHT11温湿度 + 水浸检测
*/
#ifndef SENSOR_MANAGER_H
#define SENSOR_MANAGER_H
#include <Arduino.h>
#include <DHT.h>
#include "config.h"
#define DHT_TYPE DHT11
typedef void (*SensorCallback)(void); // 数据变化通知
class SensorManager {
public:
SensorManager();
~SensorManager();
/** 初始化 */
bool begin();
/** 读取所有传感器 (定时调用) */
void read();
/** 获取温度 */
float getTemp() const { return _temp; }
/** 获取湿度 */
float getHumi() const { return _humi; }
/** DHT11 有效性 */
bool isDhtValid() const { return _dhtValid; }
/** DHT 是否持续故障 */
bool isDhtFault() const { return _dhtErrorCnt >= 10; }
/** 获取水浸状态 */
bool getRainDO() const { return _rainDO; }
/** 水浸传感器有效性 */
bool isRainValid() const { return _rainValid; }
/** 设置数据变化回调 */
void onChange(SensorCallback cb);
private:
DHT* _dht;
float _temp;
float _humi;
bool _dhtValid;
uint8_t _dhtErrorCnt; // 连续失败计数
bool _rainDO; // 1=正常 0=水浸
bool _rainValid;
bool _lastRainRaw;
uint32_t _rainChangeTime;
SensorCallback _callback;
uint32_t _lastReadTime;
};
#endif // SENSOR_MANAGER_H

132
include/wifi_manager.h Normal file
View File

@@ -0,0 +1,132 @@
/**
* @file wifi_manager.h
* @brief WiFi 配网管理器
*
* 功能:
* - 自动连接已保存的 WiFi
* - 配网失败/首次启动 → AP 模式 + 强制门户页面
* - 自定义参数: MQTT 服务器/端口/用户/密码、设备名称、环境备注
* - 长按按钮重置配置
* - LED 状态指示
* - LittleFS 持久化存储
*/
#ifndef WIFI_MANAGER_H
#define WIFI_MANAGER_H
#include <Arduino.h>
#include <WiFiManager.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "config.h"
// 设备配置结构体
struct DeviceConfig {
char mqtt_server[40];
char mqtt_port[6];
char mqtt_user[20];
char mqtt_pass[20];
char device_id[20];
char env_note[20]; // 环境备注 (如: "主卧", "客厅", "老人房")
};
class WiFiMgmt {
public:
WiFiMgmt();
~WiFiMgmt();
// 单例指针 (用于静态回调)
static WiFiMgmt* _instance;
/**
* @brief 初始化 WiFi 管理器
* @return true=成功
*/
bool begin();
/**
* @brief 主循环, 处理配网流程和按钮检测
* @return true=WiFi 已连接
*/
bool loop();
/**
* @brief WiFi 是否已连接
*/
bool isConnected() const;
/**
* @brief 获取设备配置
*/
DeviceConfig getConfig() const;
/**
* @brief 获取设备 ID (用于 MQTT client_id)
*/
const char* getDeviceId() const;
/**
* @brief 获取环境备注
*/
const char* getEnvNote() const;
/**
* @brief 强制进入配网模式
*/
void startConfigPortal();
/**
* @brief 清除所有配置并重启
*/
void resetSettings();
private:
/**
* @brief 尝试连接 WiFi
*/
bool _connectWiFi();
/**
* @brief 设置 LED 状态
* @param mode 0=慢闪(未连接) 1=快闪(配网) 2=常亮(已连接)
*/
void _setLEDMode(uint8_t mode);
/**
* @brief 检测按钮长按
*/
bool _checkButtonLongPress();
/**
* @brief 加载/保存配置到 LittleFS
*/
bool _loadConfig();
bool _saveConfig();
/**
* @brief 保存 WiFi 参数回调 (WiFiManager 的回调)
*/
static void _saveParamsCallback();
static void _apCallback(WiFiManager* wm);
WiFiManager _wm;
DeviceConfig _config;
bool _connected;
uint32_t _lastCheckTime;
uint32_t _buttonPressTime;
bool _buttonPressed;
// LED 闪烁状态
uint32_t _ledLastToggle;
bool _ledState;
// WiFiManager 自定义参数
WiFiManagerParameter* _param_mqtt_server;
WiFiManagerParameter* _param_mqtt_port;
WiFiManagerParameter* _param_mqtt_user;
WiFiManagerParameter* _param_mqtt_pass;
WiFiManagerParameter* _param_device_id;
WiFiManagerParameter* _param_env_note;
};
#endif // WIFI_MANAGER_H

262
include/wm_strings_zh.h Normal file
View File

@@ -0,0 +1,262 @@
/**
* wm_strings_zh.h
* 中文语言包 for WiFiManager
* 基于 wm_strings_en.h 翻译
*
* WiFiManager, a library for the ESP8266/Arduino platform
* for configuration of WiFi credentials using a Captive Portal
*
* @author Creator tzapu
* @author tablatronix
* @version 0.0.0
* @license MIT
*/
#ifndef _WM_STRINGS_ZH_H_
#define _WM_STRINGS_ZH_H_
// strings files must include a consts file!
#include "wm_consts_en.h" // include constants, tokens, routes
const char WM_LANGUAGE[] PROGMEM = "zh-CN"; // i18n lang code
const char HTTP_HEAD_START[] PROGMEM = "<!DOCTYPE html>"
"<html lang='zh-CN'><head>"
"<meta name='format-detection' content='telephone=no'>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
"<title>{v}</title>";
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){"
"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
"p = l.nextElementSibling.classList.contains('l');"
"document.getElementById('p').disabled = !p;"
"if(p)document.getElementById('p').focus();};"
"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
"</script>";
const char HTTP_HEAD_END[] PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
const char HTTP_ROOT_MAIN[] PROGMEM = "<h1>{t}</h1><h3>{v}</h3>";
const char * const HTTP_PORTAL_MENU[] PROGMEM = {
"<form action='/wifi' method='get'><button>配置 WiFi</button></form><br/>\n", // MENU_WIFI
"<form action='/0wifi' method='get'><button>配置 WiFi (不扫描)</button></form><br/>\n", // MENU_WIFINOSCAN
"<form action='/info' method='get'><button>系统信息</button></form><br/>\n", // MENU_INFO
"<form action='/param' method='get'><button>参数设置</button></form><br/>\n", // MENU_PARAM
"<form action='/close' method='get'><button>关闭页面</button></form><br/>\n", // MENU_CLOSE
"<form action='/restart' method='get'><button>重启设备</button></form><br/>\n", // MENU_RESTART
"<form action='/exit' method='get'><button>退出配网</button></form><br/>\n", // MENU_EXIT
"<form action='/erase' method='get'><button class='D'>清除配置</button></form><br/>\n", // MENU_ERASE
"<form action='/update' method='get'><button>固件升级</button></form><br/>\n", // MENU_UPDATE
"<hr><br/>" // MENU_SEP
};
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "";
const char HTTP_ITEM_QI[] PROGMEM = "<div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i} {h}'></div>";
const char HTTP_ITEM_QP[] PROGMEM = "<div class='q {h}'>{r}%</div>";
const char HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>{qi}{qp}</div>";
const char HTTP_FORM_START[] PROGMEM = "<form method='POST' action='{v}'>";
const char HTTP_FORM_WIFI[] PROGMEM = "<label for='s'>WiFi 名称 (SSID)</label><input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'><br/><label for='p'>WiFi 密码</label><input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' id='showpass' onclick='f()'> <label for='showpass'>显示密码</label><br/>";
const char HTTP_FORM_WIFI_END[] PROGMEM = "";
const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "<hr><br/>";
const char HTTP_FORM_END[] PROGMEM = "<br/><br/><button type='submit'>保存配置</button></form>";
const char HTTP_FORM_LABEL[] PROGMEM = "<label for='{i}'>{t}</label>";
const char HTTP_FORM_PARAM_HEAD[] PROGMEM = "<hr><br/>";
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n";
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>重新扫描</button></form>";
const char HTTP_SAVED[] PROGMEM = "<div class='msg'>正在保存配置<br/>尝试连接 WiFi...<br/>如果连接失败,请重新连接 AP 再试一次</div>";
const char HTTP_PARAMSAVED[] PROGMEM = "<div class='msg S'>配置已保存<br/></div>";
const char HTTP_END[] PROGMEM = "</div></body></html>";
const char HTTP_ERASEBTN[] PROGMEM = "<br/><form action='/erase' method='get'><button class='D'>清除 WiFi 配置</button></form>";
const char HTTP_UPDATEBTN[] PROGMEM = "<br/><form action='/update' method='get'><button>固件升级</button></form>";
const char HTTP_BACKBTN[] PROGMEM = "<hr><br/><form action='/' method='get'><button>返回首页</button></form>";
const char HTTP_STATUS_ON[] PROGMEM = "<div class='msg S'><strong>已连接</strong> {v}<br/><em><small>IP 地址: {i}</small></em></div>";
const char HTTP_STATUS_OFF[] PROGMEM = "<div class='msg {c}'><strong>未连接</strong> {v}{r}</div>";
const char HTTP_STATUS_OFFPW[] PROGMEM = "<br/>WiFi 密码错误";
const char HTTP_STATUS_OFFNOAP[] PROGMEM = "<br/>未找到该 WiFi";
const char HTTP_STATUS_OFFFAIL[] PROGMEM = "<br/>无法连接";
const char HTTP_STATUS_NONE[] PROGMEM = "<div class='msg'>未设置 WiFi</div>";
const char HTTP_BR[] PROGMEM = "<br/>";
const char HTTP_STYLE[] PROGMEM = "<style>"
".c,body{text-align:center;font-family:'PingFang SC','Microsoft YaHei','Helvetica Neue',sans-serif}div,input,select{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box}"
"input,button,select,.msg{border-radius:.3rem;width: 100%}input[type=radio],input[type=checkbox]{width:auto}"
"button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
"input[type='file']{border:1px solid #1fa3ec}"
".wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}"
"a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}"
".q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;float:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;"
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
"background-size: 95px 16px;}}"
".msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}"
"dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}"
"td{vertical-align: top;}"
".h{display:none}"
"button{transition: 0s opacity;transition-delay: 3s;transition-duration: 0s;cursor: pointer}"
"button.D{background-color:#dc3630}"
"button:active{opacity:50% !important;cursor:wait;transition-delay: 0s}"
"body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}"
"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
":disabled {opacity: 0.5;}"
"</style>";
#ifndef WM_NOHELP
const char HTTP_HELP[] PROGMEM =
"<br/><h3>可用页面</h3><hr>"
"<table class='table'>"
"<thead><tr><th>页面</th><th>功能</th></tr></thead><tbody>"
"<tr><td><a href='/'>/</a></td>"
"<td>主菜单页面</td></tr>"
"<tr><td><a href='/wifi'>/wifi</a></td>"
"<td>显示 WiFi 扫描结果并配置连接 (/0wifi 不扫描)</td></tr>"
"<tr><td><a href='/wifisave'>/wifisave</a></td>"
"<td>保存 WiFi 配置并连接设备</td></tr>"
"<tr><td><a href='/param'>/param</a></td>"
"<td>参数设置页面</td></tr>"
"<tr><td><a href='/info'>/info</a></td>"
"<td>系统信息页面</td></tr>"
"<tr><td><a href='/u'>/u</a></td>"
"<td>固件 OTA 升级</td></tr>"
"<tr><td><a href='/close'>/close</a></td>"
"<td>关闭弹出窗口,配网服务保持运行</td></tr>"
"<tr><td>/exit</td>"
"<td>退出配网模式,配网服务将关闭</td></tr>"
"<tr><td>/restart</td>"
"<td>重启设备</td></tr>"
"<tr><td>/erase</td>"
"<td>清除 WiFi 配置并重启。清除后设备不会自动连接,需重新配网</td></tr>"
"</table>"
"<p/>GitHub <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
#else
const char HTTP_HELP[] PROGMEM = "";
#endif
const char HTTP_UPDATE[] PROGMEM = "上传新固件<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>开始升级</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* 如果在配网页面内无法使用,请在浏览器中打开 http://192.168.4.1</a><small>";
const char HTTP_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>升级失败!</strong><Br/>请重启设备后重试</div>";
const char HTTP_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>升级成功!</strong><Br/>设备正在重启...</div>";
#ifdef WM_JSTEST
const char HTTP_JS[] PROGMEM =
"<script>function postAjax(url, data, success) {"
" var params = typeof data == 'string' ? data : Object.keys(data).map("
" function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }"
" ).join('&');"
" var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(\"Microsoft.XMLHTTP\");"
" xhr.open('POST', url);"
" xhr.onreadystatechange = function() {"
" if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }"
" };"
" xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');"
" xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
" xhr.send(params);"
" return xhr;}"
"postAjax('/status', 'p1=1&p2=Hello+World', function(data){ console.log(data); });"
"postAjax('/status', { p1: 1, p2: 'Hello World' }, function(data){ console.log(data); });"
"</script>";
#endif
// Info html
#ifdef ESP32
const char HTTP_INFO_esphead[] PROGMEM = "<h3>ESP32 信息</h3><hr><dl>";
const char HTTP_INFO_chiprev[] PROGMEM = "<dt>芯片版本</dt><dd>{1}</dd>";
const char HTTP_INFO_lastreset[] PROGMEM = "<dt>上次复位原因</dt><dd>CPU0: {1}<br/>CPU1: {2}</dd>";
const char HTTP_INFO_aphost[] PROGMEM = "<dt>AP 主机名</dt><dd>{1}</dd>";
const char HTTP_INFO_psrsize[] PROGMEM = "<dt>PSRAM 大小</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_temp[] PROGMEM = "<dt>芯片温度</dt><dd>{1}°C / {2}°F</dd>";
const char HTTP_INFO_hall[] PROGMEM = "<dt>霍尔传感器</dt><dd>{1}</dd>";
#else
const char HTTP_INFO_esphead[] PROGMEM = "<h3>ESP8266 信息</h3><hr><dl>";
const char HTTP_INFO_fchipid[] PROGMEM = "<dt>Flash 芯片 ID</dt><dd>{1}</dd>";
const char HTTP_INFO_corever[] PROGMEM = "<dt>Core 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_bootver[] PROGMEM = "<dt>Boot 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_lastreset[] PROGMEM = "<dt>上次复位原因</dt><dd>{1}</dd>";
const char HTTP_INFO_flashsize[] PROGMEM = "<dt>实际 Flash 大小</dt><dd>{1} 字节</dd>";
#endif
const char HTTP_INFO_memsmeter[] PROGMEM = "<br/><progress value='{1}' max='{2}'></progress></dd>";
const char HTTP_INFO_memsketch[] PROGMEM = "<dt>内存 - 程序大小</dt><dd>已用 / 总计<br/>{1} / {2} 字节";
const char HTTP_INFO_freeheap[] PROGMEM = "<dt>内存 - 可用堆</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_wifihead[] PROGMEM = "<br/><h3>WiFi 信息</h3><hr>";
const char HTTP_INFO_uptime[] PROGMEM = "<dt>运行时间</dt><dd>{1} 分 {2} 秒</dd>";
const char HTTP_INFO_chipid[] PROGMEM = "<dt>芯片 ID</dt><dd>{1}</dd>";
const char HTTP_INFO_idesize[] PROGMEM = "<dt>Flash 大小</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_sdkver[] PROGMEM = "<dt>SDK 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_cpufreq[] PROGMEM = "<dt>CPU 频率</dt><dd>{1}MHz</dd>";
const char HTTP_INFO_apip[] PROGMEM = "<dt>AP IP 地址</dt><dd>{1}</dd>";
const char HTTP_INFO_apmac[] PROGMEM = "<dt>AP MAC 地址</dt><dd>{1}</dd>";
const char HTTP_INFO_apssid[] PROGMEM = "<dt>AP SSID</dt><dd>{1}</dd>";
const char HTTP_INFO_apbssid[] PROGMEM = "<dt>BSSID</dt><dd>{1}</dd>";
const char HTTP_INFO_stassid[] PROGMEM = "<dt>Station SSID</dt><dd>{1}</dd>";
const char HTTP_INFO_staip[] PROGMEM = "<dt>Station IP</dt><dd>{1}</dd>";
const char HTTP_INFO_stagw[] PROGMEM = "<dt>网关地址</dt><dd>{1}</dd>";
const char HTTP_INFO_stasub[] PROGMEM = "<dt>子网掩码</dt><dd>{1}</dd>";
const char HTTP_INFO_dnss[] PROGMEM = "<dt>DNS 服务器</dt><dd>{1}</dd>";
const char HTTP_INFO_host[] PROGMEM = "<dt>主机名</dt><dd>{1}</dd>";
const char HTTP_INFO_stamac[] PROGMEM = "<dt>Station MAC</dt><dd>{1}</dd>";
const char HTTP_INFO_conx[] PROGMEM = "<dt>连接状态</dt><dd>{1}</dd>";
const char HTTP_INFO_autoconx[] PROGMEM = "<dt>自动连接</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutver[] PROGMEM = "<dt>WiFiManager 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutarduino[] PROGMEM = "<dt>Arduino 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutsdk[] PROGMEM = "<dt>ESP-SDK/IDF 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutdate[] PROGMEM = "<dt>构建日期</dt><dd>{1}</dd>";
// ============================================================
// 文本常量
// ============================================================
const char S_brand[] PROGMEM = "健康监测配网";
const char S_debugPrefix[] PROGMEM = "*wm:";
const char S_y[] PROGMEM = "";
const char S_n[] PROGMEM = "";
const char S_enable[] PROGMEM = "已启用";
const char S_disable[] PROGMEM = "已禁用";
const char S_GET[] PROGMEM = "GET";
const char S_POST[] PROGMEM = "POST";
const char S_NA[] PROGMEM = "未知";
const char S_passph[] PROGMEM = "********";
const char S_titlewifisaved[] PROGMEM = "WiFi 已保存";
const char S_titlewifisettings[] PROGMEM = "设置已保存";
const char S_titlewifi[] PROGMEM = "配置 WiFi";
const char S_titleinfo[] PROGMEM = "系统信息";
const char S_titleparam[] PROGMEM = "参数设置";
const char S_titleparamsaved[] PROGMEM = "设置已保存";
const char S_titleexit[] PROGMEM = "退出配网";
const char S_titlereset[] PROGMEM = "重置";
const char S_titleerase[] PROGMEM = "清除配置";
const char S_titleclose[] PROGMEM = "关闭页面";
const char S_options[] PROGMEM = "选项";
const char S_nonetworks[] PROGMEM = "未发现 WiFi 网络,请点击刷新重新扫描。";
const char S_staticip[] PROGMEM = "静态 IP";
const char S_staticgw[] PROGMEM = "静态网关";
const char S_staticdns[] PROGMEM = "静态 DNS";
const char S_subnet[] PROGMEM = "子网掩码";
const char S_exiting[] PROGMEM = "正在退出配网模式";
const char S_resetting[] PROGMEM = "设备将在几秒后重启";
const char S_closing[] PROGMEM = "可以关闭此页面,配网服务将继续运行";
const char S_error[] PROGMEM = "发生错误";
const char S_notfound[] PROGMEM = "页面未找到\n\n";
const char S_uri[] PROGMEM = "URI: ";
const char S_method[] PROGMEM = "\n请求方法: ";
const char S_args[] PROGMEM = "\n参数: ";
const char S_parampre[] PROGMEM = "param_";
// debug strings
const char D_HR[] PROGMEM = "--------------------";
// softap ssid default prefix
#ifdef ESP8266
const char S_ssidpre[] PROGMEM = "ESP";
#elif defined(ESP32)
const char S_ssidpre[] PROGMEM = "ESP32";
#else
const char S_ssidpre[] PROGMEM = "WM";
#endif
#endif