从机端
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "esp_camera.h"
#include "esp_heap_caps.h"
#include <freertos/queue.h>
#include <Preferences.h>
#include <WiFiUdp.h>
#include <stdarg.h>

#if 1
// ===== UDP通信类（is_initialized 改回普通成员 + 新增访问方法）=====
class UDPPeer {
private:
  WiFiUDP udp;
  bool ready = false;
  uint16_t local_port = 8888;
  
  // 双缓存
  String receivedRaw;
  String parsedValue;
  
  // 连接参数
  const char* ssid = nullptr;
  const char* password = nullptr;
  IPAddress local_ip;
  IPAddress gateway;
  IPAddress subnet;
  
  // ✅ 监视器IP最后一位
  uint8_t monitor_ip_last = 101;
  
  // ✅ 改回普通成员变量（非静态）
  bool is_initialized = false;
  
  uint32_t last_maintenance = 0;
  const uint32_t MAINTENANCE_INTERVAL = 1000;

  void autoMaintain() {
    if (!is_initialized) return;
    
    if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
       last_maintenance = millis();
       //Serial.printf("testA");
       
    if (WiFi.status() != WL_CONNECTED) {      
      WiFi.config(local_ip, gateway, subnet);
      if (!ready)
      {
        Serial.printf("正在启动网络连接！！！\n"); 
        WiFi.begin(ssid, password);
        delay(1000);
      }
      else
      {
       Serial.printf("网络中断，恢复中！！！\n"); 
       WiFi.reconnect();
      }
      //last_maintenance = 0;
      return;
    }
      //Serial.printf("testB");

    if (!ready) {
      ready = (udp.begin(local_port) == 1);
      if (ready) {
        Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n", 
                      WiFi.localIP().toString().c_str(), local_port);
      } else {
        //last_maintenance = 0;
        return;
      }
    }
    
    //last_maintenance = millis();
  }

public:
  // ✅ 新增：访问 is_initialized 的成员方法
  bool isInitialized() {
    return is_initialized;
  }
  
  // ✅ 设置监视器IP
  void setMonitorIPLast(uint8_t last_octet) {
    monitor_ip_last = last_octet;
  }
  
  // ✅ 获取完整监视器IP
  String getMonitorIP() {
    return String(gateway[0]) + "." + String(gateway[1]) + "." + 
           String(gateway[2]) + "." + String(monitor_ip_last);
  }

  bool connect(const char* _ssid, const char* _pwd,
               uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
               uint8_t local_ip_last, uint16_t port = 8888) {
    WiFi.disconnect(true);
    last_maintenance = 0;
    ready = false;

    ssid = _ssid;
    password = _pwd;
    local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
    gateway = IPAddress(gw1, gw2, gw3, gw4);
    subnet = IPAddress(255, 255, 255, 0);
    local_port = port;
    is_initialized = true;  // ✅ 普通成员赋值
    
    Serial.printf("📶 wifi：%s， pwd：%s，配置网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n", 
                  ssid, password, gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
    Serial.println("💡 网络将在后台自动连接...");
    
    autoMaintain();
    return true;
  }

  bool hasMessage() {
    autoMaintain();
    if (!is_initialized || !ready) return false;
    
    receivedRaw = "";
    parsedValue = "";
    
    int len = udp.parsePacket();
    if (len <= 0 || len > 512) return false;
    
    char buf[513];
    int read = udp.read((uint8_t*)buf, min(len, 512));
    if (read > 0) {
      buf[read] = '\0';
      receivedRaw = String(buf);
      return true;
    }
    return false;
  }

  bool getMessageValue(const String& key) {
    parsedValue = "";
    if (receivedRaw.length() == 0) return false;
    
    String keyPattern = "\"" + key + "\"";
    int startIndex = receivedRaw.indexOf(keyPattern);
    if (startIndex == -1) return false;
    
    int colonIndex = receivedRaw.indexOf(":", startIndex);
    if (colonIndex == -1) return false;
    
    int valueStart = colonIndex + 1;
    while (valueStart < receivedRaw.length() && 
           receivedRaw.charAt(valueStart) == ' ') valueStart++;
    
    if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
      int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
      if (valueEnd != -1) {
        parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
        return true;
      }
    } else {
      int valueEnd = valueStart;
      while (valueEnd < receivedRaw.length() &&
             receivedRaw.charAt(valueEnd) != ',' &&
             receivedRaw.charAt(valueEnd) != '}' &&
             receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
      while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
      if (valueEnd > valueStart) {
        parsedValue = receivedRaw.substring(valueStart, valueEnd);
        return true;
      }
    }
    return false;
  }

  bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
    autoMaintain();
    if (!is_initialized || !ready) return false;
    
    IPAddress target_ip;
    if (!target_ip.fromString(target_ip_str)) return false;
    
    char content[256];
    va_list args;
    va_start(args, format);
    vsnprintf(content, sizeof(content) - 1, format, args);
    va_end(args);
    
    char json[300];
    snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
    
    return udp.beginPacket(target_ip, local_port) && 
           udp.write((const uint8_t*)json, strlen(json)) && 
           udp.endPacket();
  }

  bool sendTo(const String& target_ip_str, const String& key, const String& value) {
    return sendTo(target_ip_str, key, "%s", value.c_str());
  }

  String lastValue() { return parsedValue; }
  String lastMessage() { return receivedRaw; }
  String getLocalIP() { 
    autoMaintain();
    return is_initialized ? WiFi.localIP().toString() : "未配置"; 
  }
  bool isConnected() { 
    autoMaintain();
    return is_initialized && ready; 
  }
};

