Files
2026-05-21 08:07:16 +08:00

991 lines
33 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ======================================================================
// main.c (人脸识别考勤系统 - AES加密版)
// ======================================================================
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "K210_USART.h"
#include "Buzzer_LED.h"
#include "esp8266.h"
#include "Attendance.h"
#include "Debug.h"
#include "Crypto.h" // ← 新增:加密模块
#include "DS1302.h" // ← 新增:DS1302 RTC时钟模块
#include "LightSensor.h" // ← 新增:光敏电阻和补光灯模块
#include <stdio.h>
#include <string.h>
/* --- 用户配置区域 --- */
// 导出配置供ESP8266使用(直接定义字符串常量)
const char* WIFI_SSID = "yzfh";
const char* WIFI_PASSWORD = "yzfhroot";
const char* HTTP_SERVER_IP = "27.106.113.205"; // 修改为实际服务器IP
const char* HTTP_SERVER_PORT = "8082"; // HTTP端口(8082)
const char* HTTP_API_TOKEN = "atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV"; // 必须与服务端 ATTENDANCE_API_TOKEN 一致
/* --- 班级配置 --- */
// 每个设备对应一个班级,修改此处的班级代码
#define CLASS_ID "1" // 班级编号(如 "1", "2", "3" 等)
const char* DEVICE_CLASS_ID = CLASS_ID; // 导出给其他模块使用
/* --- 全局变量定义 --- */
volatile char g_k210_rx_buffer[K210_RX_BUFFER_SIZE];
volatile uint16_t g_k210_rx_counter = 0;
volatile uint8_t g_k210_rx_flag = 0;
uint8_t g_server_connected = 0;
uint8_t g_wifi_connected = 0; // WiFi连接状态标志
uint8_t g_esp8266_ready = 0; // ESP8266初始化状态
// 时间相关变量(使用DS1302 RTC)
ESP8266_DateTime g_current_time = {1970, 1, 1, 1, 0, 0}; // 默认时间(会被NTP覆盖)
uint32_t last_time_sync = 0;
uint32_t last_rtc_read = 0; // 上次从DS1302读取时间的时刻
uint8_t g_use_ds1302 = 0; // DS1302可用标志:0=不可用,1=可用
#define TIME_SYNC_INTERVAL_MS 3600000 // NTP同步间隔:1小时
#define RTC_READ_INTERVAL_MS 1000 // DS1302读取间隔:1秒
uint32_t last_heartbeat_time = 0;
uint32_t last_reconnect_attempt = 0;
uint32_t last_wifi_reconnect_attempt = 0; // WiFi重连尝试时间
uint32_t last_sync_attempt = 0;
uint32_t last_timeslot_download = 0; // 上次下载时间段配置的时刻
uint32_t g_stm32_sync_version = 0; // STM32端已应用的服务端同步版本
#define HEARTBEAT_INTERVAL_MS 10000
#define RECONNECT_INTERVAL_MS 5000
#define WIFI_RECONNECT_INTERVAL_MS 30000 // WiFi重连间隔:30秒
#define SYNC_INTERVAL_MS 5000
#define TIMESLOT_DOWNLOAD_INTERVAL_MS 30000 // 时间段配置下载间隔:30秒
#define MAX_RETRY_COUNT 5
#define SYNC_BATCH_THRESHOLD 3
typedef enum {
UI_STATE_IDLE,
UI_STATE_SHOW_RESULT,
} UIState;
UIState ui_state = UI_STATE_IDLE;
uint32_t ui_state_start_time = 0;
#define UI_RESULT_DISPLAY_TIME 2000
typedef enum {
BEEP_IDLE,
BEEP_SUCCESS,
BEEP_DUPLICATE
} BeepState;
BeepState beep_state = BEEP_IDLE;
uint32_t beep_start_time = 0;
uint8_t beep_count = 0;
char last_student_id[20] = "";
uint32_t last_process_time = 0;
#define PROCESS_INTERVAL_MS 2000
/* --- 辅助函数 --- */
/* 函数作用:判断指定年份是否为闰年。 */
static uint8_t IsLeapYear(uint16_t year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) ? 1 : 0;
}
/* 函数作用:返回指定年份和月份对应的天数。 */
static uint8_t DaysInMonth(uint16_t year, uint8_t month)
{
static const uint8_t days_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
if (month < 1 || month > 12) return 31;
if (month == 2 && IsLeapYear(year)) return 29;
return days_table[month - 1];
}
/* 函数作用:在没有 DS1302 可用时,用系统 tick 推进软件时钟一秒。 */
static void AdvanceSoftwareClockOneSecond(void)
{
g_current_time.second++;
if (g_current_time.second < 60) return;
g_current_time.second = 0;
g_current_time.minute++;
if (g_current_time.minute < 60) return;
g_current_time.minute = 0;
g_current_time.hour++;
if (g_current_time.hour < 24) return;
g_current_time.hour = 0;
g_current_time.day++;
if (g_current_time.day <= DaysInMonth(g_current_time.year, g_current_time.month)) return;
g_current_time.day = 1;
g_current_time.month++;
if (g_current_time.month <= 12) return;
g_current_time.month = 1;
g_current_time.year++;
}
/* 函数作用:启动签到成功提示音和成功指示灯。 */
void StartBeepSuccess(void)
{
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_state = BEEP_SUCCESS;
beep_start_time = Get_Time_ms();
beep_count = 0;
BUZZER_ON();
LED_SUCCESS_ON();
}
/* 函数作用:启动重复签到提示音和指示灯闪烁序列。 */
void StartBeepDuplicate(void)
{
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_state = BEEP_DUPLICATE;
beep_start_time = Get_Time_ms();
beep_count = 0;
BUZZER_ON();
LED_SUCCESS_ON();
}
/* 函数作用:在主循环中推进蜂鸣器和指示灯的非阻塞提示状态。 */
void ProcessBeep(void)
{
if (beep_state == BEEP_IDLE) {
BUZZER_OFF();
LED_SUCCESS_OFF();
return;
}
uint32_t current_time = Get_Time_ms();
uint32_t elapsed = current_time - beep_start_time;
if (beep_state == BEEP_SUCCESS) {
if (elapsed >= 80) {
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_state = BEEP_IDLE;
}
}
else if (beep_state == BEEP_DUPLICATE) {
if (beep_count == 0) {
if (elapsed >= 80) {
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_count = 1;
}
} else if (beep_count == 1) {
if (elapsed >= 160) {
BUZZER_ON();
LED_SUCCESS_ON();
beep_count = 2;
}
} else if (beep_count == 2) {
if (elapsed >= 240) {
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_state = BEEP_IDLE;
}
}
}
}
/* 函数作用:刷新空闲状态下 OLED 显示的系统状态和待同步数量。 */
void UpdateIdleScreen(void)
{
OLED_Clear();
OLED_ShowString(1, 1, "System Ready");
OLED_ShowString(2, 1, "Encrypted Mode"); // ← 显示加密模式
OLED_ShowString(3, 1, "Total:");
OLED_ShowNum(3, 7, Attendance_GetTotalCount(), 3);
uint16_t pending = Attendance_GetPendingCount();
if (pending > 0) {
OLED_ShowString(4, 1, "Pending:");
OLED_ShowNum(4, 9, pending, 2);
}
}
/**
* @brief 发送加密的考勤记录到服务器(新版本)
*/
/* 函数作用:将一条考勤记录组装为 JSON,加密后通过 ESP8266 上传服务器。 */
uint8_t SendEncryptedAttendance(AttendanceRecord* record)
{
char json_data[256];
char encrypted_data[512];
// 1. 构造JSON数据(包含班级编号)
sprintf(json_data,
"{\"student_id\":\"%s\",\"name\":\"%s\",\"confidence\":%.2f,\"status\":\"success\",\"class_id\":\"%s\"}",
record->student_id,
record->student_name,
record->confidence,
DEVICE_CLASS_ID);
DEBUG_PRINT("[CRYPTO] Original JSON: %s\r\n", json_data);
// 2. 加密JSON数据
uint32_t encrypted_len = Crypto_EncryptJSON(json_data, encrypted_data);
if (encrypted_len == 0) {
DEBUG_ERROR("[CRYPTO] Encryption failed!");
return 0;
}
DEBUG_PRINT("[CRYPTO] Encrypted length: %u\r\n", encrypted_len);
DEBUG_PRINT("[CRYPTO] Encrypted data: %s\r\n", encrypted_data);
// 3. 发送加密数据
if (!g_server_connected) {
DEBUG_ERROR("[CRYPTO] Server not connected");
return 0;
}
if (ESP8266_SendEncryptedData(encrypted_data) == ESP8266_SUCCESS) {
DEBUG_OK("[CRYPTO] Encrypted data sent successfully");
return 1;
} else {
DEBUG_ERROR("[CRYPTO] Send failed");
return 0;
}
}
/* 函数作用:批量同步本地待上传考勤记录,并根据结果更新同步状态。 */
void SyncPendingRecords(void)
{
uint32_t current_time = Get_Time_ms();
uint16_t pending_count = Attendance_GetPendingCount();
if (pending_count == 0) return;
if (!g_server_connected) return;
uint8_t should_sync = 0;
if (current_time - last_sync_attempt >= SYNC_INTERVAL_MS) {
should_sync = 1;
DEBUG_PRINT("[Sync] Timed sync: %d records pending\r\n", pending_count);
}
if (pending_count >= SYNC_BATCH_THRESHOLD) {
should_sync = 1;
DEBUG_PRINT("[Sync] Batch sync: %d records pending\r\n", pending_count);
}
if (!should_sync) return;
last_sync_attempt = current_time;
DEBUG_INFO("[Sync] Starting batch sync (encrypted mode)...");
uint16_t synced_count = 0;
uint16_t failed_count = 0;
for (uint16_t i = 0; i < pending_count && i < 10; i++) {
uint16_t record_index;
AttendanceRecord* record = Attendance_GetNextPendingRecord(&record_index);
if (record == NULL) break;
if (record->retry_count >= MAX_RETRY_COUNT) {
DEBUG_PRINT("[Sync] Record %d exceeded max retries\r\n", record_index);
Attendance_MarkSyncStatus(record_index, SYNC_FAILED);
failed_count++;
continue;
}
Attendance_MarkSyncStatus(record_index, SYNC_SYNCING);
DEBUG_PRINT("[Sync] Syncing %d/%d: %s [retry=%d]\r\n",
i + 1, pending_count,
record->student_id,
record->retry_count);
// 使用加密发送
if (SendEncryptedAttendance(record) == 1) {
Attendance_MarkSyncStatus(record_index, SYNC_SYNCED);
synced_count++;
} else {
Attendance_IncrementRetry(record_index);
Attendance_MarkSyncStatus(record_index, SYNC_FAILED);
failed_count++;
DEBUG_ERROR("[Sync] Send failed, stopping batch sync");
break;
}
Delay_ms(100);
}
DEBUG_PRINT("[Sync] Batch complete: %d synced, %d failed\r\n",
synced_count, failed_count);
}
/* 函数作用:从服务器同步网络时间,并在 DS1302 可用时写入 RTC。 */
uint8_t SyncNetworkTime(void)
{
DEBUG_INFO("Syncing time from network...");
if (ESP8266_GetNetworkTime(&g_current_time) == ESP8266_SUCCESS) {
last_time_sync = Get_Time_ms();
last_rtc_read = last_time_sync;
DEBUG_PRINT("Time synced: %04d-%02d-%02d %02d:%02d:%02d\r\n",
g_current_time.year, g_current_time.month, g_current_time.day,
g_current_time.hour, g_current_time.minute, g_current_time.second);
// 如果DS1302可用,将网络时间写入DS1302
if (g_use_ds1302) {
DS1302_Time rtc_time;
rtc_time.year = g_current_time.year;
rtc_time.month = g_current_time.month;
rtc_time.day = g_current_time.day;
rtc_time.hour = g_current_time.hour;
rtc_time.minute = g_current_time.minute;
rtc_time.second = g_current_time.second;
rtc_time.week = 1;
DS1302_SetTime(&rtc_time);
DEBUG_OK("Network time synchronized to DS1302 RTC");
// 延时后验证写入
Delay_ms(100);
// 读回验证
DS1302_Time verify_time;
DS1302_GetTime(&verify_time);
DEBUG_PRINT("[DS1302] Verify: %04d-%02d-%02d %02d:%02d:%02d\r\n",
verify_time.year, verify_time.month, verify_time.day,
verify_time.hour, verify_time.minute, verify_time.second);
// 检查验证结果(允许1-2秒误差)
if (verify_time.year == g_current_time.year &&
verify_time.month == g_current_time.month &&
verify_time.day == g_current_time.day &&
verify_time.hour == g_current_time.hour) {
DEBUG_OK("[DS1302] Time verified successfully!");
} else {
DEBUG_ERROR("[DS1302] Time verification failed! Disabling DS1302.");
DEBUG_PRINT("[DS1302] Expected: %04d-%02d-%02d %02d:%02d, Got: %04d-%02d-%02d %02d:%02d\r\n",
g_current_time.year, g_current_time.month, g_current_time.day,
g_current_time.hour, g_current_time.minute,
verify_time.year, verify_time.month, verify_time.day,
verify_time.hour, verify_time.minute);
g_use_ds1302 = 0; // 禁用DS1302
DEBUG_INFO("[System] Switched to NTP-only mode");
}
} else {
DEBUG_INFO("[System] Using NTP time (DS1302 disabled)");
}
Attendance_SetCurrentTime(g_current_time.hour, g_current_time.minute);
return 1;
}
DEBUG_ERROR("Time sync failed");
return 0;
}
/* 函数作用:从同步状态 JSON 中提取服务端版本号。 */
static uint32_t ParseSyncVersion(const char* json)
{
const char* p;
uint32_t version = 0;
if (json == NULL) return 0;
p = strstr(json, "\"version\":");
if (p == NULL) return 0;
p += 10;
while (*p >= '0' && *p <= '9') {
version = version * 10 + (uint32_t)(*p - '0');
p++;
}
return version;
}
/* 函数作用:从服务端下载 STM32 需要的同步状态并更新时间段配置。 */
uint8_t DownloadTimeSlots(void)
{
char response_buffer[768];
char path[96];
uint32_t server_version;
DEBUG_INFO("Downloading STM32 sync state from server...");
sprintf(path, "/api/v1/sync/state?client=stm32&last_version=%lu", (unsigned long)g_stm32_sync_version);
// 按版本号拉取STM32需要的配置
if (ESP8266_HttpGet(path, response_buffer, sizeof(response_buffer)) == ESP8266_SUCCESS) {
server_version = ParseSyncVersion(response_buffer);
if (strstr(response_buffer, "\"changed\":false") != NULL) {
if (server_version > 0) {
g_stm32_sync_version = server_version;
}
last_timeslot_download = Get_Time_ms();
DEBUG_INFO("STM32 sync state unchanged");
return 1;
}
DEBUG_OK("STM32 sync state downloaded successfully");
// 解析JSON并更新时间段配置
uint8_t slot_count = Attendance_ParseTimeSlotsJSON(response_buffer);
if (slot_count > 0 || strstr(response_buffer, "\"time_slots\"") != NULL) {
DEBUG_PRINT("Time slot configuration updated: %d slots\r\n", slot_count);
if (server_version > 0) {
g_stm32_sync_version = server_version;
DEBUG_PRINT("[Sync] STM32 version updated: %lu\r\n", (unsigned long)g_stm32_sync_version);
}
// 更新下载时间戳
last_timeslot_download = Get_Time_ms();
return 1;
} else {
DEBUG_ERROR("Failed to parse time slot JSON");
return 0;
}
}
DEBUG_ERROR("Failed to download time slots, using current configuration");
return 0;
}
/* 函数作用:按固定间隔自动从服务端刷新时间段配置。 */
void UpdateTimeSlotConfig(void)
{
uint32_t current_time = Get_Time_ms();
// 如果服务器未连接,不执行下载
if (!g_server_connected) {
return;
}
// 检查是否到了更新间隔(每5分钟)
if (current_time - last_timeslot_download >= TIMESLOT_DOWNLOAD_INTERVAL_MS) {
DEBUG_INFO("Auto-updating time slot configuration...");
if (DownloadTimeSlots()) {
DEBUG_OK("Time slot configuration updated from server");
// 短暂显示更新提示
OLED_ShowString(4, 1, "Config Synced! ");
} else {
DEBUG_ERROR("Failed to update time slot configuration");
}
// 无论成功失败都更新时间戳,避免频繁重试
last_timeslot_download = current_time;
}
}
/* 函数作用:更新当前时间来源,优先读取 DS1302,失败时使用软件时钟并定期 NTP 校时。 */
void UpdateCurrentTime(void)
{
uint32_t current_ms = Get_Time_ms();
static uint8_t debug_counter = 0; // 调试计数器,每10秒打印一次
// 如果DS1302可用,每秒从DS1302读取时间
if (g_use_ds1302 && current_ms - last_rtc_read >= RTC_READ_INTERVAL_MS) {
last_rtc_read = current_ms;
DS1302_Time rtc_time;
DS1302_GetTime(&rtc_time);
// 更新全局时间变量
g_current_time.year = rtc_time.year;
g_current_time.month = rtc_time.month;
g_current_time.day = rtc_time.day;
g_current_time.hour = rtc_time.hour;
g_current_time.minute = rtc_time.minute;
g_current_time.second = rtc_time.second;
// 更新考勤模块的当前时间
Attendance_SetCurrentTime(rtc_time.hour, rtc_time.minute);
// 每10秒打印一次当前时间(调试用)
debug_counter++;
if (debug_counter >= 10) {
debug_counter = 0;
DEBUG_PRINT("[Time] DS1302: %04d-%02d-%02d %02d:%02d:%02d\r\n",
rtc_time.year, rtc_time.month, rtc_time.day,
rtc_time.hour, rtc_time.minute, rtc_time.second);
}
}
// 如果DS1302不可用,基于系统tick推进软件时钟
else if (!g_use_ds1302 && current_ms - last_rtc_read >= RTC_READ_INTERVAL_MS) {
while (current_ms - last_rtc_read >= RTC_READ_INTERVAL_MS) {
last_rtc_read += RTC_READ_INTERVAL_MS;
AdvanceSoftwareClockOneSecond();
}
Attendance_SetCurrentTime(g_current_time.hour, g_current_time.minute);
// 每10秒打印一次当前时间(调试用)
debug_counter++;
if (debug_counter >= 10) {
debug_counter = 0;
DEBUG_PRINT("[Time] NTP-soft: %04d-%02d-%02d %02d:%02d:%02d\r\n",
g_current_time.year, g_current_time.month, g_current_time.day,
g_current_time.hour, g_current_time.minute, g_current_time.second);
}
}
// 定期从网络同步时间(每小时一次,WiFi在线即可)
if (current_ms - last_time_sync > TIME_SYNC_INTERVAL_MS) {
if (g_wifi_connected) {
SyncNetworkTime();
}
}
}
/* 函数作用:按重连间隔尝试恢复 WiFi,并在成功后连接 HTTP 服务器和同步配置。 */
uint8_t TryReconnectWiFi(void)
{
uint32_t current_time = Get_Time_ms();
// 控制重连频率:30秒尝试一次
if (current_time - last_wifi_reconnect_attempt < WIFI_RECONNECT_INTERVAL_MS) {
return 0;
}
last_wifi_reconnect_attempt = current_time;
DEBUG_INFO("Attempting to reconnect WiFi...");
// 尝试连接WiFi
if (ESP8266_ConnectWiFi(WIFI_SSID, WIFI_PASSWORD) == ESP8266_SUCCESS) {
DEBUG_OK("WiFi reconnected!");
g_wifi_connected = 1;
// WiFi已连通时先做NTP对时(不依赖HTTP服务器连接)
SyncNetworkTime();
// WiFi连接成功后,立即尝试连接服务器
DEBUG_INFO("Connecting to HTTP server...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) == ESP8266_SUCCESS) {
DEBUG_OK("HTTP server connected!");
g_server_connected = 1;
last_heartbeat_time = current_time;
// 连接成功提示音
BUZZER_ON();
Delay_ms(100);
BUZZER_OFF();
// 同步时间
SyncNetworkTime();
// 下载时间段配置
DownloadTimeSlots();
// 刷新当前时段的今日记录缓存,避免重连后缓存过旧
Attendance_LoadTodayRecordsFromServer();
return 1;
} else {
DEBUG_ERROR("HTTP server connection failed");
g_server_connected = 0;
}
return 1; // WiFi已连接,即使服务器未连接
}
DEBUG_ERROR("WiFi reconnect failed, will retry in 30s...");
return 0;
}
/* 函数作用:按重连间隔尝试恢复 HTTP 服务器连接。 */
uint8_t TryReconnectServer(void)
{
uint32_t current_time = Get_Time_ms();
if (current_time - last_reconnect_attempt < RECONNECT_INTERVAL_MS) {
return 0;
}
last_reconnect_attempt = current_time;
DEBUG_INFO("Attempting to reconnect to HTTP server...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) == ESP8266_SUCCESS) {
DEBUG_OK("Reconnected to HTTP server!");
g_server_connected = 1;
last_heartbeat_time = current_time;
BUZZER_ON();
Delay_ms(50);
BUZZER_OFF();
DownloadTimeSlots();
Attendance_LoadTodayRecordsFromServer();
return 1;
}
DEBUG_ERROR("Reconnect failed, will retry...");
return 0;
}
/* 函数作用:周期性检查服务器连接状态,断线时触发自动重连。 */
void CheckServerConnection(void)
{
uint32_t current_time = Get_Time_ms();
// 如果WiFi未连接,尝试重连WiFi
if (!g_wifi_connected) {
TryReconnectWiFi();
return; // WiFi未连接时,不检查服务器
}
// WiFi已连接,检查服务器连接状态
if (current_time - last_heartbeat_time < HEARTBEAT_INTERVAL_MS) {
return;
}
last_heartbeat_time = current_time;
if (g_server_connected) {
// 检查服务器连接是否仍然有效
if (ESP8266_CheckConnection() == ESP8266_SUCCESS) {
DEBUG_INFO("Server connection OK");
} else {
DEBUG_INFO("Server TCP connection closed, reconnecting...");
g_server_connected = 0;
TryReconnectServer();
}
} else {
// 服务器未连接,尝试重连
TryReconnectServer();
}
}
// ======================================================================
// 主函数
// ======================================================================
// 主函数
// ======================================================================
/* 函数作用:STM32 固件入口,完成硬件初始化并循环处理识别、考勤、联网和显示任务。 */
int main(void)
{
AttendanceStatus att_status;
uint16_t unsynced_temp;
// 1. 硬件和驱动初始化
Debug_Init();
DEBUG_INFO("===========================================");
DEBUG_INFO(" STM32 Face Recognition System");
DEBUG_INFO(" with AES-128-CBC Encryption");
DEBUG_INFO("===========================================");
Delay_Init();
DEBUG_OK("Delay initialized");
DEBUG_INFO("Initializing OLED...");
OLED_Init();
DEBUG_OK("OLED initialized");
DEBUG_INFO("Initializing Buzzer & LED...");
Buzzer_LED_Init();
DEBUG_OK("Buzzer & LED initialized");
DEBUG_INFO("Initializing USART1 PA9/PA10 (ESP8266)...");
Serial_Init();
DEBUG_OK("USART1 PA9/PA10 (ESP8266) initialized");
DEBUG_INFO("Initializing USART3 PB10/PB11 (K210)...");
K210_USART_Init();
DEBUG_OK("USART3 PB10/PB11 (K210) initialized");
DEBUG_INFO("Initializing Attendance module...");
Attendance_Init();
DEBUG_OK("Attendance module initialized");
// ← 新增:初始化加密模块
DEBUG_INFO("Initializing Crypto module...");
Crypto_Init();
DEBUG_OK("Crypto module initialized (AES-128-CBC)");
// ⭐ 新增:初始化DS1302 RTC时钟模块(带硬件检测)
DEBUG_INFO("Initializing DS1302 RTC...");
DS1302_Init();
// 尝试读取DS1302,检测硬件是否正常
DS1302_Time rtc_time;
DS1302_GetTime(&rtc_time);
DEBUG_PRINT("RTC Time: %04d-%02d-%02d %02d:%02d:%02d\r\n",
rtc_time.year, rtc_time.month, rtc_time.day,
rtc_time.hour, rtc_time.minute, rtc_time.second);
// 检测DS1302读取的数据是否合理
if (rtc_time.year >= 2020 && rtc_time.year <= 2100 &&
rtc_time.month >= 1 && rtc_time.month <= 12 &&
rtc_time.day >= 1 && rtc_time.day <= 31 &&
rtc_time.hour <= 23 && rtc_time.minute <= 59 && rtc_time.second <= 59) {
// DS1302数据合理,启用DS1302
g_use_ds1302 = 1;
DEBUG_OK("DS1302 RTC initialized and verified");
// 将DS1302时间同步到全局时间变量
g_current_time.year = rtc_time.year;
g_current_time.month = rtc_time.month;
g_current_time.day = rtc_time.day;
g_current_time.hour = rtc_time.hour;
g_current_time.minute = rtc_time.minute;
g_current_time.second = rtc_time.second;
// 设置考勤模块的当前时间
Attendance_SetCurrentTime(rtc_time.hour, rtc_time.minute);
} else {
// DS1302数据异常,禁用DS1302
g_use_ds1302 = 0;
DEBUG_ERROR("DS1302 hardware error or not connected!");
DEBUG_INFO("System will use NTP time only");
}
// ⭐ 新增:初始化光敏电阻和补光灯模块
DEBUG_INFO("Initializing Light Sensor and Fill Light...");
LightSensor_Init();
DEBUG_OK("Light Sensor initialized (PB0=ADC, PB1=LED)");
last_rtc_read = Get_Time_ms();
// 2. ESP8266网络连接流程(优化:失败后继续运行,后台自动重连)
DEBUG_INFO("Starting ESP8266 setup...");
OLED_ShowString(1, 1, "ESP setup...");
if (ESP8266_Setup() == ESP8266_SUCCESS)
{
DEBUG_OK("ESP8266 setup success");
g_esp8266_ready = 1;
DEBUG_PRINT("Connecting to WiFi: %s\r\n", WIFI_SSID);
OLED_ShowString(2, 1, "WiFi connecting");
if (ESP8266_ConnectWiFi(WIFI_SSID, WIFI_PASSWORD) == ESP8266_SUCCESS)
{
DEBUG_OK("WiFi connected!");
g_wifi_connected = 1;
// WiFi已连通时先做NTP对时(不依赖HTTP服务器连接)
SyncNetworkTime();
OLED_Clear();
OLED_ShowString(1, 1, "WiFi OK!");
OLED_ShowString(2, 1, "HTTP connecting");
// 尝试连接HTTP服务器
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) == ESP8266_SUCCESS)
{
DEBUG_OK("HTTP server connected!");
g_server_connected = 1;
last_heartbeat_time = Get_Time_ms();
// 下载时间段配置
DownloadTimeSlots();
// 下载今日已签到记录到本地缓存
OLED_ShowString(2, 1, "Loading records");
uint8_t loaded = Attendance_LoadTodayRecordsFromServer();
DEBUG_PRINT("[Main] Loaded %d today's records from server\r\n", loaded);
}
else
{
DEBUG_ERROR("HTTP connection failed!");
DEBUG_INFO("Will retry in background...");
g_server_connected = 0;
}
}
else
{
DEBUG_ERROR("WiFi connection failed!");
DEBUG_INFO("Will retry in background (every 30s)...");
g_wifi_connected = 0;
last_wifi_reconnect_attempt = Get_Time_ms(); // 初始化重连时间
}
}
else
{
DEBUG_ERROR("ESP8266 setup failed!");
DEBUG_INFO("WiFi module unavailable, system will run offline");
g_esp8266_ready = 0;
g_wifi_connected = 0;
g_server_connected = 0;
}
// 3. 根据连接结果给出反馈(无论成功失败都继续运行)
OLED_Clear();
if (g_server_connected)
{
DEBUG_INFO("=================================");
DEBUG_INFO("System Ready! Server connected!");
DEBUG_INFO("Encryption Mode: AES-128-CBC");
DEBUG_INFO("=================================");
OLED_ShowString(1, 1, "System Ready");
OLED_ShowString(2, 1, "Online Mode");
// 系统就绪:熄灭PC13状态指示LED
LED_STATUS_OFF();
StartBeepSuccess();
Delay_ms(500);
}
else if (g_wifi_connected)
{
DEBUG_INFO("=================================");
DEBUG_INFO("System Ready! WiFi connected");
DEBUG_INFO("Server reconnecting...");
DEBUG_INFO("=================================");
OLED_ShowString(1, 1, "System Ready");
OLED_ShowString(2, 1, "Reconnecting..");
// 系统就绪:熄灭PC13状态指示LED
LED_STATUS_OFF();
}
else if (g_esp8266_ready)
{
DEBUG_INFO("=================================");
DEBUG_INFO("System Ready! (WiFi reconnecting)");
DEBUG_INFO("Auto-retry every 30 seconds");
DEBUG_INFO("=================================");
OLED_ShowString(1, 1, "System Ready");
OLED_ShowString(2, 1, "WiFi retry...");
// 系统就绪:熄灭PC13状态指示LED
LED_STATUS_OFF();
}
else
{
DEBUG_ERROR("ESP8266 not available");
DEBUG_INFO("=================================");
DEBUG_INFO("System Ready! (Offline mode)");
DEBUG_INFO("Data will be stored locally");
DEBUG_INFO("=================================");
OLED_ShowString(1, 1, "System Ready");
OLED_ShowString(2, 1, "Offline Mode");
// 系统就绪:熄灭PC13状态指示LED
LED_STATUS_OFF();
StartBeepDuplicate();
Delay_ms(1000);
}
UpdateIdleScreen();
// 4. 主循环
DEBUG_INFO("Entering main loop...");
while(1)
{
uint32_t current_time = Get_Time_ms();
ProcessBeep();
UpdateCurrentTime();
CheckServerConnection();
SyncPendingRecords();
UpdateTimeSlotConfig(); // ⭐ 新增:定期更新时间段配置
LightSensor_Update(); // ⭐ 新增:更新补光灯状态(根据光敏电阻)
if (K210_GetRxFlag())
{
if (K210_FaceResult.success)
{
DEBUG_PRINT("Face recognized: ID=%s, Name=%s, Conf=%.2f\r\n",
K210_FaceResult.student_id,
K210_FaceResult.student_name,
K210_FaceResult.confidence);
// 智能节流
if (strcmp(last_student_id, K210_FaceResult.student_id) == 0) {
if (current_time - last_process_time < PROCESS_INTERVAL_MS) {
DEBUG_INFO("Same person throttled - skipping");
continue;
}
}
strncpy(last_student_id, K210_FaceResult.student_id, sizeof(last_student_id) - 1);
last_process_time = current_time;
// 确保蜂鸣器关闭(服务器查重可能耗时较长)
BUZZER_OFF();
LED_SUCCESS_OFF();
beep_state = BEEP_IDLE;
OLED_Clear();
OLED_ShowString(1, 1, "ID:");
OLED_ShowString(1, 4, K210_FaceResult.student_id);
OLED_ShowString(2, 1, K210_FaceResult.student_name);
OLED_ShowString(3, 1, "Verifying..."); // 显示正在验证
att_status = Attendance_ProcessFaceResult(&K210_FaceResult);
switch(att_status)
{
case ATTENDANCE_SUCCESS:
DEBUG_OK("Attendance SUCCESS! (Encrypted)");
OLED_ShowString(3, 1, "Check-in OK!");
OLED_ShowString(4, 1, "Encrypted");
StartBeepSuccess();
DEBUG_INFO("Record queued for encrypted sync");
break;
case ATTENDANCE_DUPLICATE:
DEBUG_ERROR("Duplicate attendance detected!");
OLED_ShowString(3, 1, "Already Signed!");
StartBeepDuplicate();
break;
case ATTENDANCE_LOW_CONFIDENCE:
DEBUG_ERROR("Low confidence rejected");
OLED_ShowString(3, 1, "Low Confidence");
StartBeepDuplicate();
break;
case ATTENDANCE_OUT_OF_TIME:
DEBUG_ERROR("Not in time slot");
OLED_ShowString(3, 1, "Not Check Time");
StartBeepDuplicate();
break;
default:
DEBUG_ERROR("Attendance processing error");
OLED_ShowString(3, 1, "Error!");
StartBeepDuplicate();
break;
}
ui_state = UI_STATE_SHOW_RESULT;
ui_state_start_time = current_time;
unsynced_temp = Attendance_GetPendingCount();
DEBUG_PRINT("Total: %d, Pending: %d\r\n",
Attendance_GetTotalCount(), unsynced_temp);
}
else
{
DEBUG_INFO("Unknown face - ignored");
}
}
if (ui_state == UI_STATE_SHOW_RESULT) {
if (current_time - ui_state_start_time >= UI_RESULT_DISPLAY_TIME) {
ui_state = UI_STATE_IDLE;
UpdateIdleScreen();
}
}
Delay_ms(10);
}
}