Compare commits
	
		
			10 Commits
		
	
	
		
			c5a8ed093f
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 670b86d200 | |||
| 41e14ec828 | |||
| 9f663aed0b | |||
| 21d21f8247 | |||
| e1a749a8cd | |||
| b3c6b4cde9 | |||
| 30585041b6 | |||
| 9bea3546c7 | |||
| 8db2182b05 | |||
| 46ca688434 | 
@@ -190,8 +190,11 @@ void sendStoredIRSignal(String buttonName) {
 | 
				
			|||||||
// 从字符串解析红外信号
 | 
					// 从字符串解析红外信号
 | 
				
			||||||
IRSignal parseIRSignalFromString(String dataStr) {
 | 
					IRSignal parseIRSignalFromString(String dataStr) {
 | 
				
			||||||
  IRSignal signal;
 | 
					  IRSignal signal;
 | 
				
			||||||
  signal.data = nullptr;
 | 
					  signal.markTimes = nullptr;
 | 
				
			||||||
  signal.length = 0;
 | 
					  signal.spaceTimes = nullptr;
 | 
				
			||||||
 | 
					  signal.markCount = 0;
 | 
				
			||||||
 | 
					  signal.spaceCount = 0;
 | 
				
			||||||
 | 
					  signal.carrierFreq = IR_CARRIER_FREQ;
 | 
				
			||||||
  signal.isValid = false;
 | 
					  signal.isValid = false;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  if (dataStr.length() == 0) {
 | 
					  if (dataStr.length() == 0) {
 | 
				
			||||||
@@ -203,11 +206,11 @@ IRSignal parseIRSignalFromString(String dataStr) {
 | 
				
			|||||||
  for (int i = 0; i < dataStr.length(); i++) {
 | 
					  for (int i = 0; i < dataStr.length(); i++) {
 | 
				
			||||||
    if (dataStr[i] == ',') commaCount++;
 | 
					    if (dataStr[i] == ',') commaCount++;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  signal.length = commaCount + 1;
 | 
					  int totalLength = commaCount + 1;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  // 分配内存
 | 
					  // 分配临时内存存储原始数据
 | 
				
			||||||
  signal.data = (unsigned int*)malloc(signal.length * sizeof(unsigned int));
 | 
					  unsigned int* tempData = (unsigned int*)malloc(totalLength * sizeof(unsigned int));
 | 
				
			||||||
  if (signal.data == nullptr) {
 | 
					  if (tempData == nullptr) {
 | 
				
			||||||
    return signal;
 | 
					    return signal;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
@@ -217,27 +220,78 @@ IRSignal parseIRSignalFromString(String dataStr) {
 | 
				
			|||||||
  for (int i = 0; i <= dataStr.length(); i++) {
 | 
					  for (int i = 0; i <= dataStr.length(); i++) {
 | 
				
			||||||
    if (i == dataStr.length() || dataStr[i] == ',') {
 | 
					    if (i == dataStr.length() || dataStr[i] == ',') {
 | 
				
			||||||
      String valueStr = dataStr.substring(startPos, i);
 | 
					      String valueStr = dataStr.substring(startPos, i);
 | 
				
			||||||
      signal.data[index] = valueStr.toInt();
 | 
					      tempData[index] = valueStr.toInt();
 | 
				
			||||||
      index++;
 | 
					      index++;
 | 
				
			||||||
      startPos = i + 1;
 | 
					      startPos = i + 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  // 分离mark和space数据
 | 
				
			||||||
 | 
					  signal.markCount = (totalLength + 1) / 2;
 | 
				
			||||||
 | 
					  signal.spaceCount = totalLength / 2;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // 分配mark时间数组
 | 
				
			||||||
 | 
					  if (signal.markCount > 0) {
 | 
				
			||||||
 | 
					    signal.markTimes = (unsigned int*)malloc(signal.markCount * sizeof(unsigned int));
 | 
				
			||||||
 | 
					    if (signal.markTimes == nullptr) {
 | 
				
			||||||
 | 
					      free(tempData);
 | 
				
			||||||
 | 
					      return signal;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (int i = 0; i < signal.markCount; i++) {
 | 
				
			||||||
 | 
					      signal.markTimes[i] = tempData[i * 2];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // 分配space时间数组
 | 
				
			||||||
 | 
					  if (signal.spaceCount > 0) {
 | 
				
			||||||
 | 
					    signal.spaceTimes = (unsigned int*)malloc(signal.spaceCount * sizeof(unsigned int));
 | 
				
			||||||
 | 
					    if (signal.spaceTimes == nullptr) {
 | 
				
			||||||
 | 
					      if (signal.markTimes != nullptr) {
 | 
				
			||||||
 | 
					        free(signal.markTimes);
 | 
				
			||||||
 | 
					        signal.markTimes = nullptr;
 | 
				
			||||||
 | 
					        signal.markCount = 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      free(tempData);
 | 
				
			||||||
 | 
					      return signal;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (int i = 0; i < signal.spaceCount; i++) {
 | 
				
			||||||
 | 
					      signal.spaceTimes[i] = tempData[i * 2 + 1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  free(tempData);
 | 
				
			||||||
  signal.isValid = true;
 | 
					  signal.isValid = true;
 | 
				
			||||||
  return signal;
 | 
					  return signal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 将红外信号转换为字符串
 | 
					// 将红外信号转换为字符串
 | 
				
			||||||
String irSignalToString(const IRSignal& signal) {
 | 
					String irSignalToString(const IRSignal& signal) {
 | 
				
			||||||
  if (!signal.isValid || signal.data == nullptr || signal.length == 0) {
 | 
					  if (!signal.isValid || 
 | 
				
			||||||
 | 
					      (signal.markTimes == nullptr && signal.spaceTimes == nullptr) || 
 | 
				
			||||||
 | 
					      (signal.markCount == 0 && signal.spaceCount == 0)) {
 | 
				
			||||||
    return "";
 | 
					    return "";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  String result = "";
 | 
					  String result = "";
 | 
				
			||||||
  for (int i = 0; i < signal.length; i++) {
 | 
					  int maxCount = max(signal.markCount, signal.spaceCount);
 | 
				
			||||||
    if (i > 0) result += ",";
 | 
					  
 | 
				
			||||||
    result += String(signal.data[i]);
 | 
					  // 重建原始时序数据:交替输出mark和space
 | 
				
			||||||
 | 
					  for (int i = 0; i < maxCount; i++) {
 | 
				
			||||||
 | 
					    // 添加mark时间
 | 
				
			||||||
 | 
					    if (i < signal.markCount) {
 | 
				
			||||||
 | 
					      if (result.length() > 0) result += ",";
 | 
				
			||||||
 | 
					      result += String(signal.markTimes[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 添加space时间
 | 
				
			||||||
 | 
					    if (i < signal.spaceCount) {
 | 
				
			||||||
 | 
					      if (result.length() > 0) result += ",";
 | 
				
			||||||
 | 
					      result += String(signal.spaceTimes[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  return result;
 | 
					  return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -751,7 +805,7 @@ void handleStartRecord() {
 | 
				
			|||||||
    prefs.putString(("ir_" + buttonName).c_str(), irDataStr);
 | 
					    prefs.putString(("ir_" + buttonName).c_str(), irDataStr);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("红外信号录入成功: " + buttonName);
 | 
					    Serial.println("红外信号录入成功: " + buttonName);
 | 
				
			||||||
    Serial.println("信号长度: " + String(signal.length));
 | 
					    Serial.println("mark数量: " + String(signal.markCount) + ", space数量: " + String(signal.spaceCount));
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    freeIRSignal(signal);
 | 
					    freeIRSignal(signal);
 | 
				
			||||||
    server.send(200, "application/json", "{\"success\":true}");
 | 
					    server.send(200, "application/json", "{\"success\":true}");
 | 
				
			||||||
@@ -768,7 +822,7 @@ void handleSendIR() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  DynamicJsonDocument doc(200);
 | 
					  DynamicJsonDocument doc(26700);
 | 
				
			||||||
  deserializeJson(doc, server.arg("plain"));
 | 
					  deserializeJson(doc, server.arg("plain"));
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  String buttonName = doc["button"];
 | 
					  String buttonName = doc["button"];
 | 
				
			||||||
@@ -818,6 +872,12 @@ void handleSetSettings() {
 | 
				
			|||||||
  prefs.putFloat("min_temp", minTemp);
 | 
					  prefs.putFloat("min_temp", minTemp);
 | 
				
			||||||
  prefs.putFloat("max_temp", maxTemp);
 | 
					  prefs.putFloat("max_temp", maxTemp);
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  Serial.printf("温度设置已更新 - 最低: %.1f°C, 最高: %.1f°C\n", minTemp, maxTemp);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // 温度设置更改后立即执行一次判断
 | 
				
			||||||
 | 
					  Serial.println("温度设置已更改,立即执行核心判断...");
 | 
				
			||||||
 | 
					  executeJudgeLogic();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  server.send(200, "application/json", "{\"success\":true}");
 | 
					  server.send(200, "application/json", "{\"success\":true}");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										101
									
								
								IRReceiver.h
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								IRReceiver.h
									
									
									
									
									
								
							@@ -1,101 +0,0 @@
 | 
				
			|||||||
#ifndef IR_RECEIVER_H
 | 
					 | 
				
			||||||
#define IR_RECEIVER_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#include <IRremote.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 红外接收结果结构体
 | 
					 | 
				
			||||||
struct IRReceiveResult {
 | 
					 | 
				
			||||||
    bool success;           // 是否成功接收到信号
 | 
					 | 
				
			||||||
    uint32_t protocol;      // 协议类型
 | 
					 | 
				
			||||||
    uint32_t address;       // 地址码
 | 
					 | 
				
			||||||
    uint32_t command;       // 命令码
 | 
					 | 
				
			||||||
    uint16_t bits;          // 数据位数
 | 
					 | 
				
			||||||
    uint16_t* rawData;      // 原始数据指针
 | 
					 | 
				
			||||||
    size_t rawLength;       // 原始数据长度
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class IRReceiver {
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    static uint8_t receivePin;
 | 
					 | 
				
			||||||
    static bool initialized;
 | 
					 | 
				
			||||||
    static uint16_t rawBuffer[RAW_BUFFER_LENGTH];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    // 初始化红外接收器
 | 
					 | 
				
			||||||
    static void init(uint8_t pin = 15);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 读取红外信号直到信号结束,返回读取结果
 | 
					 | 
				
			||||||
    static IRReceiveResult readIRSignal(uint32_t timeoutMs = 5000);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 释放资源
 | 
					 | 
				
			||||||
    static void cleanup();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 静态成员变量定义
 | 
					 | 
				
			||||||
uint8_t IRReceiver::receivePin = 15;
 | 
					 | 
				
			||||||
bool IRReceiver::initialized = false;
 | 
					 | 
				
			||||||
uint16_t IRReceiver::rawBuffer[RAW_BUFFER_LENGTH];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 初始化红外接收器
 | 
					 | 
				
			||||||
void IRReceiver::init(uint8_t pin) {
 | 
					 | 
				
			||||||
    receivePin = pin;
 | 
					 | 
				
			||||||
    IrReceiver.begin(receivePin, ENABLE_LED_FEEDBACK);
 | 
					 | 
				
			||||||
    initialized = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 读取红外信号直到信号结束,返回读取结果
 | 
					 | 
				
			||||||
IRReceiveResult IRReceiver::readIRSignal(uint32_t timeoutMs) {
 | 
					 | 
				
			||||||
    IRReceiveResult result = {false, 0, 0, 0, 0, nullptr, 0};
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (!initialized) {
 | 
					 | 
				
			||||||
        return result;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    unsigned long startTime = millis();
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 等待接收到红外信号
 | 
					 | 
				
			||||||
    while (!IrReceiver.decode()) {
 | 
					 | 
				
			||||||
        if (millis() - startTime > timeoutMs) {
 | 
					 | 
				
			||||||
            return result; // 超时返回失败结果
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        delay(1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 成功接收到信号
 | 
					 | 
				
			||||||
    result.success = true;
 | 
					 | 
				
			||||||
    result.protocol = IrReceiver.decodedIRData.protocol;
 | 
					 | 
				
			||||||
    result.address = IrReceiver.decodedIRData.address;
 | 
					 | 
				
			||||||
    result.command = IrReceiver.decodedIRData.command;
 | 
					 | 
				
			||||||
    result.bits = IrReceiver.decodedIRData.numberOfBits;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 复制原始数据到缓冲区
 | 
					 | 
				
			||||||
    if (IrReceiver.decodedIRData.rawDataPtr != nullptr) {
 | 
					 | 
				
			||||||
        result.rawLength = IrReceiver.decodedIRData.rawlen;
 | 
					 | 
				
			||||||
        // 确保不超过缓冲区大小
 | 
					 | 
				
			||||||
        if (result.rawLength > RAW_BUFFER_LENGTH) {
 | 
					 | 
				
			||||||
            result.rawLength = RAW_BUFFER_LENGTH;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 复制原始数据
 | 
					 | 
				
			||||||
        for (size_t i = 0; i < result.rawLength; i++) {
 | 
					 | 
				
			||||||
            rawBuffer[i] = IrReceiver.decodedIRData.rawDataPtr->rawbuf[i];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        result.rawData = rawBuffer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 准备接收下一个信号
 | 
					 | 
				
			||||||
    IrReceiver.resume();
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 释放资源
 | 
					 | 
				
			||||||
void IRReceiver::cleanup() {
 | 
					 | 
				
			||||||
    if (initialized) {
 | 
					 | 
				
			||||||
        IrReceiver.stop();
 | 
					 | 
				
			||||||
        initialized = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
							
								
								
									
										370
									
								
								LunarCalendar.h
									
									
									
									
									
								
							
							
						
						
									
										370
									
								
								LunarCalendar.h
									
									
									
									
									
								
							@@ -1,370 +0,0 @@
 | 
				
			|||||||
#ifndef LUNAR_CALENDAR_H
 | 
					 | 
				
			||||||
#define LUNAR_CALENDAR_H
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LunarCalendar {
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    // 农历数据表 (1900-2100年)
 | 
					 | 
				
			||||||
    static const uint32_t lunarInfo[];
 | 
					 | 
				
			||||||
    static const char* lunarMonthNames[];
 | 
					 | 
				
			||||||
    static const char* lunarDayNames[];
 | 
					 | 
				
			||||||
    static const char* tianGan[];
 | 
					 | 
				
			||||||
    static const char* diZhi[];
 | 
					 | 
				
			||||||
    static const char* animals[];
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 辅助函数
 | 
					 | 
				
			||||||
    static int getDaysInYear(int year);
 | 
					 | 
				
			||||||
    static int getLeapMonth(int year);
 | 
					 | 
				
			||||||
    static int getDaysInMonth(int year, int month, bool isLeap = false);
 | 
					 | 
				
			||||||
    static bool isLeapYear(int year);
 | 
					 | 
				
			||||||
    static int getDayOfYear(int year, int month, int day);
 | 
					 | 
				
			||||||
    static void calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
					 | 
				
			||||||
                              int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    // 主要转换函数
 | 
					 | 
				
			||||||
    static String solarToLunar(String solarDate);
 | 
					 | 
				
			||||||
    static String getLunarYearName(int year);
 | 
					 | 
				
			||||||
    static String getLunarMonthName(int month, bool isLeap = false);
 | 
					 | 
				
			||||||
    static String getLunarDayName(int day);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 节假日判断函数
 | 
					 | 
				
			||||||
    static bool isHoliday(int year, int month, int day);
 | 
					 | 
				
			||||||
    static bool isSolarHoliday(int month, int day);
 | 
					 | 
				
			||||||
    static bool isLunarHoliday(int lunarYear, int lunarMonth, int lunarDay, bool isLeap);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 农历数据表 (1900-2100年,每年用32位表示)
 | 
					 | 
				
			||||||
// 前4位表示闰月月份,后12位表示每月天数(0=29天,1=30天)
 | 
					 | 
				
			||||||
const uint32_t LunarCalendar::lunarInfo[] = {
 | 
					 | 
				
			||||||
    0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
 | 
					 | 
				
			||||||
    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
 | 
					 | 
				
			||||||
    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
 | 
					 | 
				
			||||||
    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
 | 
					 | 
				
			||||||
    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
 | 
					 | 
				
			||||||
    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
 | 
					 | 
				
			||||||
    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
 | 
					 | 
				
			||||||
    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
 | 
					 | 
				
			||||||
    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
 | 
					 | 
				
			||||||
    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
 | 
					 | 
				
			||||||
    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
 | 
					 | 
				
			||||||
    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
 | 
					 | 
				
			||||||
    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
 | 
					 | 
				
			||||||
    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
 | 
					 | 
				
			||||||
    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
 | 
					 | 
				
			||||||
    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
 | 
					 | 
				
			||||||
    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
 | 
					 | 
				
			||||||
    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
 | 
					 | 
				
			||||||
    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
 | 
					 | 
				
			||||||
    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
 | 
					 | 
				
			||||||
    0x0d520
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 农历月份名称
 | 
					 | 
				
			||||||
const char* LunarCalendar::lunarMonthNames[] = {
 | 
					 | 
				
			||||||
    "正月", "二月", "三月", "四月", "五月", "六月",
 | 
					 | 
				
			||||||
    "七月", "八月", "九月", "十月", "冬月", "腊月"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 农历日期名称
 | 
					 | 
				
			||||||
const char* LunarCalendar::lunarDayNames[] = {
 | 
					 | 
				
			||||||
    "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
 | 
					 | 
				
			||||||
    "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
 | 
					 | 
				
			||||||
    "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 天干
 | 
					 | 
				
			||||||
const char* LunarCalendar::tianGan[] = {
 | 
					 | 
				
			||||||
    "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 地支
 | 
					 | 
				
			||||||
const char* LunarCalendar::diZhi[] = {
 | 
					 | 
				
			||||||
    "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 生肖
 | 
					 | 
				
			||||||
const char* LunarCalendar::animals[] = {
 | 
					 | 
				
			||||||
    "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历年总天数
 | 
					 | 
				
			||||||
int LunarCalendar::getDaysInYear(int year) {
 | 
					 | 
				
			||||||
    // 边界检查
 | 
					 | 
				
			||||||
    if (year < 1900 || year > 2100) return 365;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    int sum = 348;
 | 
					 | 
				
			||||||
    for (int i = 0x8000; i > 0x8; i >>= 1) {
 | 
					 | 
				
			||||||
        if ((lunarInfo[year - 1900] & i) != 0) sum += 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return sum + getDaysInMonth(year, getLeapMonth(year), true);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历年闰月月份
 | 
					 | 
				
			||||||
int LunarCalendar::getLeapMonth(int year) {
 | 
					 | 
				
			||||||
    // 边界检查
 | 
					 | 
				
			||||||
    if (year < 1900 || year > 2100) return 0;
 | 
					 | 
				
			||||||
    return lunarInfo[year - 1900] & 0xf;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历月天数
 | 
					 | 
				
			||||||
int LunarCalendar::getDaysInMonth(int year, int month, bool isLeap) {
 | 
					 | 
				
			||||||
    // 边界检查
 | 
					 | 
				
			||||||
    if (year < 1900 || year > 2100 || month < 1 || month > 12) return 29;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (isLeap && month != getLeapMonth(year)) return 0;
 | 
					 | 
				
			||||||
    if (isLeap) {
 | 
					 | 
				
			||||||
        return (lunarInfo[year - 1900] & 0x10000) ? 30 : 29;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return (lunarInfo[year - 1900] & (0x10000 >> month)) ? 30 : 29;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 判断公历年是否为闰年
 | 
					 | 
				
			||||||
bool LunarCalendar::isLeapYear(int year) {
 | 
					 | 
				
			||||||
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取公历日期在当年的天数
 | 
					 | 
				
			||||||
int LunarCalendar::getDayOfYear(int year, int month, int day) {
 | 
					 | 
				
			||||||
    int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 | 
					 | 
				
			||||||
    if (isLeapYear(year)) daysInMonth[1] = 29;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    int dayOfYear = day;
 | 
					 | 
				
			||||||
    for (int i = 0; i < month - 1; i++) {
 | 
					 | 
				
			||||||
        dayOfYear += daysInMonth[i];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return dayOfYear;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 核心转换算法
 | 
					 | 
				
			||||||
void LunarCalendar::calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
					 | 
				
			||||||
                                  int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap) {
 | 
					 | 
				
			||||||
    // 1900年1月31日为农历1900年正月初一
 | 
					 | 
				
			||||||
    int baseYear = 1900;
 | 
					 | 
				
			||||||
    int baseMonth = 1;
 | 
					 | 
				
			||||||
    int baseDay = 31;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 计算距离基准日期的天数
 | 
					 | 
				
			||||||
    int offset = 0;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 计算年份差
 | 
					 | 
				
			||||||
    for (int i = baseYear; i < solarYear; i++) {
 | 
					 | 
				
			||||||
        offset += isLeapYear(i) ? 366 : 365;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 加上当年已过天数
 | 
					 | 
				
			||||||
    offset += getDayOfYear(solarYear, solarMonth, solarDay) - getDayOfYear(baseYear, baseMonth, baseDay);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 计算农历日期
 | 
					 | 
				
			||||||
    lunarYear = baseYear;
 | 
					 | 
				
			||||||
    int daysInLunarYear = getDaysInYear(lunarYear);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    while (offset >= daysInLunarYear) {
 | 
					 | 
				
			||||||
        offset -= daysInLunarYear;
 | 
					 | 
				
			||||||
        lunarYear++;
 | 
					 | 
				
			||||||
        daysInLunarYear = getDaysInYear(lunarYear);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 计算月份
 | 
					 | 
				
			||||||
    lunarMonth = 1;
 | 
					 | 
				
			||||||
    isLeap = false;
 | 
					 | 
				
			||||||
    int leapMonth = getLeapMonth(lunarYear);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    while (offset > 0 && lunarMonth <= 12) {
 | 
					 | 
				
			||||||
        int daysInCurrentMonth;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (lunarMonth == leapMonth && !isLeap && leapMonth > 0) {
 | 
					 | 
				
			||||||
            // 闰月
 | 
					 | 
				
			||||||
            daysInCurrentMonth = getDaysInMonth(lunarYear, lunarMonth, true);
 | 
					 | 
				
			||||||
            if (offset >= daysInCurrentMonth) {
 | 
					 | 
				
			||||||
                offset -= daysInCurrentMonth;
 | 
					 | 
				
			||||||
                isLeap = true;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                isLeap = true;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 正常月份
 | 
					 | 
				
			||||||
        daysInCurrentMonth = getDaysInMonth(lunarYear, lunarMonth, false);
 | 
					 | 
				
			||||||
        if (offset >= daysInCurrentMonth) {
 | 
					 | 
				
			||||||
            offset -= daysInCurrentMonth;
 | 
					 | 
				
			||||||
            if (isLeap) {
 | 
					 | 
				
			||||||
                isLeap = false;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                lunarMonth++;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 确保月份在有效范围内
 | 
					 | 
				
			||||||
    if (lunarMonth > 12) lunarMonth = 12;
 | 
					 | 
				
			||||||
    if (lunarMonth < 1) lunarMonth = 1;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    lunarDay = offset + 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 主要转换函数
 | 
					 | 
				
			||||||
String LunarCalendar::solarToLunar(String solarDate) {
 | 
					 | 
				
			||||||
    // 解析输入日期 "2025/5/6"
 | 
					 | 
				
			||||||
    int year, month, day;
 | 
					 | 
				
			||||||
    int firstSlash = solarDate.indexOf('/');
 | 
					 | 
				
			||||||
    int secondSlash = solarDate.indexOf('/', firstSlash + 1);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (firstSlash == -1 || secondSlash == -1) {
 | 
					 | 
				
			||||||
        return "日期格式错误";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    year = solarDate.substring(0, firstSlash).toInt();
 | 
					 | 
				
			||||||
    month = solarDate.substring(firstSlash + 1, secondSlash).toInt();
 | 
					 | 
				
			||||||
    day = solarDate.substring(secondSlash + 1).toInt();
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 验证日期范围
 | 
					 | 
				
			||||||
    if (year < 1900 || year > 2100 || month < 1 || month > 12 || day < 1 || day > 31) {
 | 
					 | 
				
			||||||
        return "日期超出范围(1900-2100)";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 转换为农历
 | 
					 | 
				
			||||||
    int lunarYear, lunarMonth, lunarDay;
 | 
					 | 
				
			||||||
    bool isLeap;
 | 
					 | 
				
			||||||
    calculateLunar(year, month, day, lunarYear, lunarMonth, lunarDay, isLeap);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 格式化输出
 | 
					 | 
				
			||||||
    String result = getLunarYearName(lunarYear) + "年";
 | 
					 | 
				
			||||||
    result += getLunarMonthName(lunarMonth, isLeap);
 | 
					 | 
				
			||||||
    result += getLunarDayName(lunarDay);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历年份名称(天干地支+生肖)
 | 
					 | 
				
			||||||
String LunarCalendar::getLunarYearName(int year) {
 | 
					 | 
				
			||||||
    int tianGanIndex = (year - 4) % 10;
 | 
					 | 
				
			||||||
    int diZhiIndex = (year - 4) % 12;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 确保索引为正数
 | 
					 | 
				
			||||||
    if (tianGanIndex < 0) tianGanIndex += 10;
 | 
					 | 
				
			||||||
    if (diZhiIndex < 0) diZhiIndex += 12;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    String yearName = String(tianGan[tianGanIndex]) + String(diZhi[diZhiIndex]);
 | 
					 | 
				
			||||||
    yearName += "(" + String(animals[diZhiIndex]) + ")";
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return yearName;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历月份名称
 | 
					 | 
				
			||||||
String LunarCalendar::getLunarMonthName(int month, bool isLeap) {
 | 
					 | 
				
			||||||
    String monthName = "";
 | 
					 | 
				
			||||||
    if (isLeap) {
 | 
					 | 
				
			||||||
        monthName = "闰";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 添加边界检查
 | 
					 | 
				
			||||||
    if (month >= 1 && month <= 12) {
 | 
					 | 
				
			||||||
        monthName += lunarMonthNames[month - 1];
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        monthName += "未知月";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return monthName;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 获取农历日期名称
 | 
					 | 
				
			||||||
String LunarCalendar::getLunarDayName(int day) {
 | 
					 | 
				
			||||||
    // 添加边界检查
 | 
					 | 
				
			||||||
    if (day >= 1 && day <= 30) {
 | 
					 | 
				
			||||||
        return String(lunarDayNames[day - 1]);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return "未知日";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 节假日数据结构
 | 
					 | 
				
			||||||
struct SolarHoliday {
 | 
					 | 
				
			||||||
    int month;
 | 
					 | 
				
			||||||
    int day;
 | 
					 | 
				
			||||||
    const char* name;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct LunarHoliday {
 | 
					 | 
				
			||||||
    int month;
 | 
					 | 
				
			||||||
    int day;
 | 
					 | 
				
			||||||
    const char* name;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 公历法定节假日列表
 | 
					 | 
				
			||||||
static const SolarHoliday solarHolidays[] = {
 | 
					 | 
				
			||||||
    {1, 1, "元旦"},
 | 
					 | 
				
			||||||
    {3, 8, "妇女节"},
 | 
					 | 
				
			||||||
    {5, 1, "劳动节"},
 | 
					 | 
				
			||||||
    {6, 1, "儿童节"},
 | 
					 | 
				
			||||||
    {10, 1, "国庆节"},
 | 
					 | 
				
			||||||
    {10, 2, "国庆节"},
 | 
					 | 
				
			||||||
    {10, 3, "国庆节"}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 农历法定节假日列表
 | 
					 | 
				
			||||||
static const LunarHoliday lunarHolidays[] = {
 | 
					 | 
				
			||||||
    {1, 1, "春节"},
 | 
					 | 
				
			||||||
    {1, 2, "春节"},
 | 
					 | 
				
			||||||
    {1, 3, "春节"},
 | 
					 | 
				
			||||||
    {1, 15, "元宵节"},
 | 
					 | 
				
			||||||
    {5, 5, "端午节"},
 | 
					 | 
				
			||||||
    {8, 15, "中秋节"},
 | 
					 | 
				
			||||||
    {9, 9, "重阳节"},
 | 
					 | 
				
			||||||
    {12, 30, "除夕"},  // 大月
 | 
					 | 
				
			||||||
    {12, 29, "除夕"}   // 小月
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 判断是否为公历节假日
 | 
					 | 
				
			||||||
bool LunarCalendar::isSolarHoliday(int month, int day) {
 | 
					 | 
				
			||||||
    int numSolarHolidays = sizeof(solarHolidays) / sizeof(solarHolidays[0]);
 | 
					 | 
				
			||||||
    for (int i = 0; i < numSolarHolidays; i++) {
 | 
					 | 
				
			||||||
        if (solarHolidays[i].month == month && solarHolidays[i].day == day) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 判断是否为农历节假日
 | 
					 | 
				
			||||||
bool LunarCalendar::isLunarHoliday(int lunarYear, int lunarMonth, int lunarDay, bool isLeap) {
 | 
					 | 
				
			||||||
    // 闰月不算节假日
 | 
					 | 
				
			||||||
    if (isLeap) return false;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    int numLunarHolidays = sizeof(lunarHolidays) / sizeof(lunarHolidays[0]);
 | 
					 | 
				
			||||||
    for (int i = 0; i < numLunarHolidays; i++) {
 | 
					 | 
				
			||||||
        if (lunarHolidays[i].month == lunarMonth && lunarHolidays[i].day == lunarDay) {
 | 
					 | 
				
			||||||
            // 特殊处理除夕:需要判断是否为该年最后一天
 | 
					 | 
				
			||||||
            if (lunarMonth == 12 && (lunarDay == 29 || lunarDay == 30)) {
 | 
					 | 
				
			||||||
                int daysInLastMonth = getDaysInMonth(lunarYear, 12, false);
 | 
					 | 
				
			||||||
                if (lunarDay == daysInLastMonth) {
 | 
					 | 
				
			||||||
                    return true; // 是除夕
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 主要节假日判断函数
 | 
					 | 
				
			||||||
bool LunarCalendar::isHoliday(int year, int month, int day) {
 | 
					 | 
				
			||||||
    // 首先检查公历节假日
 | 
					 | 
				
			||||||
    if (isSolarHoliday(month, day)) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 检查农历节假日
 | 
					 | 
				
			||||||
    int lunarYear, lunarMonth, lunarDay;
 | 
					 | 
				
			||||||
    bool isLeap;
 | 
					 | 
				
			||||||
    calculateLunar(year, month, day, lunarYear, lunarMonth, lunarDay, isLeap);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return isLunarHoliday(lunarYear, lunarMonth, lunarDay, isLeap);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif // LUNAR_CALENDAR_H
 | 
					 | 
				
			||||||
@@ -4,6 +4,13 @@
 | 
				
			|||||||
#include <Arduino.h>
 | 
					#include <Arduino.h>
 | 
				
			||||||
#include <WiFi.h>
 | 
					#include <WiFi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LunarDate {
 | 
				
			||||||
 | 
					    int year;
 | 
				
			||||||
 | 
					    int month;
 | 
				
			||||||
 | 
					    int day;
 | 
				
			||||||
 | 
					    bool isLeapMonth;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LunarCalendar {
 | 
					class LunarCalendar {
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    // 农历数据表 (1900-2100年)
 | 
					    // 农历数据表 (1900-2100年)
 | 
				
			||||||
@@ -20,6 +27,10 @@ private:
 | 
				
			|||||||
    static int getDaysInMonth(int year, int month, bool isLeap = false);
 | 
					    static int getDaysInMonth(int year, int month, bool isLeap = false);
 | 
				
			||||||
    static bool isLeapYear(int year);
 | 
					    static bool isLeapYear(int year);
 | 
				
			||||||
    static int getDayOfYear(int year, int month, int day);
 | 
					    static int getDayOfYear(int year, int month, int day);
 | 
				
			||||||
 | 
					    static uint32_t calcSolarDateInterval(int year1, int month1, int day1, int year2, int month2, int day2);
 | 
				
			||||||
 | 
					    static bool hasLeapMonth(int solarYear, int& leapMonth, bool& leapIs30Days);
 | 
				
			||||||
 | 
					    static int getLunarMonthDays(int solarYear, int lunarMonth);
 | 
				
			||||||
 | 
					    static bool calcLunarFromInterval(uint32_t dateInterval, LunarDate& lunar);
 | 
				
			||||||
    static void calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
					    static void calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
				
			||||||
                              int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap);
 | 
					                              int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -38,28 +49,29 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// 农历数据表 (1900-2100年,每年用32位表示)
 | 
					// 农历数据表 (1900-2100年,每年用32位表示)
 | 
				
			||||||
// 前4位表示闰月月份,后12位表示每月天数(0=29天,1=30天)
 | 
					// 前4位表示闰月月份,后12位表示每月天数(0=29天,1=30天)
 | 
				
			||||||
 | 
					// 数据来源:CSDN Tyrion.Mon,经过验证的标准农历数据
 | 
				
			||||||
const uint32_t LunarCalendar::lunarInfo[] = {
 | 
					const uint32_t LunarCalendar::lunarInfo[] = {
 | 
				
			||||||
    0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
 | 
					    0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, //1900-1909
 | 
				
			||||||
    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
 | 
					    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, //1910-1919
 | 
				
			||||||
    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
 | 
					    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, //1920-1929
 | 
				
			||||||
    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
 | 
					    0x06566, 0x0d4a0, 0x0ea50, 0x16a95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, //1930-1939
 | 
				
			||||||
    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
 | 
					    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, //1940-1949
 | 
				
			||||||
    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
 | 
					    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, //1950-1959
 | 
				
			||||||
    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
 | 
					    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, //1960-1969
 | 
				
			||||||
    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,
 | 
					    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, //1970-1979
 | 
				
			||||||
    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
 | 
					    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, //1980-1989
 | 
				
			||||||
    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
 | 
					    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, //1990-1999
 | 
				
			||||||
    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
 | 
					    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, //2000-2009
 | 
				
			||||||
    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
 | 
					    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, //2010-2019
 | 
				
			||||||
    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
 | 
					    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, //2020-2029
 | 
				
			||||||
    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
 | 
					    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, //2030-2039
 | 
				
			||||||
    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
 | 
					    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, //2040-2049
 | 
				
			||||||
    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
 | 
					    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06aa0, 0x1a6c4, 0x0aae0, //2050-2059
 | 
				
			||||||
    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
 | 
					    0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, //2060-2069
 | 
				
			||||||
    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
 | 
					    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, //2070-2079
 | 
				
			||||||
    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
 | 
					    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, //2080-2089
 | 
				
			||||||
    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
 | 
					    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, //2090-2099
 | 
				
			||||||
    0x0d520
 | 
					    0x0d520 //2100
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 农历月份名称
 | 
					// 农历月份名称
 | 
				
			||||||
@@ -122,10 +134,7 @@ int LunarCalendar::getDaysInMonth(int year, int month, bool isLeap) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 判断公历年是否为闰年
 | 
					
 | 
				
			||||||
bool LunarCalendar::isLeapYear(int year) {
 | 
					 | 
				
			||||||
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 获取公历日期在当年的天数
 | 
					// 获取公历日期在当年的天数
 | 
				
			||||||
int LunarCalendar::getDayOfYear(int year, int month, int day) {
 | 
					int LunarCalendar::getDayOfYear(int year, int month, int day) {
 | 
				
			||||||
@@ -139,74 +148,158 @@ int LunarCalendar::getDayOfYear(int year, int month, int day) {
 | 
				
			|||||||
    return dayOfYear;
 | 
					    return dayOfYear;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 核心转换算法
 | 
					// 计算公历年份是否为闰年
 | 
				
			||||||
 | 
					bool LunarCalendar::isLeapYear(int year) {
 | 
				
			||||||
 | 
					    return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 计算两个公历日期之间的天数间隔
 | 
				
			||||||
 | 
					uint32_t LunarCalendar::calcSolarDateInterval(int year1, int month1, int day1, int year2, int month2, int day2) {
 | 
				
			||||||
 | 
					    uint8_t monthDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 | 
				
			||||||
 | 
					    uint32_t totalDays1 = 0;
 | 
				
			||||||
 | 
					    uint32_t totalDays2 = 0;
 | 
				
			||||||
 | 
					    uint32_t totalDays = 0;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 计算第一个日期从年初到该日期的天数
 | 
				
			||||||
 | 
					    monthDays[1] = (isLeapYear(year1) ? 29 : 28);
 | 
				
			||||||
 | 
					    for (int i = 1; i < month1; i++) {
 | 
				
			||||||
 | 
					        totalDays1 += monthDays[i - 1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    totalDays1 += day1;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 计算第二个日期从年初到该日期的天数
 | 
				
			||||||
 | 
					    monthDays[1] = (isLeapYear(year2) ? 29 : 28);
 | 
				
			||||||
 | 
					    for (int i = 1; i < month2; i++) {
 | 
				
			||||||
 | 
					        totalDays2 += monthDays[i - 1];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    totalDays2 += day2;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 如果是同一年
 | 
				
			||||||
 | 
					    if (year1 == year2) {
 | 
				
			||||||
 | 
					        totalDays = totalDays2 - totalDays1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // 计算两年之间的天数
 | 
				
			||||||
 | 
					        for (int i = year1 + 1; i < year2; i++) {
 | 
				
			||||||
 | 
					            totalDays += (isLeapYear(i) ? 366 : 365);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        totalDays += ((isLeapYear(year1) ? 366 : 365) - totalDays1 + totalDays2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return totalDays;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 计算某农历年是否有闰月
 | 
				
			||||||
 | 
					bool LunarCalendar::hasLeapMonth(int solarYear, int& leapMonth, bool& leapIs30Days) {
 | 
				
			||||||
 | 
					    if (solarYear < 1900 || solarYear > 2100) return false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    uint32_t info = lunarInfo[solarYear - 1900];
 | 
				
			||||||
 | 
					    leapMonth = info & 0x0000000f;  // 低4位表示闰月月份
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (leapMonth == 0 || leapMonth > 12) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    leapIs30Days = (info >> 16) & 0x00000001;  // bit16表示闰月大小
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 计算某农历月的天数
 | 
				
			||||||
 | 
					int LunarCalendar::getLunarMonthDays(int solarYear, int lunarMonth) {
 | 
				
			||||||
 | 
					    if (solarYear < 1900 || solarYear > 2100) return -1;
 | 
				
			||||||
 | 
					    if (lunarMonth < 1 || lunarMonth > 12) return -1;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    uint32_t info = lunarInfo[solarYear - 1900];
 | 
				
			||||||
 | 
					    // 农历1月对应bit15,2月对应bit14,...,12月对应bit4
 | 
				
			||||||
 | 
					    if ((info >> ((12 - lunarMonth) + 4)) & 0x00000001) {
 | 
				
			||||||
 | 
					        return 30;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return 29;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 根据天数间隔计算农历日期
 | 
				
			||||||
 | 
					bool LunarCalendar::calcLunarFromInterval(uint32_t dateInterval, LunarDate& lunar) {
 | 
				
			||||||
 | 
					    int solarYear = 1900;
 | 
				
			||||||
 | 
					    uint32_t tempInterval = 0;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 初始化农历日期 (1900年正月初一)
 | 
				
			||||||
 | 
					    lunar.year = 1900;
 | 
				
			||||||
 | 
					    lunar.month = 1;
 | 
				
			||||||
 | 
					    lunar.day = 1;
 | 
				
			||||||
 | 
					    lunar.isLeapMonth = false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    while (true) {
 | 
				
			||||||
 | 
					        int leapMonth = 0;
 | 
				
			||||||
 | 
					        bool leapIs30Days = false;
 | 
				
			||||||
 | 
					        bool hasLeap = hasLeapMonth(solarYear, leapMonth, leapIs30Days);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 如果当前月是闰月
 | 
				
			||||||
 | 
					        if (lunar.isLeapMonth) {
 | 
				
			||||||
 | 
					            tempInterval += leapIs30Days ? 30 : 29;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 当前月是正常月
 | 
				
			||||||
 | 
					            tempInterval += getLunarMonthDays(solarYear, lunar.month);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 如果总天数已达到目标
 | 
				
			||||||
 | 
					        if (tempInterval >= dateInterval) {
 | 
				
			||||||
 | 
					            if (lunar.isLeapMonth) {
 | 
				
			||||||
 | 
					                lunar.day = (leapIs30Days ? 30 : 29) - (tempInterval - dateInterval);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                lunar.day = getLunarMonthDays(solarYear, lunar.month) - (tempInterval - dateInterval);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 处理月份递增
 | 
				
			||||||
 | 
					        if (hasLeap && !lunar.isLeapMonth && leapMonth == lunar.month) {
 | 
				
			||||||
 | 
					            // 进入闰月
 | 
				
			||||||
 | 
					            lunar.isLeapMonth = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // 进入下一个月
 | 
				
			||||||
 | 
					            lunar.isLeapMonth = false;
 | 
				
			||||||
 | 
					            lunar.month++;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 年份递增
 | 
				
			||||||
 | 
					            if (lunar.month > 12) {
 | 
				
			||||||
 | 
					                solarYear++;
 | 
				
			||||||
 | 
					                if (solarYear > 2100) return false;
 | 
				
			||||||
 | 
					                lunar.year = solarYear;
 | 
				
			||||||
 | 
					                lunar.month = 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 核心转换算法 - 基于标准农历算法重新实现
 | 
				
			||||||
void LunarCalendar::calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
					void LunarCalendar::calculateLunar(int solarYear, int solarMonth, int solarDay, 
 | 
				
			||||||
                                  int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap) {
 | 
					                                  int& lunarYear, int& lunarMonth, int& lunarDay, bool& isLeap) {
 | 
				
			||||||
    // 1900年1月31日为农历1900年正月初一
 | 
					    // 边界检查
 | 
				
			||||||
    int baseYear = 1900;
 | 
					    if (solarYear < 1900 || solarYear > 2100) {
 | 
				
			||||||
    int baseMonth = 1;
 | 
					        lunarYear = solarYear;
 | 
				
			||||||
    int baseDay = 31;
 | 
					        lunarMonth = 1;
 | 
				
			||||||
    
 | 
					        lunarDay = 1;
 | 
				
			||||||
    // 计算距离基准日期的天数
 | 
					        isLeap = false;
 | 
				
			||||||
    int offset = 0;
 | 
					        return;
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 计算年份差
 | 
					 | 
				
			||||||
    for (int i = baseYear; i < solarYear; i++) {
 | 
					 | 
				
			||||||
        offset += isLeapYear(i) ? 366 : 365;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 加上当年已过天数
 | 
					    // 计算与基准日期(1900年1月31日,农历正月初一)的天数差
 | 
				
			||||||
    offset += getDayOfYear(solarYear, solarMonth, solarDay) - getDayOfYear(baseYear, baseMonth, baseDay);
 | 
					    uint32_t interval = calcSolarDateInterval(1900, 1, 31, solarYear, solarMonth, solarDay) + 1;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 计算农历日期
 | 
					    // 根据天数间隔计算农历日期
 | 
				
			||||||
    lunarYear = baseYear;
 | 
					    LunarDate result;
 | 
				
			||||||
    int daysInLunarYear = getDaysInYear(lunarYear);
 | 
					    if (!calcLunarFromInterval(interval, result)) {
 | 
				
			||||||
    
 | 
					        lunarYear = solarYear;
 | 
				
			||||||
    while (offset >= daysInLunarYear) {
 | 
					        lunarMonth = 1;
 | 
				
			||||||
        offset -= daysInLunarYear;
 | 
					        lunarDay = 1;
 | 
				
			||||||
        lunarYear++;
 | 
					        isLeap = false;
 | 
				
			||||||
        daysInLunarYear = getDaysInYear(lunarYear);
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 计算月份
 | 
					    lunarYear = result.year;
 | 
				
			||||||
    lunarMonth = 1;
 | 
					    lunarMonth = result.month;
 | 
				
			||||||
    isLeap = false;
 | 
					    lunarDay = result.day;
 | 
				
			||||||
    int leapMonth = getLeapMonth(lunarYear);
 | 
					    isLeap = result.isLeapMonth;
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    while (offset > 0 && lunarMonth <= 12) {
 | 
					 | 
				
			||||||
        int daysInCurrentMonth;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (lunarMonth == leapMonth && !isLeap && leapMonth > 0) {
 | 
					 | 
				
			||||||
            // 闰月
 | 
					 | 
				
			||||||
            daysInCurrentMonth = getDaysInMonth(lunarYear, lunarMonth, true);
 | 
					 | 
				
			||||||
            if (offset >= daysInCurrentMonth) {
 | 
					 | 
				
			||||||
                offset -= daysInCurrentMonth;
 | 
					 | 
				
			||||||
                isLeap = true;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                isLeap = true;
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 正常月份
 | 
					 | 
				
			||||||
        daysInCurrentMonth = getDaysInMonth(lunarYear, lunarMonth, false);
 | 
					 | 
				
			||||||
        if (offset >= daysInCurrentMonth) {
 | 
					 | 
				
			||||||
            offset -= daysInCurrentMonth;
 | 
					 | 
				
			||||||
            if (isLeap) {
 | 
					 | 
				
			||||||
                isLeap = false;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                lunarMonth++;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 确保月份在有效范围内
 | 
					 | 
				
			||||||
    if (lunarMonth > 12) lunarMonth = 12;
 | 
					 | 
				
			||||||
    if (lunarMonth < 1) lunarMonth = 1;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    lunarDay = offset + 1;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 主要转换函数
 | 
					// 主要转换函数
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ extern "C" {
 | 
				
			|||||||
// 预处理参数
 | 
					// 预处理参数
 | 
				
			||||||
#define MEL_FMIN 0.0f               // Mel滤波器最低频率
 | 
					#define MEL_FMIN 0.0f               // Mel滤波器最低频率
 | 
				
			||||||
#define MEL_FMAX 8000.0f            // Mel滤波器最高频率
 | 
					#define MEL_FMAX 8000.0f            // Mel滤波器最高频率
 | 
				
			||||||
#define WINDOW_TYPE_HANN 1          // 汉宁窗
 | 
					#define WINDOW_TYPE_HANN 1          // 海宁窗
 | 
				
			||||||
#define ENERGY_THRESHOLD 0.01f      // 音频活动检测阈值
 | 
					#define ENERGY_THRESHOLD 0.01f      // 音频活动检测阈值
 | 
				
			||||||
#define CONFIDENCE_THRESHOLD 0.6f   // 预测置信度阈值
 | 
					#define CONFIDENCE_THRESHOLD 0.6f   // 预测置信度阈值
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -100,6 +100,8 @@ float calculate_rms_energy(const int16_t* audio_data, int length);
 | 
				
			|||||||
void audio_model_cleanup(void);
 | 
					void audio_model_cleanup(void);
 | 
				
			||||||
const unsigned char* get_audio_model_data(void);
 | 
					const unsigned char* get_audio_model_data(void);
 | 
				
			||||||
size_t get_audio_model_size(void);
 | 
					size_t get_audio_model_size(void);
 | 
				
			||||||
 | 
					const char* get_class_name_en(AudioClassType class_id);
 | 
				
			||||||
 | 
					const char* get_class_name_cn(AudioClassType class_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== 核心API函数 ====================
 | 
					// ==================== 核心API函数 ====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -124,7 +126,7 @@ int audio_model_init(void) {
 | 
				
			|||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 预计算汉宁窗
 | 
					    // 预计算海宁窗
 | 
				
			||||||
    for (int i = 0; i < N_FFT; i++) {
 | 
					    for (int i = 0; i < N_FFT; i++) {
 | 
				
			||||||
        g_preprocessor.window_buffer[i] = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (N_FFT - 1)));
 | 
					        g_preprocessor.window_buffer[i] = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (N_FFT - 1)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -185,7 +187,7 @@ int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredic
 | 
				
			|||||||
        strcpy(g_last_error, "无效参数");
 | 
					        strcpy(g_last_error, "无效参数");
 | 
				
			||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    uint32_t start_time = micros();
 | 
					    uint32_t start_time = micros();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 添加看门狗喂狗
 | 
					    // 添加看门狗喂狗
 | 
				
			||||||
@@ -193,128 +195,73 @@ int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredic
 | 
				
			|||||||
    yield();
 | 
					    yield();
 | 
				
			||||||
    #endif
 | 
					    #endif
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 使用栈上的小缓冲区替代大数组,减少内存使用
 | 
					    // TODO: 集成TensorFlow Lite模型进行真实的音频识别
 | 
				
			||||||
    const int REDUCED_FEATURES = 32;  // 减少特征数量
 | 
					    // 当前使用audio_model_data.h中的TensorFlow Lite模型数据
 | 
				
			||||||
    float mel_features[REDUCED_FEATURES];
 | 
					    // 需要实现以下步骤:
 | 
				
			||||||
 | 
					    // 1. 初始化TensorFlow Lite解释器
 | 
				
			||||||
 | 
					    // 2. 加载模型数据 (audio_model_data)
 | 
				
			||||||
 | 
					    // 3. 预处理音频数据为模型输入格式
 | 
				
			||||||
 | 
					    // 4. 执行推理
 | 
				
			||||||
 | 
					    // 5. 解析输出结果
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 简化的音频特征提取,避免复杂的Mel频谱图计算
 | 
					    // 临时实现:基于音频能量的简单分类,提供更合理的结果
 | 
				
			||||||
    if (preprocess_audio_to_mel_simple(audio_data, audio_length, mel_features, REDUCED_FEATURES) != 0) {
 | 
					 | 
				
			||||||
        strcpy(g_last_error, "音频预处理失败");
 | 
					 | 
				
			||||||
        return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 添加看门狗喂狗
 | 
					 | 
				
			||||||
    #ifdef ARDUINO
 | 
					    #ifdef ARDUINO
 | 
				
			||||||
    yield();
 | 
					    Serial.println("警告:当前使用临时实现,等待TensorFlow Lite模型集成");
 | 
				
			||||||
    #endif
 | 
					    #endif
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 使用简化的特征分析替代复杂的TensorFlow Lite推理
 | 
					    // 计算音频能量来做简单的分类判断
 | 
				
			||||||
    // 计算基本统计特征
 | 
					    float rms_energy = calculate_rms_energy(audio_data, audio_length);
 | 
				
			||||||
    float mean_energy = 0.0f;
 | 
					 | 
				
			||||||
    float energy_variance = 0.0f;
 | 
					 | 
				
			||||||
    float max_energy = -1000.0f;
 | 
					 | 
				
			||||||
    float min_energy = 1000.0f;
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 计算平均能量和能量范围
 | 
					    // 添加调试信息:检查音频数据的实际值
 | 
				
			||||||
    for (int i = 0; i < REDUCED_FEATURES; i++) {
 | 
					    #ifdef ARDUINO
 | 
				
			||||||
        mean_energy += mel_features[i];
 | 
					    int non_zero_count = 0;
 | 
				
			||||||
        if (mel_features[i] > max_energy) max_energy = mel_features[i];
 | 
					    int16_t min_val = 32767, max_val = -32768;
 | 
				
			||||||
        if (mel_features[i] < min_energy) min_energy = mel_features[i];
 | 
					    long long sum_abs = 0;
 | 
				
			||||||
        
 | 
					    
 | 
				
			||||||
        // 定期喂狗
 | 
					    for (int i = 0; i < min(100, audio_length); i++) {  // 检查前100个样本
 | 
				
			||||||
        if (i % 10 == 0) {
 | 
					        if (audio_data[i] != 0) non_zero_count++;
 | 
				
			||||||
            #ifdef ARDUINO
 | 
					        if (audio_data[i] < min_val) min_val = audio_data[i];
 | 
				
			||||||
            yield();
 | 
					        if (audio_data[i] > max_val) max_val = audio_data[i];
 | 
				
			||||||
            #endif
 | 
					        sum_abs += abs(audio_data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.printf("音频数据调试: 非零样本=%d/100, 最小值=%d, 最大值=%d, 平均绝对值=%lld\n", 
 | 
				
			||||||
 | 
					                  non_zero_count, min_val, max_val, sum_abs/100);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 基于能量水平进行简单分类
 | 
				
			||||||
 | 
					    AudioClassType predicted_class;
 | 
				
			||||||
 | 
					    float confidence;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (rms_energy > 0.1f) {
 | 
				
			||||||
 | 
					        // 高能量:可能是关门声或钥匙声
 | 
				
			||||||
 | 
					        if (rms_energy > 0.3f) {
 | 
				
			||||||
 | 
					            predicted_class = AUDIO_CLASS_DOOR_CLOSING;
 | 
				
			||||||
 | 
					            confidence = 0.75f;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            predicted_class = AUDIO_CLASS_KEY_JINGLING;
 | 
				
			||||||
 | 
					            confidence = 0.65f;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    } else if (rms_energy > 0.02f) {
 | 
				
			||||||
    mean_energy /= REDUCED_FEATURES;
 | 
					        // 中等能量:室内有人
 | 
				
			||||||
    
 | 
					        predicted_class = AUDIO_CLASS_PERSON_PRESENT;
 | 
				
			||||||
    // 计算能量方差
 | 
					        confidence = 0.70f;
 | 
				
			||||||
    for (int i = 0; i < REDUCED_FEATURES; i++) {
 | 
					    } else {
 | 
				
			||||||
        float diff = mel_features[i] - mean_energy;
 | 
					        // 低能量:室内无人
 | 
				
			||||||
        energy_variance += diff * diff;
 | 
					        predicted_class = AUDIO_CLASS_PERSON_ABSENT;
 | 
				
			||||||
    }
 | 
					        confidence = 0.80f;
 | 
				
			||||||
    energy_variance /= REDUCED_FEATURES;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 添加看门狗喂狗
 | 
					 | 
				
			||||||
    #ifdef ARDUINO
 | 
					 | 
				
			||||||
    yield();
 | 
					 | 
				
			||||||
    #endif
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 基于简化特征的分类逻辑
 | 
					 | 
				
			||||||
    memset(result->class_probabilities, 0, sizeof(result->class_probabilities));
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 使用能量和方差进行简单分类
 | 
					 | 
				
			||||||
    float energy_range = max_energy - min_energy;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 钥匙声特征:中等能量,高方差
 | 
					 | 
				
			||||||
    float key_score = 0.0f;
 | 
					 | 
				
			||||||
    if (mean_energy > -5.0f && mean_energy < -2.0f && energy_variance > 2.0f) {
 | 
					 | 
				
			||||||
        key_score = 0.4f;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 关门声特征:高能量,低方差
 | 
					    // 设置结果
 | 
				
			||||||
    float door_score = 0.0f;
 | 
					    result->predicted_class = predicted_class;
 | 
				
			||||||
    if (mean_energy > -2.0f && energy_variance < 1.0f) {
 | 
					    result->confidence = confidence;
 | 
				
			||||||
        door_score = 0.5f;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 人员活动声特征:中等能量,中等方差
 | 
					    // 设置概率分布
 | 
				
			||||||
    float person_score = 0.0f;
 | 
					 | 
				
			||||||
    if (mean_energy > -6.0f && mean_energy < -1.0f && energy_variance > 0.5f && energy_variance < 3.0f) {
 | 
					 | 
				
			||||||
        person_score = 0.3f;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 无人声特征:低能量,低方差
 | 
					 | 
				
			||||||
    float absent_score = 0.0f;
 | 
					 | 
				
			||||||
    if (mean_energy < -8.0f && energy_variance < 0.5f) {
 | 
					 | 
				
			||||||
        absent_score = 0.6f;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 添加看门狗喂狗
 | 
					 | 
				
			||||||
    #ifdef ARDUINO
 | 
					 | 
				
			||||||
    yield();
 | 
					 | 
				
			||||||
    #endif
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 归一化概率
 | 
					 | 
				
			||||||
    float total_score = key_score + door_score + person_score + absent_score;
 | 
					 | 
				
			||||||
    if (total_score < 0.1f) {
 | 
					 | 
				
			||||||
        // 默认为有人状态
 | 
					 | 
				
			||||||
        person_score = 0.4f;
 | 
					 | 
				
			||||||
        total_score = 0.4f;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 添加少量随机性模拟AI不确定性
 | 
					 | 
				
			||||||
    uint32_t audio_hash = 0;
 | 
					 | 
				
			||||||
    for (int i = 0; i < audio_length; i += 1000) {
 | 
					 | 
				
			||||||
        audio_hash = audio_hash * 31 + (uint32_t)abs(audio_data[i]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    float noise_factor = (float)(audio_hash % 50) / 1000.0f; // 0-0.05的随机因子
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    result->class_probabilities[AUDIO_CLASS_KEY_JINGLING] = (key_score / total_score) + noise_factor;
 | 
					 | 
				
			||||||
    result->class_probabilities[AUDIO_CLASS_DOOR_CLOSING] = (door_score / total_score) + noise_factor * 0.8f;
 | 
					 | 
				
			||||||
    result->class_probabilities[AUDIO_CLASS_PERSON_PRESENT] = (person_score / total_score) + noise_factor * 0.6f;
 | 
					 | 
				
			||||||
    result->class_probabilities[AUDIO_CLASS_PERSON_ABSENT] = (absent_score / total_score) + noise_factor * 0.4f;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 重新归一化
 | 
					 | 
				
			||||||
    float prob_sum = 0.0f;
 | 
					 | 
				
			||||||
    for (int i = 0; i < NUM_CLASSES; i++) {
 | 
					    for (int i = 0; i < NUM_CLASSES; i++) {
 | 
				
			||||||
        prob_sum += result->class_probabilities[i];
 | 
					        if (i == (int)predicted_class) {
 | 
				
			||||||
    }
 | 
					            result->class_probabilities[i] = confidence;
 | 
				
			||||||
    if (prob_sum > 0) {
 | 
					        } else {
 | 
				
			||||||
        for (int i = 0; i < NUM_CLASSES; i++) {
 | 
					            result->class_probabilities[i] = (1.0f - confidence) / (NUM_CLASSES - 1);
 | 
				
			||||||
            result->class_probabilities[i] /= prob_sum;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 找到最高概率的类别
 | 
					 | 
				
			||||||
    result->confidence = 0.0f;
 | 
					 | 
				
			||||||
    result->predicted_class = AUDIO_CLASS_PERSON_PRESENT;
 | 
					 | 
				
			||||||
    for (int i = 0; i < NUM_CLASSES; i++) {
 | 
					 | 
				
			||||||
        if (result->class_probabilities[i] > result->confidence) {
 | 
					 | 
				
			||||||
            result->confidence = result->class_probabilities[i];
 | 
					 | 
				
			||||||
            result->predicted_class = (AudioClassType)i;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -329,6 +276,11 @@ int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredic
 | 
				
			|||||||
        g_total_confidence += result->confidence;
 | 
					        g_total_confidence += result->confidence;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    #ifdef ARDUINO
 | 
				
			||||||
 | 
					    Serial.printf("音频能量: %.4f, 预测类别: %s, 置信度: %.2f\n", 
 | 
				
			||||||
 | 
					                  rms_energy, get_class_name_cn(predicted_class), confidence);
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -406,22 +358,16 @@ int preprocess_audio_to_mel_simple(const int16_t* audio_data, int audio_length,
 | 
				
			|||||||
        int16_t prev_sample = 0;
 | 
					        int16_t prev_sample = 0;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        for (int i = start_idx; i < end_idx; i++) {
 | 
					        for (int i = start_idx; i < end_idx; i++) {
 | 
				
			||||||
            // 音频增益放大20倍,然后进行能量计算
 | 
					            float sample = (float)audio_data[i] / 32768.0f;
 | 
				
			||||||
            int32_t amplified_sample = (int32_t)audio_data[i] * 20;
 | 
					 | 
				
			||||||
            // 防止溢出,限制在int16_t范围内
 | 
					 | 
				
			||||||
            if (amplified_sample > 32767) amplified_sample = 32767;
 | 
					 | 
				
			||||||
            if (amplified_sample < -32768) amplified_sample = -32768;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            float sample = (float)amplified_sample / 32768.0f;
 | 
					 | 
				
			||||||
            energy += sample * sample;
 | 
					            energy += sample * sample;
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 零交叉率计算 - 使用放大后的音频数据
 | 
					            // 零交叉率计算
 | 
				
			||||||
            if (i > start_idx && 
 | 
					            if (i > start_idx && 
 | 
				
			||||||
                ((amplified_sample >= 0 && prev_sample < 0) || 
 | 
					                ((audio_data[i] >= 0 && prev_sample < 0) || 
 | 
				
			||||||
                 (amplified_sample < 0 && prev_sample >= 0))) {
 | 
					                 (audio_data[i] < 0 && prev_sample >= 0))) {
 | 
				
			||||||
                zero_crossings += 1.0f;
 | 
					                zero_crossings += 1.0f;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            prev_sample = (int16_t)amplified_sample;
 | 
					            prev_sample = audio_data[i];
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 添加看门狗喂狗,防止长时间计算
 | 
					            // 添加看门狗喂狗,防止长时间计算
 | 
				
			||||||
            #ifdef ARDUINO
 | 
					            #ifdef ARDUINO
 | 
				
			||||||
@@ -479,22 +425,16 @@ int preprocess_audio_to_mel(const int16_t* audio_data, int audio_length, float*
 | 
				
			|||||||
        int16_t prev_sample = 0;
 | 
					        int16_t prev_sample = 0;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        for (int i = start_idx; i < end_idx; i++) {
 | 
					        for (int i = start_idx; i < end_idx; i++) {
 | 
				
			||||||
            // 音频增益放大20倍,然后进行能量计算
 | 
					            float sample = (float)audio_data[i] / 32768.0f;
 | 
				
			||||||
            int32_t amplified_sample = (int32_t)audio_data[i] * 20;
 | 
					 | 
				
			||||||
            // 防止溢出,限制在int16_t范围内
 | 
					 | 
				
			||||||
            if (amplified_sample > 32767) amplified_sample = 32767;
 | 
					 | 
				
			||||||
            if (amplified_sample < -32768) amplified_sample = -32768;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            float sample = (float)amplified_sample / 32768.0f;
 | 
					 | 
				
			||||||
            energy += sample * sample;
 | 
					            energy += sample * sample;
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 零交叉率计算 - 使用放大后的音频数据
 | 
					            // 零交叉率计算
 | 
				
			||||||
            if (i > start_idx && 
 | 
					        if (i > start_idx && 
 | 
				
			||||||
                ((amplified_sample >= 0 && prev_sample < 0) || 
 | 
					            ((audio_data[i] >= 0 && prev_sample < 0) || 
 | 
				
			||||||
                 (amplified_sample < 0 && prev_sample >= 0))) {
 | 
					             (audio_data[i] < 0 && prev_sample >= 0))) {
 | 
				
			||||||
                zero_crossings += 1.0f;
 | 
					            zero_crossings += 1.0f;
 | 
				
			||||||
            }
 | 
					        }
 | 
				
			||||||
            prev_sample = (int16_t)amplified_sample;
 | 
					        prev_sample = audio_data[i];
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // 添加看门狗喂狗,防止长时间计算
 | 
					            // 添加看门狗喂狗,防止长时间计算
 | 
				
			||||||
            #ifdef ARDUINO
 | 
					            #ifdef ARDUINO
 | 
				
			||||||
@@ -670,13 +610,7 @@ float calculate_rms_energy(const int16_t* audio_data, int length) {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    float sum = 0.0f;
 | 
					    float sum = 0.0f;
 | 
				
			||||||
    for (int i = 0; i < length; i++) {
 | 
					    for (int i = 0; i < length; i++) {
 | 
				
			||||||
        // 音频增益放大20倍,然后计算RMS能量
 | 
					        float sample = (float)audio_data[i] / 32768.0f;  // 归一化到[-1,1]
 | 
				
			||||||
        int32_t amplified_sample = (int32_t)audio_data[i] * 20;
 | 
					 | 
				
			||||||
        // 防止溢出,限制在int16_t范围内
 | 
					 | 
				
			||||||
        if (amplified_sample > 32767) amplified_sample = 32767;
 | 
					 | 
				
			||||||
        if (amplified_sample < -32768) amplified_sample = -32768;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        float sample = (float)amplified_sample / 32768.0f;  // 归一化到[-1,1]
 | 
					 | 
				
			||||||
        sum += sample * sample;
 | 
					        sum += sample * sample;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return sqrtf(sum / length);
 | 
					    return sqrtf(sum / length);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								core.h
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								core.h
									
									
									
									
									
								
							@@ -188,10 +188,13 @@ int judge() {
 | 
				
			|||||||
    // 获取用户设置的温度范围 (从Preferences读取)
 | 
					    // 获取用户设置的温度范围 (从Preferences读取)
 | 
				
			||||||
    Preferences prefs;
 | 
					    Preferences prefs;
 | 
				
			||||||
    prefs.begin("DACSC", true); // 只读模式
 | 
					    prefs.begin("DACSC", true); // 只读模式
 | 
				
			||||||
    float min_temp = prefs.getFloat("min_temp", 5.0); // 默认最低温度22°C
 | 
					    float min_temp = prefs.getFloat("min_temp", 10.0); // 默认最低温度10°C
 | 
				
			||||||
    float max_temp = prefs.getFloat("max_temp", 28.0); // 默认最高温度26°C
 | 
					    float max_temp = prefs.getFloat("max_temp", 28.0); // 默认最高温度28°C
 | 
				
			||||||
    prefs.end();
 | 
					    prefs.end();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    // 打印当前使用的温度设置
 | 
				
			||||||
 | 
					    Serial.printf("当前温度设置 - 最低: %.1f°C, 最高: %.1f°C\n", min_temp, max_temp);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // 判断逻辑:基于节假日、音频识别、时间、温度和湿度的智能控制
 | 
					    // 判断逻辑:基于节假日、音频识别、时间、温度和湿度的智能控制
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 规则1:节假日规则(优先级最高)
 | 
					    // 规则1:节假日规则(优先级最高)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										432
									
								
								ir_control.cpp
									
									
									
									
									
								
							
							
						
						
									
										432
									
								
								ir_control.cpp
									
									
									
									
									
								
							@@ -1,169 +1,336 @@
 | 
				
			|||||||
#include "ir_control.h"
 | 
					#include "ir_control.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IRremoteESP8266库的接收器对象
 | 
				
			||||||
 | 
					// 增加缓冲区大小到500以支持长信号,超时设置为15ms
 | 
				
			||||||
 | 
					IRrecv irrecv(IR_RECEIVE_PIN, 500, 15);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void initIRControl() {
 | 
					void initIRControl() {
 | 
				
			||||||
    pinMode(IR_RECEIVE_PIN, INPUT);
 | 
					    // 配置RMT发送通道
 | 
				
			||||||
    pinMode(IR_SEND_PIN, OUTPUT);
 | 
					    rmt_config_t rmt_tx_config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)IR_SEND_PIN, RMT_TX_CHANNEL);
 | 
				
			||||||
    digitalWrite(IR_SEND_PIN, LOW);
 | 
					    rmt_tx_config.clk_div = RMT_CLK_DIV;
 | 
				
			||||||
 | 
					    rmt_tx_config.mem_block_num = RMT_MEM_BLOCK_NUM;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.carrier_en = RMT_TX_CARRIER_EN;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.carrier_freq_hz = RMT_TX_CARRIER_FREQ_HZ;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.carrier_duty_percent = RMT_TX_CARRIER_DUTY_PERCENT;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.carrier_level = RMT_TX_CARRIER_LEVEL;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.idle_level = RMT_IDLE_LEVEL;
 | 
				
			||||||
 | 
					    rmt_tx_config.tx_config.idle_output_en = true;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("红外控制模块初始化完成");
 | 
					    esp_err_t err = rmt_config(&rmt_tx_config);
 | 
				
			||||||
    Serial.print("接收引脚: ");
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
    Serial.println(IR_RECEIVE_PIN);
 | 
					        Serial.printf("RMT发送通道配置失败: %s\n", esp_err_to_name(err));
 | 
				
			||||||
    Serial.print("发送引脚: ");
 | 
					        return;
 | 
				
			||||||
    Serial.println(IR_SEND_PIN);
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    err = rmt_driver_install(RMT_TX_CHANNEL, 0, 0);
 | 
				
			||||||
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
 | 
					        Serial.printf("RMT发送驱动安装失败: %s\n", esp_err_to_name(err));
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.println("红外控制模块初始化完成 (使用RMT发送 + IRremoteESP8266接收)");
 | 
				
			||||||
 | 
					    Serial.printf("发送引脚: %d (RMT通道: %d)\n", IR_SEND_PIN, RMT_TX_CHANNEL);
 | 
				
			||||||
 | 
					    Serial.printf("接收引脚: %d (IRremoteESP8266)\n", IR_RECEIVE_PIN);
 | 
				
			||||||
 | 
					    Serial.printf("载波频率: %d Hz\n", RMT_TX_CARRIER_FREQ_HZ);
 | 
				
			||||||
 | 
					    Serial.printf("时钟分辨率: %d us\n", RMT_CLK_DIV);
 | 
				
			||||||
 | 
					    Serial.printf("接收缓冲区大小: 500, 超时: 15ms\n");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 初始化IRremoteESP8266接收器
 | 
				
			||||||
 | 
					    irrecv.enableIRIn();  // 启用红外接收
 | 
				
			||||||
 | 
					    Serial.println("IRremoteESP8266接收器已启用");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool checkIRSignalStart() {
 | 
					bool checkIRSignalStart() {
 | 
				
			||||||
    return (digitalRead(IR_RECEIVE_PIN) == LOW);
 | 
					    // 使用IRremoteESP8266检查是否有信号开始
 | 
				
			||||||
 | 
					    decode_results results;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 尝试解码,但不等待完整信号
 | 
				
			||||||
 | 
					    if (irrecv.decode(&results, nullptr, 0, false)) {
 | 
				
			||||||
 | 
					        // 有信号开始,恢复接收状态
 | 
				
			||||||
 | 
					        irrecv.resume();
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
IRSignal receiveIRSignal() {
 | 
					IRSignal receiveIRSignal() {
 | 
				
			||||||
    IRSignal signal;
 | 
					    IRSignal signal;
 | 
				
			||||||
    signal.data = nullptr;
 | 
					    signal.markTimes = nullptr;
 | 
				
			||||||
    signal.length = 0;
 | 
					    signal.spaceTimes = nullptr;
 | 
				
			||||||
 | 
					    signal.markCount = 0;
 | 
				
			||||||
 | 
					    signal.spaceCount = 0;
 | 
				
			||||||
 | 
					    signal.carrierFreq = IR_CARRIER_FREQ;
 | 
				
			||||||
    signal.isValid = false;
 | 
					    signal.isValid = false;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("开始接收红外信号...");
 | 
					    Serial.println("开始接收红外信号 (使用IRremoteESP8266)...");
 | 
				
			||||||
 | 
					    Serial.println("请按下遥控器按键...");
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 分配内存存储信号数据
 | 
					    decode_results results;
 | 
				
			||||||
    signal.data = (unsigned int*)malloc(MAX_SIGNAL_LENGTH * sizeof(unsigned int));
 | 
					 | 
				
			||||||
    if (signal.data == nullptr) {
 | 
					 | 
				
			||||||
        Serial.println("内存分配失败");
 | 
					 | 
				
			||||||
        return signal;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    unsigned long startTime = micros();
 | 
					    // 清空接收缓冲区
 | 
				
			||||||
    unsigned long lastChange = startTime;
 | 
					    irrecv.resume();
 | 
				
			||||||
    bool currentState = HIGH;
 | 
					 | 
				
			||||||
    bool lastState = HIGH;
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 等待信号开始(第一个低电平)
 | 
					    // 等待接收完整的红外信号
 | 
				
			||||||
    while (digitalRead(IR_RECEIVE_PIN) == HIGH && (micros() - startTime) < RECEIVE_TIMEOUT_US) {
 | 
					    unsigned long startTime = millis();
 | 
				
			||||||
        delayMicroseconds(10);
 | 
					    while (millis() - startTime < (RECEIVE_TIMEOUT_US / 1000)) {
 | 
				
			||||||
    }
 | 
					        if (irrecv.decode(&results)) {
 | 
				
			||||||
    
 | 
					            Serial.printf("检测到红外信号!原始数据长度: %d\n", results.rawlen);
 | 
				
			||||||
    if ((micros() - startTime) >= RECEIVE_TIMEOUT_US) {
 | 
					            Serial.printf("Raw数组总长度: %d\n", results.rawlen);
 | 
				
			||||||
        Serial.println("等待信号超时");
 | 
					 | 
				
			||||||
        free(signal.data);
 | 
					 | 
				
			||||||
        signal.data = nullptr;
 | 
					 | 
				
			||||||
        return signal;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    Serial.println("检测到信号开始");
 | 
					 | 
				
			||||||
    lastChange = micros();
 | 
					 | 
				
			||||||
    lastState = LOW;
 | 
					 | 
				
			||||||
    signal.length = 0;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // 接收信号数据
 | 
					 | 
				
			||||||
    while (signal.length < MAX_SIGNAL_LENGTH) {
 | 
					 | 
				
			||||||
        currentState = digitalRead(IR_RECEIVE_PIN);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (currentState != lastState) {
 | 
					 | 
				
			||||||
            // 状态改变,记录持续时间
 | 
					 | 
				
			||||||
            unsigned long duration = micros() - lastChange;
 | 
					 | 
				
			||||||
            signal.data[signal.length] = duration;
 | 
					 | 
				
			||||||
            signal.length++;
 | 
					 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            lastState = currentState;
 | 
					            // 检查缓冲区溢出
 | 
				
			||||||
            lastChange = micros();
 | 
					            if (results.overflow) {
 | 
				
			||||||
        }
 | 
					                Serial.println("警告: 接收缓冲区溢出!信号可能不完整");
 | 
				
			||||||
        
 | 
					                Serial.println("建议: 增加缓冲区大小或检查信号长度");
 | 
				
			||||||
        // 检查是否信号结束(高电平持续时间过长)
 | 
					            }
 | 
				
			||||||
        if (currentState == HIGH && (micros() - lastChange) > SIGNAL_END_TIMEOUT_US) {
 | 
					            
 | 
				
			||||||
            Serial.println("检测到信号结束");
 | 
					            // 检查信号长度是否合理(典型红外信号应该有足够的数据)
 | 
				
			||||||
 | 
					            if (results.rawlen < 10) {
 | 
				
			||||||
 | 
					                Serial.printf("警告: 信号长度过短 (%d),可能不是完整信号\n", results.rawlen);
 | 
				
			||||||
 | 
					            } else if (results.rawlen >= 490) {  // 接近缓冲区上限
 | 
				
			||||||
 | 
					                Serial.printf("警告: 信号长度接近缓冲区上限 (%d/500),可能被截断\n", results.rawlen);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 打印接收到的协议信息
 | 
				
			||||||
 | 
					            Serial.printf("协议: %s\n", typeToString(results.decode_type).c_str());
 | 
				
			||||||
 | 
					            Serial.printf("值: 0x%08X\n", results.value);
 | 
				
			||||||
 | 
					            Serial.printf("位数: %d\n", results.bits);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (results.rawlen > 1) {
 | 
				
			||||||
 | 
					                // IRremoteESP8266的rawbuf包含原始时序数据
 | 
				
			||||||
 | 
					                // rawbuf[0]是未使用的,实际数据从rawbuf[1]开始
 | 
				
			||||||
 | 
					                // 数据格式:mark, space, mark, space, ...
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                int dataLength = results.rawlen - 1;  // 减去未使用的第一个元素
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 计算mark和space的数量
 | 
				
			||||||
 | 
					                signal.markCount = (dataLength + 1) / 2;  // 奇数位置是mark
 | 
				
			||||||
 | 
					                signal.spaceCount = dataLength / 2;       // 偶数位置是space
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Serial.printf("数据长度: %d, mark数量: %d, space数量: %d\n", 
 | 
				
			||||||
 | 
					                             dataLength, signal.markCount, signal.spaceCount);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 分配mark时间数组
 | 
				
			||||||
 | 
					                if (signal.markCount > 0) {
 | 
				
			||||||
 | 
					                    signal.markTimes = (unsigned int*)malloc(signal.markCount * sizeof(unsigned int));
 | 
				
			||||||
 | 
					                    if (signal.markTimes == nullptr) {
 | 
				
			||||||
 | 
					                        Serial.println("mark时间数组内存分配失败");
 | 
				
			||||||
 | 
					                        irrecv.resume();
 | 
				
			||||||
 | 
					                        return signal;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 提取mark时间(奇数索引:1, 3, 5, ...)
 | 
				
			||||||
 | 
					                    for (int i = 0; i < signal.markCount; i++) {
 | 
				
			||||||
 | 
					                        int rawIndex = i * 2 + 1;  // 1, 3, 5, ...
 | 
				
			||||||
 | 
					                        if (rawIndex < results.rawlen) {
 | 
				
			||||||
 | 
					                            // IRremoteESP8266的时间单位是tick,需要转换为微秒
 | 
				
			||||||
 | 
					                            // 默认tick = 50us
 | 
				
			||||||
 | 
					                            signal.markTimes[i] = results.rawbuf[rawIndex] * kRawTick;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 分配space时间数组
 | 
				
			||||||
 | 
					                if (signal.spaceCount > 0) {
 | 
				
			||||||
 | 
					                    signal.spaceTimes = (unsigned int*)malloc(signal.spaceCount * sizeof(unsigned int));
 | 
				
			||||||
 | 
					                    if (signal.spaceTimes == nullptr) {
 | 
				
			||||||
 | 
					                        Serial.println("space时间数组内存分配失败");
 | 
				
			||||||
 | 
					                        if (signal.markTimes != nullptr) {
 | 
				
			||||||
 | 
					                            free(signal.markTimes);
 | 
				
			||||||
 | 
					                            signal.markTimes = nullptr;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        irrecv.resume();
 | 
				
			||||||
 | 
					                        return signal;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 提取space时间(偶数索引:2, 4, 6, ...)
 | 
				
			||||||
 | 
					                    for (int i = 0; i < signal.spaceCount; i++) {
 | 
				
			||||||
 | 
					                        int rawIndex = i * 2 + 2;  // 2, 4, 6, ...
 | 
				
			||||||
 | 
					                        if (rawIndex < results.rawlen) {
 | 
				
			||||||
 | 
					                            // IRremoteESP8266的时间单位是tick,需要转换为微秒
 | 
				
			||||||
 | 
					                            signal.spaceTimes[i] = results.rawbuf[rawIndex] * kRawTick;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                signal.isValid = true;
 | 
				
			||||||
 | 
					                Serial.printf("IRremoteESP8266信号接收成功,mark数量: %d, space数量: %d\n", 
 | 
				
			||||||
 | 
					                             signal.markCount, signal.spaceCount);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 检查信号头部特征(典型的9000us mark + 4500us space)
 | 
				
			||||||
 | 
					                if (signal.markCount > 0 && signal.spaceCount > 0) {
 | 
				
			||||||
 | 
					                    unsigned int firstMark = signal.markTimes[0];
 | 
				
			||||||
 | 
					                    unsigned int firstSpace = signal.spaceTimes[0];
 | 
				
			||||||
 | 
					                    Serial.printf("信号头部: mark=%dus, space=%dus\n", firstMark, firstSpace);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 检查是否有典型的信号头
 | 
				
			||||||
 | 
					                    if (firstMark > 8000 && firstMark < 10000 && firstSpace > 3500 && firstSpace < 5500) {
 | 
				
			||||||
 | 
					                        Serial.println("检测到标准信号头 (约9ms mark + 4.5ms space)");
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        Serial.println("警告: 信号头不符合标准格式,可能丢失或不完整");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 打印前几个时序数据用于调试
 | 
				
			||||||
 | 
					                Serial.print("前几个时序数据(us): ");
 | 
				
			||||||
 | 
					                for (int i = 0; i < min(10, (int)results.rawlen - 1); i++) {
 | 
				
			||||||
 | 
					                    Serial.printf("%d ", results.rawbuf[i + 1] * kRawTick);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Serial.println();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 打印原始数据的十六进制表示
 | 
				
			||||||
 | 
					                Serial.print("原始数据(tick): ");
 | 
				
			||||||
 | 
					                for (int i = 1; i < min(11, (int)results.rawlen); i++) {
 | 
				
			||||||
 | 
					                    Serial.printf("%d ", results.rawbuf[i]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                Serial.println();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Serial.println("接收到的信号数据长度不足");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 恢复接收状态
 | 
				
			||||||
 | 
					            irrecv.resume();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // 总体超时检查
 | 
					        // 短暂延时避免CPU占用过高
 | 
				
			||||||
        if ((micros() - startTime) > RECEIVE_TIMEOUT_US) {
 | 
					        delay(10);
 | 
				
			||||||
            Serial.println("接收总体超时");
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (signal.length > 0) {
 | 
					    if (!signal.isValid) {
 | 
				
			||||||
        signal.isValid = true;
 | 
					        Serial.println("IRremoteESP8266接收超时或无有效信号");
 | 
				
			||||||
        Serial.print("信号接收成功,长度: ");
 | 
					        Serial.println("请检查:");
 | 
				
			||||||
        Serial.println(signal.length);
 | 
					        Serial.println("1. 遥控器是否有电");
 | 
				
			||||||
        
 | 
					        Serial.println("2. 红外接收器是否正确连接到引脚 " + String(IR_RECEIVE_PIN));
 | 
				
			||||||
        // 重新分配内存以节省空间
 | 
					        Serial.println("3. 遥控器是否对准红外接收器");
 | 
				
			||||||
        signal.data = (unsigned int*)realloc(signal.data, signal.length * sizeof(unsigned int));
 | 
					 | 
				
			||||||
        if (signal.data == nullptr) {
 | 
					 | 
				
			||||||
            Serial.println("内存重新分配失败");
 | 
					 | 
				
			||||||
            signal.isValid = false;
 | 
					 | 
				
			||||||
            signal.length = 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        Serial.println("未接收到有效信号");
 | 
					 | 
				
			||||||
        free(signal.data);
 | 
					 | 
				
			||||||
        signal.data = nullptr;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return signal;
 | 
					    return signal;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// generateCarrier函数已被RMT模块替代,不再需要
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool sendIRSignal(const IRSignal& signal) {
 | 
					bool sendIRSignal(const IRSignal& signal) {
 | 
				
			||||||
    if (!isValidIRSignal(signal)) {
 | 
					    if (!isValidIRSignal(signal)) {
 | 
				
			||||||
        Serial.println("信号无效,无法发送");
 | 
					        Serial.println("信号无效,无法发送");
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("开始发送红外信号...");
 | 
					    Serial.println("开始发送红外信号 (使用RMT)...");
 | 
				
			||||||
    Serial.print("信号长度: ");
 | 
					    Serial.printf("载波频率: %d Hz\n", signal.carrierFreq);
 | 
				
			||||||
    Serial.println(signal.length);
 | 
					    Serial.printf("mark数量: %d, space数量: %d\n", signal.markCount, signal.spaceCount);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 禁用中断以确保精确的时序
 | 
					    // 计算需要的RMT项目数量
 | 
				
			||||||
    noInterrupts();
 | 
					    int maxCount = max(signal.markCount, signal.spaceCount);
 | 
				
			||||||
 | 
					    size_t item_count = maxCount;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    bool state = LOW; // 红外信号通常以低电平开始
 | 
					    // 计算并显示raw数组总长度(mark + space的总数)
 | 
				
			||||||
 | 
					    int totalRawLength = signal.markCount + signal.spaceCount;
 | 
				
			||||||
 | 
					    Serial.printf("发送Raw数组总长度: %d (mark: %d + space: %d)\n", 
 | 
				
			||||||
 | 
					                  totalRawLength, signal.markCount, signal.spaceCount);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    for (int i = 0; i < signal.length; i++) {
 | 
					    // 分配RMT项目数组
 | 
				
			||||||
        digitalWrite(IR_SEND_PIN, state);
 | 
					    rmt_item32_t* items = (rmt_item32_t*)malloc(item_count * sizeof(rmt_item32_t));
 | 
				
			||||||
        delayMicroseconds(signal.data[i]);
 | 
					    if (items == nullptr) {
 | 
				
			||||||
        state = !state; // 切换状态
 | 
					        Serial.println("RMT项目内存分配失败");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 确保最后是低电平
 | 
					    // 构建RMT数据项
 | 
				
			||||||
    digitalWrite(IR_SEND_PIN, LOW);
 | 
					    for (int i = 0; i < maxCount; i++) {
 | 
				
			||||||
 | 
					        items[i].level0 = 1;  // mark期间为高电平(载波调制)
 | 
				
			||||||
 | 
					        items[i].duration0 = (i < signal.markCount) ? signal.markTimes[i] : 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        items[i].level1 = 0;  // space期间为低电平(无载波)
 | 
				
			||||||
 | 
					        items[i].duration1 = (i < signal.spaceCount) ? signal.spaceTimes[i] : 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 重新启用中断
 | 
					    // 发送RMT数据
 | 
				
			||||||
    interrupts();
 | 
					    esp_err_t err = rmt_write_items(RMT_TX_CHANNEL, items, item_count, true);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("信号发送完成");
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
 | 
					        Serial.printf("RMT发送失败: %s\n", esp_err_to_name(err));
 | 
				
			||||||
 | 
					        free(items);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 等待发送完成
 | 
				
			||||||
 | 
					    err = rmt_wait_tx_done(RMT_TX_CHANNEL, pdMS_TO_TICKS(1000));
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
 | 
					        Serial.printf("RMT发送等待超时: %s\n", esp_err_to_name(err));
 | 
				
			||||||
 | 
					        free(items);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    free(items);
 | 
				
			||||||
 | 
					    Serial.println("RMT信号发送完成");
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void freeIRSignal(IRSignal& signal) {
 | 
					void freeIRSignal(IRSignal& signal) {
 | 
				
			||||||
    if (signal.data != nullptr) {
 | 
					    if (signal.markTimes != nullptr) {
 | 
				
			||||||
        free(signal.data);
 | 
					        free(signal.markTimes);
 | 
				
			||||||
        signal.data = nullptr;
 | 
					        signal.markTimes = nullptr;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    signal.length = 0;
 | 
					    if (signal.spaceTimes != nullptr) {
 | 
				
			||||||
 | 
					        free(signal.spaceTimes);
 | 
				
			||||||
 | 
					        signal.spaceTimes = nullptr;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    signal.markCount = 0;
 | 
				
			||||||
 | 
					    signal.spaceCount = 0;
 | 
				
			||||||
 | 
					    signal.carrierFreq = 0;
 | 
				
			||||||
    signal.isValid = false;
 | 
					    signal.isValid = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
IRSignal copyIRSignal(const IRSignal& source) {
 | 
					IRSignal copyIRSignal(const IRSignal& source) {
 | 
				
			||||||
    IRSignal copy;
 | 
					    IRSignal copy;
 | 
				
			||||||
    copy.data = nullptr;
 | 
					    copy.markTimes = nullptr;
 | 
				
			||||||
    copy.length = 0;
 | 
					    copy.spaceTimes = nullptr;
 | 
				
			||||||
 | 
					    copy.markCount = 0;
 | 
				
			||||||
 | 
					    copy.spaceCount = 0;
 | 
				
			||||||
 | 
					    copy.carrierFreq = 0;
 | 
				
			||||||
    copy.isValid = false;
 | 
					    copy.isValid = false;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (!isValidIRSignal(source)) {
 | 
					    if (!isValidIRSignal(source)) {
 | 
				
			||||||
        return copy;
 | 
					        return copy;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 分配内存
 | 
					    // 复制mark时间数组
 | 
				
			||||||
    copy.data = (unsigned int*)malloc(source.length * sizeof(unsigned int));
 | 
					    if (source.markCount > 0 && source.markTimes != nullptr) {
 | 
				
			||||||
    if (copy.data == nullptr) {
 | 
					        copy.markTimes = (unsigned int*)malloc(source.markCount * sizeof(unsigned int));
 | 
				
			||||||
        Serial.println("复制信号时内存分配失败");
 | 
					        if (copy.markTimes == nullptr) {
 | 
				
			||||||
        return copy;
 | 
					            Serial.println("复制mark时间数组时内存分配失败");
 | 
				
			||||||
 | 
					            return copy;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (int i = 0; i < source.markCount; i++) {
 | 
				
			||||||
 | 
					            copy.markTimes[i] = source.markTimes[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        copy.markCount = source.markCount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // 复制数据
 | 
					    // 复制space时间数组
 | 
				
			||||||
    for (int i = 0; i < source.length; i++) {
 | 
					    if (source.spaceCount > 0 && source.spaceTimes != nullptr) {
 | 
				
			||||||
        copy.data[i] = source.data[i];
 | 
					        copy.spaceTimes = (unsigned int*)malloc(source.spaceCount * sizeof(unsigned int));
 | 
				
			||||||
 | 
					        if (copy.spaceTimes == nullptr) {
 | 
				
			||||||
 | 
					            Serial.println("复制space时间数组时内存分配失败");
 | 
				
			||||||
 | 
					            if (copy.markTimes != nullptr) {
 | 
				
			||||||
 | 
					                free(copy.markTimes);
 | 
				
			||||||
 | 
					                copy.markTimes = nullptr;
 | 
				
			||||||
 | 
					                copy.markCount = 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return copy;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (int i = 0; i < source.spaceCount; i++) {
 | 
				
			||||||
 | 
					            copy.spaceTimes[i] = source.spaceTimes[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        copy.spaceCount = source.spaceCount;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    copy.length = source.length;
 | 
					    copy.carrierFreq = source.carrierFreq;
 | 
				
			||||||
    copy.isValid = true;
 | 
					    copy.isValid = true;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return copy;
 | 
					    return copy;
 | 
				
			||||||
@@ -176,33 +343,56 @@ void printIRSignal(const IRSignal& signal, int maxPrint) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("=== 红外信号数据 ===");
 | 
					    Serial.println("=== 红外信号数据 ===");
 | 
				
			||||||
    Serial.print("信号长度: ");
 | 
					    Serial.print("载波频率: ");
 | 
				
			||||||
    Serial.println(signal.length);
 | 
					    Serial.print(signal.carrierFreq);
 | 
				
			||||||
 | 
					    Serial.println(" Hz");
 | 
				
			||||||
 | 
					    Serial.print("mark数量: ");
 | 
				
			||||||
 | 
					    Serial.println(signal.markCount);
 | 
				
			||||||
 | 
					    Serial.print("space数量: ");
 | 
				
			||||||
 | 
					    Serial.println(signal.spaceCount);
 | 
				
			||||||
    Serial.print("信号有效: ");
 | 
					    Serial.print("信号有效: ");
 | 
				
			||||||
    Serial.println(signal.isValid ? "是" : "否");
 | 
					    Serial.println(signal.isValid ? "是" : "否");
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    int printCount = (maxPrint == 0) ? signal.length : min(maxPrint, signal.length);
 | 
					    // 打印mark时间
 | 
				
			||||||
    
 | 
					    int markPrintCount = (maxPrint == 0) ? signal.markCount : min(maxPrint, signal.markCount);
 | 
				
			||||||
    Serial.println("原始数据 (微秒):");
 | 
					    Serial.println("Mark时间 (微秒):");
 | 
				
			||||||
    for (int i = 0; i < printCount; i++) {
 | 
					    for (int i = 0; i < markPrintCount; i++) {
 | 
				
			||||||
        Serial.print("Index ");
 | 
					        Serial.print("Mark ");
 | 
				
			||||||
        Serial.print(i);
 | 
					        Serial.print(i);
 | 
				
			||||||
        Serial.print(": ");
 | 
					        Serial.print(": ");
 | 
				
			||||||
        Serial.print(signal.data[i]);
 | 
					        Serial.print(signal.markTimes[i]);
 | 
				
			||||||
        Serial.print(" us (");
 | 
					        Serial.println(" us");
 | 
				
			||||||
        Serial.print((i % 2 == 0) ? "LOW" : "HIGH");
 | 
					 | 
				
			||||||
        Serial.println(")");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (printCount < signal.length) {
 | 
					    if (markPrintCount < signal.markCount) {
 | 
				
			||||||
        Serial.print("... 还有 ");
 | 
					        Serial.print("... 还有 ");
 | 
				
			||||||
        Serial.print(signal.length - printCount);
 | 
					        Serial.print(signal.markCount - markPrintCount);
 | 
				
			||||||
        Serial.println(" 个数据点未显示");
 | 
					        Serial.println(" 个mark数据点未显示");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 打印space时间
 | 
				
			||||||
 | 
					    int spacePrintCount = (maxPrint == 0) ? signal.spaceCount : min(maxPrint, signal.spaceCount);
 | 
				
			||||||
 | 
					    Serial.println("Space时间 (微秒):");
 | 
				
			||||||
 | 
					    for (int i = 0; i < spacePrintCount; i++) {
 | 
				
			||||||
 | 
					        Serial.print("Space ");
 | 
				
			||||||
 | 
					        Serial.print(i);
 | 
				
			||||||
 | 
					        Serial.print(": ");
 | 
				
			||||||
 | 
					        Serial.print(signal.spaceTimes[i]);
 | 
				
			||||||
 | 
					        Serial.println(" us");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (spacePrintCount < signal.spaceCount) {
 | 
				
			||||||
 | 
					        Serial.print("... 还有 ");
 | 
				
			||||||
 | 
					        Serial.print(signal.spaceCount - spacePrintCount);
 | 
				
			||||||
 | 
					        Serial.println(" 个space数据点未显示");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    Serial.println("==================");
 | 
					    Serial.println("==================");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool isValidIRSignal(const IRSignal& signal) {
 | 
					bool isValidIRSignal(const IRSignal& signal) {
 | 
				
			||||||
    return (signal.data != nullptr && signal.length > 0 && signal.isValid);
 | 
					    return (signal.isValid && 
 | 
				
			||||||
 | 
					            ((signal.markTimes != nullptr && signal.markCount > 0) || 
 | 
				
			||||||
 | 
					             (signal.spaceTimes != nullptr && signal.spaceCount > 0)) &&
 | 
				
			||||||
 | 
					            signal.carrierFreq > 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								ir_control.h
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								ir_control.h
									
									
									
									
									
								
							@@ -2,12 +2,19 @@
 | 
				
			|||||||
#define IR_CONTROL_H
 | 
					#define IR_CONTROL_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <Arduino.h>
 | 
					#include <Arduino.h>
 | 
				
			||||||
 | 
					#include "driver/rmt.h"
 | 
				
			||||||
 | 
					#include "driver/gpio.h"
 | 
				
			||||||
 | 
					#include <IRrecv.h>
 | 
				
			||||||
 | 
					#include <IRutils.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 红外信号结构体
 | 
					// 红外信号结构体
 | 
				
			||||||
struct IRSignal {
 | 
					struct IRSignal {
 | 
				
			||||||
    unsigned int* data;     // 原始信号数据数组(微秒)
 | 
					    unsigned int* markTimes;    // mark时间数组(微秒)
 | 
				
			||||||
    int length;            // 信号长度
 | 
					    unsigned int* spaceTimes;   // space时间数组(微秒)
 | 
				
			||||||
    bool isValid;          // 信号是否有效
 | 
					    int markCount;             // mark数量
 | 
				
			||||||
 | 
					    int spaceCount;            // space数量
 | 
				
			||||||
 | 
					    unsigned int carrierFreq;   // 载波频率(Hz)
 | 
				
			||||||
 | 
					    bool isValid;              // 信号是否有效
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 配置参数
 | 
					// 配置参数
 | 
				
			||||||
@@ -16,6 +23,22 @@ struct IRSignal {
 | 
				
			|||||||
#define MAX_SIGNAL_LENGTH 1000
 | 
					#define MAX_SIGNAL_LENGTH 1000
 | 
				
			||||||
#define RECEIVE_TIMEOUT_US 1000000  // 1秒接收超时
 | 
					#define RECEIVE_TIMEOUT_US 1000000  // 1秒接收超时
 | 
				
			||||||
#define SIGNAL_END_TIMEOUT_US 50000 // 50ms信号结束判断
 | 
					#define SIGNAL_END_TIMEOUT_US 50000 // 50ms信号结束判断
 | 
				
			||||||
 | 
					#define IR_CARRIER_FREQ 38000       // 38kHz载波频率
 | 
				
			||||||
 | 
					#define CARRIER_PERIOD_US 26        // 38kHz载波周期(微秒)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RMT配置参数
 | 
				
			||||||
 | 
					#define RMT_TX_CHANNEL RMT_CHANNEL_0    // RMT发送通道
 | 
				
			||||||
 | 
					#define RMT_RX_CHANNEL RMT_CHANNEL_1    // RMT接收通道
 | 
				
			||||||
 | 
					#define RMT_CLK_DIV 80                  // RMT时钟分频器 (80MHz / 80 = 1MHz, 1us分辨率)
 | 
				
			||||||
 | 
					#define RMT_MEM_BLOCK_NUM 1             // RMT内存块数量
 | 
				
			||||||
 | 
					#define RMT_TX_CARRIER_EN true          // 启用发送载波
 | 
				
			||||||
 | 
					#define RMT_TX_CARRIER_FREQ_HZ 38000    // 发送载波频率
 | 
				
			||||||
 | 
					#define RMT_TX_CARRIER_DUTY_PERCENT 33  // 载波占空比
 | 
				
			||||||
 | 
					#define RMT_TX_CARRIER_LEVEL RMT_CARRIER_LEVEL_HIGH  // 载波电平
 | 
				
			||||||
 | 
					#define RMT_IDLE_LEVEL RMT_IDLE_LEVEL_LOW            // 空闲电平
 | 
				
			||||||
 | 
					#define RMT_RX_FILTER_EN true           // 启用接收滤波器
 | 
				
			||||||
 | 
					#define RMT_RX_FILTER_THRESH_US 100     // 接收滤波器阈值(微秒)
 | 
				
			||||||
 | 
					#define RMT_RX_IDLE_THRESH_US 10000     // 接收空闲阈值(微秒)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 初始化红外控制模块
 | 
					 * 初始化红外控制模块
 | 
				
			||||||
@@ -33,9 +56,12 @@ bool checkIRSignalStart();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 接收红外信号
 | 
					 * 接收红外信号
 | 
				
			||||||
 * @return IRSignal 包含原始红外信号数据的结构体
 | 
					 * @return IRSignal 包含红外信号mark/space时序和载波频率的结构体
 | 
				
			||||||
 *         - data: 指向信号数据数组的指针
 | 
					 *         - markTimes: 指向mark时间数组的指针
 | 
				
			||||||
 *         - length: 信号数据长度
 | 
					 *         - spaceTimes: 指向space时间数组的指针
 | 
				
			||||||
 | 
					 *         - markCount: mark数量
 | 
				
			||||||
 | 
					 *         - spaceCount: space数量
 | 
				
			||||||
 | 
					 *         - carrierFreq: 载波频率
 | 
				
			||||||
 *         - isValid: 是否成功接收到有效信号
 | 
					 *         - isValid: 是否成功接收到有效信号
 | 
				
			||||||
 * 
 | 
					 * 
 | 
				
			||||||
 * 注意:调用者需要在使用完毕后调用 freeIRSignal() 释放内存
 | 
					 * 注意:调用者需要在使用完毕后调用 freeIRSignal() 释放内存
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user