extern UDPPeer peer;
UDPPeer peer;

// ===== 默认配置 =====
#define DEFAULT_SSID "ESP32-Camera"
#define DEFAULT_PWD "12345678"
#define DEFAULT_GW1 192
#define DEFAULT_GW2 168
#define DEFAULT_GW3 4
#define DEFAULT_GW4 1
#define DEFAULT_LOCAL_LAST 100
#define DEFAULT_MONITOR_LAST 101

// ===== Flash配置管理 =====
void loadConfigFromFlash() {
  Preferences prefs;
  if (!prefs.begin("cam_cfg", true)) {  // 只读模式
    Serial.println("⚠️  Flash配置不存在，使用默认值");
    peer.connect(DEFAULT_SSID, DEFAULT_PWD, 
                 DEFAULT_GW1, DEFAULT_GW2, DEFAULT_GW3, DEFAULT_GW4, 
                 DEFAULT_LOCAL_LAST);
    peer.setMonitorIPLast(DEFAULT_MONITOR_LAST);
    prefs.end();
    return;
  }
  
  // 读取配置
  String ssid = prefs.getString("ssid", DEFAULT_SSID);
  String pwd = prefs.getString("pwd", DEFAULT_PWD);
  uint8_t gw1 = prefs.getUChar("gw1", DEFAULT_GW1);
  uint8_t gw2 = prefs.getUChar("gw2", DEFAULT_GW2);
  uint8_t gw3 = prefs.getUChar("gw3", DEFAULT_GW3);
  uint8_t gw4 = prefs.getUChar("gw4", DEFAULT_GW4);
  uint8_t local_last = prefs.getUChar("local", DEFAULT_LOCAL_LAST);
  uint8_t monitor_last = prefs.getUChar("monitor", DEFAULT_MONITOR_LAST);
  
  prefs.end();
  
  // 应用配置
  peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
  peer.setMonitorIPLast(monitor_last);
  
  Serial.println("✅ 从Flash加载配置成功");
}

