初始化毕业设计项目
This commit is contained in:
+925
@@ -0,0 +1,925 @@
|
||||
// ======================================================================
|
||||
// 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 = "change-me-token"; // 必须与服务端 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
|
||||
|
||||
/* --- 辅助函数 --- */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 发送加密的考勤记录到服务器(新版本)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t SyncNetworkTime(void)
|
||||
{
|
||||
DEBUG_INFO("Syncing time from network...");
|
||||
|
||||
if (ESP8266_GetNetworkTime(&g_current_time) == ESP8266_SUCCESS) {
|
||||
last_time_sync = Get_Time_ms();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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不可用,直接使用NTP时间(每秒更新考勤时间)
|
||||
else if (!g_use_ds1302 && current_ms - last_rtc_read >= RTC_READ_INTERVAL_MS) {
|
||||
last_rtc_read = current_ms;
|
||||
|
||||
// 直接使用全局NTP时间
|
||||
Attendance_SetCurrentTime(g_current_time.hour, g_current_time.minute);
|
||||
|
||||
// 每10秒打印一次当前时间(调试用)
|
||||
debug_counter++;
|
||||
if (debug_counter >= 10) {
|
||||
debug_counter = 0;
|
||||
DEBUG_PRINT("[Time] NTP: %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);
|
||||
}
|
||||
}
|
||||
|
||||
// 定期从网络同步时间(每小时一次,WiFi在线即可)
|
||||
if (current_ms - last_time_sync > TIME_SYNC_INTERVAL_MS) {
|
||||
if (g_wifi_connected) {
|
||||
SyncNetworkTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 主函数
|
||||
// ======================================================================
|
||||
// 主函数
|
||||
// ======================================================================
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user