void saveConfigToFlash(const char* ssid, const char* pwd,
                      uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
                      uint8_t local_last, uint8_t monitor_last) {
  Preferences prefs;
  if (!prefs.begin("cam_cfg", false)) {  // 读写模式
    Serial.println("❌ Flash配置保存失败");
    return;
  }
  
  prefs.putString("ssid", ssid);
  prefs.putString("pwd", pwd);
  prefs.putUChar("gw1", gw1);
  prefs.putUChar("gw2", gw2);
  prefs.putUChar("gw3", gw3);
  prefs.putUChar("gw4", gw4);
  prefs.putUChar("local", local_last);
  prefs.putUChar("monitor", monitor_last);
  
  prefs.end();
  Serial.println("✅ 配置已保存到Flash");
}

// ===== 串口指令解析 =====
void handleSerialCommand() {
  if (!Serial.available()) return;
  
  String cmd = Serial.readStringUntil('\n');
  cmd.trim();
  
  if (!cmd.startsWith("wificar:")) 
  {
    if(cmd.endsWith("LZU")||cmd.endsWith("LZU\r"))
    {
       peer.sendTo(peer.getMonitorIP(), "f", cmd);
      Serial.printf("📤 已发送到 %s: {\"f\":%s}\n", peer.getMonitorIP().c_str(), cmd);
    }
    return;
  }  
  // 解析格式: wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last
  // 示例: wificar:MyCam:secure123:192:168:43:1:100:101
  int pos[7];  // 存储6个冒号位置
  int count = 0;
  for (int i = 8; i < cmd.length() && count < 7; i++) {  // 从"wificar:"后开始
    if (cmd.charAt(i) == ':') {
      pos[count++] = i;
    }
  }
  
  if (count < 7) {
    Serial.println("❌ 指令格式错误，应为: wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last");
    return;
  }
  
  String ssid = cmd.substring(8, pos[0]);
  String pwd = cmd.substring(pos[0] + 1, pos[1]);
  uint8_t gw1 = cmd.substring(pos[1] + 1, pos[2]).toInt();
  uint8_t gw2 = cmd.substring(pos[2] + 1, pos[3]).toInt();
  uint8_t gw3 = cmd.substring(pos[3] + 1, pos[4]).toInt();
  uint8_t gw4 = cmd.substring(pos[4] + 1, pos[5]).toInt();
  uint8_t local_last = cmd.substring(pos[5] + 1, pos[6]).toInt();
  uint8_t monitor_last = cmd.substring(pos[6] + 1).toInt();
  
  // 应用新配置
  peer.connect(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last);
  peer.setMonitorIPLast(monitor_last);
  
  // 保存到Flash
  saveConfigToFlash(ssid.c_str(), pwd.c_str(), gw1, gw2, gw3, gw4, local_last, monitor_last);
  
  Serial.println("✅ WiFi配置已更新并保存");
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("\n╔════════════════════════════════════════════════════════════════╗");
  Serial.println("║  ESP32-S3 摄像头 UDP 通信系统 v1.0                          ║");
  Serial.println("║  支持Flash配置存储 | 串口动态配置 | 自动重连                ║");
  Serial.println("╚════════════════════════════════════════════════════════════════╝");
  Serial.println("💡 串口指令:wificar:ssid:pwd:gw1:gw2:gw3:gw4:local_last:monitor_last");
  Serial.println("   示例: wificar:MyCam:secure123:192:168:43:1:100:101\n");
  
  // 从Flash加载配置（自动回退到默认值）
  loadConfigFromFlash();
}

void loop() {
  // 处理串口配置指令
  handleSerialCommand();
  
  // ✅ 使用成员方法读取 is_initialized 状态
  if (peer.isInitialized()) {
    // 每2秒发送一次消息到监视器
    // static uint32_t last_send = 0;
    // if (millis() - last_send >= 2000) {
    //   last_send = millis();
    //   peer.sendTo(peer.getMonitorIP(), "f", "device");
    //   Serial.printf("📤 已发送到 %s: {\"f\":\"start\"}\n", peer.getMonitorIP().c_str());
    // }
    
    // 每5ms检查一次消息
    static uint32_t last_check = 0;
    if (millis() - last_check >= 6) {
      last_check = millis();
      
      if (peer.hasMessage()) {
        if (peer.getMessageValue("c")) {
          Serial.printf("📥 收到消息: %sZHY\n", peer.lastValue().c_str());
        }
      }
    }
  }
  
  delay(1);  // 避免忙等待
}
#else

主机端
#include <WiFi.h>
#include <WiFiUdp.h>
#include <stdarg.h>

class UDPPeer {
private:
  WiFiUDP udp;
  bool ready = false;
  uint16_t local_port = 8888;
  
  // 双缓存
  String receivedRaw;
  String parsedValue;
  
  // 连接参数
  const char* ssid = nullptr;
  const char* password = nullptr;
  IPAddress local_ip;
  IPAddress gateway;
  IPAddress subnet;
  
  // ✅ 新增：AP模式标志（默认false=STA模式）
  bool is_ap_mode = false;
  
  // ✅ ✅ ✅ 新增：网络配置缓存（6行） ✅ ✅ ✅
  String current_ssid = "";
  String current_password = "";
  uint8_t cached_gw1 = 0, cached_gw2 = 0, cached_gw3 = 0, cached_gw4 = 0;
  
  // 状态标志
  bool is_initialized = false;
  uint32_t last_maintenance = 0;
  const uint32_t MAINTENANCE_INTERVAL = 1000;

  // ✅ 修正后的轻量维护（AP模式特殊处理）
  void autoMaintain() {
    if (!is_initialized) return;
    
    if (millis() - last_maintenance < MAINTENANCE_INTERVAL) return;
     last_maintenance = millis();

    // ✅ AP模式：无需维护WiFi（始终在线），仅确保UDP就绪
    if (is_ap_mode) {
      if (!ready) {
        ready = (udp.begin(local_port) == 1);
        if (ready) {
          Serial.printf("✅ AP模式UDP就绪: 端口=%d\n", local_port);
        }
      }
      //last_maintenance = millis();
      return;
    }
    
    if (WiFi.status() != WL_CONNECTED) {    
      WiFi.config(local_ip, gateway, subnet);
      if (!ready)
      {
        Serial.printf("start local net正在启动网络连接！！！\n"); 
        WiFi.begin(ssid, password);
        delay(1000);
      }
      else
      {
       Serial.printf("reconnect网络中断，恢复中！！！\n"); 
       WiFi.reconnect();
      }
      //last_maintenance = 0;
      return;
    }
    
    if (!ready) {
      ready = (udp.begin(local_port) == 1);
      if (ready) {
        Serial.printf("✅ 网络就绪: IP=%s 端口=%d\n", 
                      WiFi.localIP().toString().c_str(), local_port);
      } else {
        //last_maintenance = 0;
        return;
      }
    }
    
    //last_maintenance = millis();
  }

public:
  // ✅ STA模式连接（末尾添加配置缓存 - 6行）
  bool connect(const char* _ssid, const char* _pwd,
               uint8_t gw1, uint8_t gw2, uint8_t gw3, uint8_t gw4,
               uint8_t local_ip_last, uint16_t port = 8888) {
    // 切换到STA模式
    WiFi.mode(WIFI_STA);
    WiFi.disconnect(true);
    last_maintenance = 0;
    ready = false;
    ssid = _ssid;
    password = _pwd;
    local_ip = IPAddress(gw1, gw2, gw3, local_ip_last);
    gateway = IPAddress(gw1, gw2, gw3, gw4);
    subnet = IPAddress(255, 255, 255, 0);
    local_port = port;
    is_initialized = true;
    is_ap_mode = false;
    
    // ✅ ✅ ✅ 新增：缓存网络配置 ✅ ✅ ✅
    current_ssid = String(_ssid);
    current_password = String(_pwd);
    cached_gw1 = gw1;
    cached_gw2 = gw2;
    cached_gw3 = gw3;
    cached_gw4 = gw4;
    
    Serial.printf("📶 配置STA网络: IP=%d.%d.%d.%d 网关=%d.%d.%d.%d 端口=%d\n", 
                  gw1, gw2, gw3, local_ip_last, gw1, gw2, gw3, gw4, port);
    Serial.println("💡 网络将在后台自动连接...");
    
    autoMaintain();
    return true;
  }

  // ✅ ✅ ✅ AP模式连接（末尾添加配置缓存 - 6行）✅ ✅ ✅
  bool connectAP(const char* _ssid, const char* _pwd, uint16_t port = 8888) {
    WiFi.disconnect(true);
    WiFi.mode(WIFI_AP);
    last_maintenance = 0;
    ready = false;
    WiFi.softAPConfig(IPAddress(192, 168, 4, 1), 
                      IPAddress(192, 168, 4, 1), 
                      IPAddress(255, 255, 255, 0));
    
    if (!WiFi.softAP(_ssid, _pwd, 4, false, 4)) {
      Serial.println("❌ AP启动失败");
      return false;
    }

    local_port = port;
    ready = (udp.begin(local_port) == 1);
    is_initialized = true;
    is_ap_mode = true;
    
    // ✅ ✅ ✅ 新增：缓存AP模式固定配置 ✅ ✅ ✅
    current_ssid = String(_ssid);
    current_password = String(_pwd);
    cached_gw1 = 192;
    cached_gw2 = 168;
    cached_gw3 = 4;
    cached_gw4 = 1;
    
    Serial.printf("✅ AP模式启动: 热点=\"%s\" 密码=\"%s\"\n", _ssid, _pwd);
    Serial.printf("🌐 本机IP: %s | 端口: %d | 客户端: %d/%d\n", 
                  WiFi.softAPIP().toString().c_str(), port,
                  WiFi.softAPgetStationNum(), 4);
    return true;
  }

  // ✅ ✅ ✅ 核心：向小车发送配置（仅需IP最后1段 - 20行）✅ ✅ ✅
  void sendConfigToDevice(uint8_t car_last_octet, uint8_t monitor_last_octet) {
    if (!is_initialized) {
      Serial.println("❌ 手柄未配置网络，无法发送配置");
      return;
    }
    if (car_last_octet == cached_gw4 || car_last_octet == 0 || car_last_octet == 255) {
      Serial.println("❌ 无效的小车编号（需1~254，且不能与网关相同）");
      return;
    }
    
    // 获取本机IP最后1段（AP模式固定为1，STA模式取local_ip[3]）
    uint8_t local_last = is_ap_mode ? 1 : local_ip[3];
    if(monitor_last_octet!=255)  local_last =monitor_last_octet;  

    // 发送标准配置指令（与终端handleSerialCommand完全兼容）
    Serial.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
                  current_ssid.c_str(),
                  current_password.c_str(),
                  cached_gw1, cached_gw2, cached_gw3, cached_gw4,
                  car_last_octet,  // 小车本机IP最后1段
                  local_last);     // 监视器IP最后1段（指向本机）

    Serial1.printf("wificar:%s:%s:%d:%d:%d:%d:%d:%d\nWZHY",
                  current_ssid.c_str(),
                  current_password.c_str(),
                  cached_gw1, cached_gw2, cached_gw3, cached_gw4,
                  car_last_octet,  // 小车本机IP最后1段
                  local_last);     // 监视器IP最后1段（指向本机）
 
    
    Serial.printf("✅ 已配置小车 #%d → IP=%d.%d.%d.%d | 监视器=%d.%d.%d.%d\n",
                  car_last_octet,
                  cached_gw1, cached_gw2, cached_gw3, car_last_octet,
                  cached_gw1, cached_gw2, cached_gw3, local_last);
  }

  // ✅ 接收：轻量维护 + 纯检查（自动适配AP/STA）
  bool hasMessage() {
    autoMaintain();
    if (!is_initialized || !ready) return false;
    
    receivedRaw = "";
    parsedValue = "";
    
    int len = udp.parsePacket();
    if (len <= 0 || len > 512) return false;
    
    char buf[513];
    int read = udp.read((uint8_t*)buf, min(len, 512));
    if (read > 0) {
      buf[read] = '\0';
      receivedRaw = String(buf);
      // ✅【修复 1】补全右括号 + 简化 this->
      // ✅【修复 2】类内调用直接写 lastValue()
      if (getMessageValue("ESPNOW_REG")) {
        Serial.println(lastValue());
      }
      return true;
    }
    return false;
  }

  // ✅ 解析字段（不变）
  bool getMessageValue(const String& key) {
    parsedValue = "";
    if (receivedRaw.length() == 0) return false;
    
    String keyPattern = "\"" + key + "\"";
    int startIndex = receivedRaw.indexOf(keyPattern);
    if (startIndex == -1) return false;
    
    int colonIndex = receivedRaw.indexOf(":", startIndex);
    if (colonIndex == -1) return false;
    
    int valueStart = colonIndex + 1;
    while (valueStart < receivedRaw.length() && 
           receivedRaw.charAt(valueStart) == ' ') valueStart++;
    
    if (valueStart < receivedRaw.length() && receivedRaw.charAt(valueStart) == '"') {
      int valueEnd = receivedRaw.indexOf("\"", valueStart + 1);
      if (valueEnd != -1) {
        parsedValue = receivedRaw.substring(valueStart + 1, valueEnd);
        return true;
      }
    } else {
      int valueEnd = valueStart;
      while (valueEnd < receivedRaw.length() &&
             receivedRaw.charAt(valueEnd) != ',' &&
             receivedRaw.charAt(valueEnd) != '}' &&
             receivedRaw.charAt(valueEnd) != ' ') valueEnd++;
      while (valueEnd > valueStart && receivedRaw.charAt(valueEnd - 1) == ' ') valueEnd--;
      if (valueEnd > valueStart) {
        parsedValue = receivedRaw.substring(valueStart, valueEnd);
        return true;
      }
    }
    return false;
  }

  // ✅ 发送：轻量维护 + 直接发送（自动适配AP/STA）
  bool sendTo(const String& target_ip_str, const String& key, const char* format, ...) {
    autoMaintain();
    if (!is_initialized || !ready) return false;
    
    IPAddress target_ip;
    if (!target_ip.fromString(target_ip_str)) return false;
    
    char content[256];
    va_list args;
    va_start(args, format);
    vsnprintf(content, sizeof(content) - 1, format, args);
    va_end(args);
    
    char json[300];
    snprintf(json, sizeof(json), "{\"%s\":\"%s\"}\r\n", key.c_str(), content);
    
    return udp.beginPacket(target_ip, local_port) && 
           udp.write((const uint8_t*)json, strlen(json)) && 
           udp.endPacket();
  }

  // ✅ 积木友好重载
  bool sendTo(const String& target_ip_str, const String& key, const String& value) {
    return sendTo(target_ip_str, key, "%s", value.c_str());
  }

  // ✅ 辅助方法：自动返回正确IP（AP/STA自适应）
  String lastValue() { return parsedValue; }
  String lastMessage() { return receivedRaw; }
  String getLocalIP() { 
    autoMaintain();
    return is_initialized ? 
           (is_ap_mode ? WiFi.softAPIP().toString() : WiFi.localIP().toString()) : 
           "未配置"; 
  }
  bool isConnected() { 
    autoMaintain();
    return is_initialized && ready; 
  }
  bool isAPMode() { return is_ap_mode; }
};
extern UDPPeer peer;
