初始化毕业设计项目

This commit is contained in:
2026-05-01 20:29:02 +08:00
commit df5b05e936
158 changed files with 67957 additions and 0 deletions
+624
View File
@@ -0,0 +1,624 @@
/*
* ESP8266 WiFi模块驱动(支持HTTP加密传输)
* 版本:v2.0 - 加密方案C
* 修改:
* 1. 添加HTTP POST请求支持
* 2. 添加加密数据传输
* 3. 保留原有TCP兼容接口
*/
#include "esp8266.h"
#include "Serial.h"
#include "Delay.h"
#include "Debug.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // ← 添加atoi函数声明
// 外部配置(在main.c中定义)
extern const char* WIFI_SSID;
extern const char* WIFI_PASSWORD;
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
extern const char* HTTP_API_TOKEN;
extern uint8_t g_server_connected;
/* --- 私有辅助函数 --- */
static void ESP8266_SendCmd(const char* cmd) {
Serial_SendString(cmd);
Serial_SendString("\r\n");
}
static uint8_t ESP8266_WaitForAck(const char* ack, uint32_t timeout) {
uint32_t time_start = Get_Time_ms();
uint32_t time_elapsed;
Serial_ClearRxBuffer();
DEBUG_PRINT("[ESP8266] Waiting for: %s (timeout: %dms)\r\n", ack, timeout);
while(1) {
time_elapsed = Get_Time_ms() - time_start;
if (time_elapsed > timeout) {
DEBUG_PRINT("[ESP8266] Timeout after %dms\r\n", time_elapsed);
break;
}
char* response = Serial_GetRxBuffer();
if (response && strstr(response, ack) != NULL) {
DEBUG_OK("[ESP8266] Received expected response");
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
DEBUG_PRINT("[ESP8266] Failed! Response: %s\r\n", Serial_GetRxBuffer());
return ESP8266_FAIL;
}
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 == 2 && IsLeapYear(year)) return 29;
return days_table[month - 1];
}
static void EpochToDateTimeUTC8(uint32_t epoch_seconds, ESP8266_DateTime* datetime)
{
// 转为北京时间(UTC+8)
uint32_t total = epoch_seconds + 8U * 3600U;
uint32_t days = total / 86400U;
uint32_t sec_of_day = total % 86400U;
datetime->hour = sec_of_day / 3600U;
datetime->minute = (sec_of_day % 3600U) / 60U;
datetime->second = sec_of_day % 60U;
datetime->year = 1970;
while (1) {
uint16_t ydays = IsLeapYear(datetime->year) ? 366 : 365;
if (days < ydays) break;
days -= ydays;
datetime->year++;
}
datetime->month = 1;
while (1) {
uint8_t mdays = DaysInMonth(datetime->year, datetime->month);
if (days < mdays) break;
days -= mdays;
datetime->month++;
}
datetime->day = (uint8_t)(days + 1U);
}
/* --- 公共接口函数实现 --- */
uint8_t ESP8266_Setup(void)
{
ESP8266_SendCmd("+++");
Delay_ms(500);
ESP8266_SendCmd("AT");
if (!ESP8266_WaitForAck("OK", 1000)) return ESP8266_FAIL;
ESP8266_SendCmd("AT+CWMODE=1");
if (!ESP8266_WaitForAck("OK", 2000)) return ESP8266_FAIL;
// 先关闭可能存在的连接(避免 "link is builded" 错误)
ESP8266_SendCmd("AT+CIPCLOSE");
Delay_ms(500); // 等待连接关闭
// 设置单连接模式
ESP8266_SendCmd("AT+CIPMUX=0");
ESP8266_WaitForAck("OK", 1000);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_ConnectWiFi(const char* ssid, const char* password)
{
char cmd_buffer[128];
sprintf(cmd_buffer, "AT+CWJAP=\"%s\",\"%s\"", ssid, password);
ESP8266_SendCmd(cmd_buffer);
return ESP8266_WaitForAck("WIFI GOT IP", 15000);
}
/**
* @brief 连接到HTTP服务器(新增)
*/
uint8_t ESP8266_ConnectHTTPServer(const char* ip, const char* port)
{
char cmd_buffer[100];
uint32_t start_time;
DEBUG_PRINT("[ESP8266] Connecting to HTTP server %s:%s\r\n", ip, port);
// 先断开之前的连接
ESP8266_SendCmd("AT+CIPCLOSE");
Delay_ms(500);
// 建立TCP连接
sprintf(cmd_buffer, "AT+CIPSTART=\"TCP\",\"%s\",%s", ip, port);
Serial_ClearRxBuffer();
ESP8266_SendCmd(cmd_buffer);
// 等待连接成功
start_time = Get_Time_ms();
while(Get_Time_ms() - start_time < 10000) {
char* response = Serial_GetRxBuffer();
if (strstr(response, "CONNECT") != NULL || strstr(response, "ALREADY") != NULL) {
DEBUG_OK("[ESP8266] HTTP server connected");
Delay_ms(500);
return ESP8266_SUCCESS;
}
if (strstr(response, "ERROR") != NULL || strstr(response, "CLOSED") != NULL) {
DEBUG_ERROR("[ESP8266] Connection failed");
break;
}
Delay_ms(100);
}
DEBUG_ERROR("[ESP8266] Connection timeout");
return ESP8266_FAIL;
}
/**
* @brief 发送加密数据(HTTP POST请求)
*/
uint8_t ESP8266_SendEncryptedData(const char* encrypted_data)
{
char http_request[1024];
char cmd_buffer[32];
uint32_t content_length = strlen(encrypted_data);
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] HTTP connection not ready, reconnecting...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed before POST");
return ESP8266_FAIL;
}
g_server_connected = 1;
}
// 构建HTTP POST请求
sprintf(http_request,
"POST /api/v1/attendance/checkins HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"Content-Type: application/octet-stream\r\n"
"X-Api-Token: %s\r\n"
"X-Encrypted: AES128-CBC\r\n"
"Content-Length: %u\r\n"
"Connection: close\r\n"
"\r\n"
"%s",
HTTP_SERVER_IP,
HTTP_SERVER_PORT,
HTTP_API_TOKEN,
content_length,
encrypted_data
);
uint32_t request_len = strlen(http_request);
DEBUG_PRINT("[ESP8266] Sending HTTP POST (%u bytes)\r\n", request_len);
// 发送数据长度命令
sprintf(cmd_buffer, "AT+CIPSEND=%u", request_len);
Serial_ClearRxBuffer();
ESP8266_SendCmd(cmd_buffer);
// 等待 ">" 提示符
uint32_t start_time = Get_Time_ms();
while(Get_Time_ms() - start_time < 2000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
DEBUG_INFO("[ESP8266] Ready to send");
// 发送HTTP请求
Serial_SendString(http_request);
Delay_ms(100);
// 不等待SEND OK,直接返回成功
DEBUG_OK("[ESP8266] Data sent");
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
DEBUG_ERROR("[ESP8266] No '>' prompt");
return ESP8266_FAIL;
}
uint8_t ESP8266_GetNetworkTime(ESP8266_DateTime* datetime)
{
char response_buffer[256];
char* epoch_ptr;
uint32_t epoch_seconds = 0;
uint8_t digit_count = 0;
DEBUG_INFO("[ESP8266] Getting network time...");
// 优先直接请求业务服务器时间接口
// 期望响应: {"epoch":1700000000,"server_time":"2026-02-12 12:34:56"}
if (ESP8266_HttpGet("/api/v1/system/time", response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] /api/v1/system/time request failed, try reconnect...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed when getting time");
return ESP8266_FAIL;
}
g_server_connected = 1;
if (ESP8266_HttpGet("/api/v1/system/time", response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] /api/v1/system/time request failed after reconnect");
return ESP8266_FAIL;
}
}
epoch_ptr = strstr(response_buffer, "\"epoch\":");
if (epoch_ptr == NULL) {
DEBUG_ERROR("[ESP8266] /api/v1/system/time response missing epoch");
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_FAIL;
}
epoch_ptr += 8; // 跳过 "epoch":
while (*epoch_ptr == ' ' || *epoch_ptr == '\"') epoch_ptr++;
while (*epoch_ptr >= '0' && *epoch_ptr <= '9' && digit_count < 10) {
epoch_seconds = epoch_seconds * 10U + (uint32_t)(*epoch_ptr - '0');
epoch_ptr++;
digit_count++;
}
if (digit_count < 9) {
DEBUG_ERROR("[ESP8266] Invalid epoch value in /api/v1/system/time response");
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_FAIL;
}
EpochToDateTimeUTC8(epoch_seconds, datetime);
DEBUG_PRINT("[ESP8266] Time(from server): %04d-%02d-%02d %02d:%02d:%02d\r\n",
datetime->year, datetime->month, datetime->day,
datetime->hour, datetime->minute, datetime->second);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_CheckConnection(void)
{
char* response;
Serial_ClearRxBuffer();
ESP8266_SendCmd("AT+CIPSTATUS");
Delay_ms(500);
response = Serial_GetRxBuffer();
if (strstr(response, "STATUS:3") != NULL) {
DEBUG_INFO("[ESP8266] Connection OK (STATUS:3)");
return ESP8266_SUCCESS;
}
if (strstr(response, "STATUS:4") != NULL) {
DEBUG_ERROR("[ESP8266] Connection closed (STATUS:4)");
return ESP8266_FAIL;
}
if (strstr(response, "STATUS:5") != NULL) {
DEBUG_ERROR("[ESP8266] WiFi disconnected (STATUS:5)");
return ESP8266_FAIL;
}
if (strstr(response, "STATUS:2") != NULL) {
DEBUG_ERROR("[ESP8266] Not connected to server (STATUS:2)");
return ESP8266_FAIL;
}
DEBUG_ERROR("[ESP8266] Unknown connection status");
return ESP8266_FAIL;
}
/**
* @brief 发送HTTP GET请求并接收响应
*/
uint8_t ESP8266_HttpGet(const char* path, char* response_buffer, uint16_t buffer_size)
{
char http_request[256];
char cmd_buffer[32];
uint8_t request_sent = 0;
DEBUG_PRINT("[ESP8266] HTTP GET: %s\r\n", path);
if (response_buffer != NULL && buffer_size > 0) {
response_buffer[0] = '\0';
}
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] HTTP connection not ready, reconnecting...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed before GET");
return ESP8266_FAIL;
}
g_server_connected = 1;
}
// 构建HTTP GET请求
sprintf(http_request,
"GET %s HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"X-Api-Token: %s\r\n"
"Connection: close\r\n"
"\r\n",
path,
HTTP_SERVER_IP,
HTTP_SERVER_PORT,
HTTP_API_TOKEN
);
uint32_t request_len = strlen(http_request);
// 发送数据长度命令
sprintf(cmd_buffer, "AT+CIPSEND=%u", request_len);
Serial_ClearRxBuffer();
ESP8266_SendCmd(cmd_buffer);
// 等待 ">" 提示符
uint32_t start_time = Get_Time_ms();
while(Get_Time_ms() - start_time < 2000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
DEBUG_INFO("[ESP8266] Ready to send GET request");
// 发送HTTP GET请求
Serial_SendString(http_request);
request_sent = 1;
break;
}
Delay_ms(10);
}
if (!request_sent) {
DEBUG_ERROR("[ESP8266] No '>' prompt for GET request");
return ESP8266_FAIL;
}
// 等待接收完整响应(最多8秒)
start_time = Get_Time_ms();
uint8_t found_header = 0;
uint32_t content_length = 0;
while(Get_Time_ms() - start_time < 8000) {
char* response = Serial_GetRxBuffer();
// 1. 处理+IPD前缀(ESP8266格式:+IPD,长度:数据)
char* ipd_marker = strstr(response, "+IPD,");
if (ipd_marker != NULL) {
// 跳过 "+IPD," 找到实际数据
char* data_start = strchr(ipd_marker, ':');
if (data_start != NULL) {
data_start++; // 跳过 ':'
response = data_start; // 从这里开始才是HTTP响应
}
}
// 2. 查找HTTP响应体(JSON数据在\r\n\r\n之后)
char* body_start = strstr(response, "\r\n\r\n");
if (body_start != NULL) {
body_start += 4; // 跳过 \r\n\r\n
// 3. 如果还没提取Content-Length,尝试提取
if (!found_header) {
char* cl_str = strstr(response, "Content-Length:");
if (cl_str != NULL) {
cl_str += 15; // 跳过 "Content-Length:"
while (*cl_str == ' ') cl_str++;
content_length = atoi(cl_str);
DEBUG_PRINT("[ESP8266] Content-Length: %u\r\n", content_length);
found_header = 1;
}
}
// 4. 检查是否接收完整(比较实际长度和Content-Length)
uint16_t body_len = strlen(body_start);
// 移除末尾的 "CLOSED" "OK" 等ESP8266附加信息
char* closed_marker = strstr(body_start, "\r\nCLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
// 同时处理没有\r\n的情况
closed_marker = strstr(body_start, "CLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
// 移除末尾的OK
char* ok_marker = strstr(body_start, "\r\nOK");
if (ok_marker != NULL) {
*ok_marker = '\0';
body_len = strlen(body_start);
}
// 5. 如果数据完整或超时,复制到缓冲区
if ((found_header && body_len >= content_length) ||
(Get_Time_ms() - start_time > 3000 && body_len > 0)) {
if (body_len >= buffer_size) {
body_len = buffer_size - 1;
}
strncpy(response_buffer, body_start, body_len);
response_buffer[body_len] = '\0';
DEBUG_PRINT("[ESP8266] Received response (%d bytes)\r\n", strlen(response_buffer));
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_SUCCESS;
}
}
Delay_ms(100);
}
DEBUG_ERROR("[ESP8266] No valid HTTP response or timeout");
return ESP8266_FAIL;
}
// ========== 兼容旧版本的函数 ==========
/**
* @brief 查询学生在指定时段是否已签到(服务器端查重)
* @param student_id 学号
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @retval 1 表示已签到(重复), 0 表示未签到
*/
uint8_t ESP8266_CheckDuplicate(const char* student_id, const char* slot_start, const char* slot_end)
{
char path[128];
char response_buffer[256];
// 构建查询路径
sprintf(path, "/api/v1/attendance/duplicates?student_id=%s&slot_start=%s&slot_end=%s",
student_id, slot_start, slot_end);
DEBUG_PRINT("[ESP8266] Checking duplicate: %s\r\n", path);
// 先检查连接状态,如果断开则重连
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] Connection lost, reconnecting...");
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed");
return 0; // 连接失败,默认允许签到
}
Delay_ms(200); // 等待连接稳定
}
// 发送HTTP GET请求
if (ESP8266_HttpGet(path, response_buffer, sizeof(response_buffer)) == ESP8266_SUCCESS) {
// 解析响应,查找 "duplicate":true
if (strstr(response_buffer, "\"duplicate\":true") != NULL) {
DEBUG_INFO("[ESP8266] Duplicate found on server!");
return 1; // 已签到
} else {
DEBUG_INFO("[ESP8266] No duplicate found");
return 0; // 未签到
}
}
DEBUG_ERROR("[ESP8266] Failed to check duplicate, assuming not duplicate");
return 0; // 查询失败时默认为未签到,允许本地签到
}
/**
* @brief 下载今日已签到学生列表(开机时调用)
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @param student_ids 输出:学号数组(逗号分隔的字符串)
* @param buffer_size 缓冲区大小
* @retval 成功下载的学号数量
*/
uint8_t ESP8266_DownloadTodayRecords(const char* slot_start, const char* slot_end, char* student_ids, uint16_t buffer_size)
{
char path[128];
char response_buffer[512];
// 构建查询路径
sprintf(path, "/api/v1/attendance/today?slot_start=%s&slot_end=%s", slot_start, slot_end);
DEBUG_PRINT("[ESP8266] Downloading today's records: %s\r\n", path);
// 先检查连接状态,如果断开则重连
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] Connection lost, reconnecting before download...");
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed");
return 0;
}
Delay_ms(300); // 等待连接稳定
}
// 发送HTTP GET请求
if (ESP8266_HttpGet(path, response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Failed to download today's records");
return 0;
}
// 解析响应,提取学号列表
// 格式: {"date":"2026-02-07","records":["1","2","3"]}
char* records_start = strstr(response_buffer, "\"records\":[");
if (records_start == NULL) {
DEBUG_ERROR("[ESP8266] Invalid response format");
return 0;
}
records_start += 11; // 跳过 "records":[
char* records_end = strchr(records_start, ']');
if (records_end == NULL) {
DEBUG_ERROR("[ESP8266] Invalid response format");
return 0;
}
// 复制学号列表到输出缓冲区
uint16_t len = records_end - records_start;
if (len >= buffer_size) len = buffer_size - 1;
strncpy(student_ids, records_start, len);
student_ids[len] = '\0';
// 统计学号数量(空列表返回0)
uint8_t count = 0;
if (len > 2) { // 至少有一个学号 "x"
count = 1;
for (uint16_t i = 0; i < len; i++) {
if (student_ids[i] == ',') count++;
}
}
DEBUG_PRINT("[ESP8266] Downloaded %d records: %s\r\n", count, student_ids);
return count;
}
uint8_t ESP8266_ConnectTCPServer(const char* ip, const char* port)
{
// 重定向到HTTP版本
return ESP8266_ConnectHTTPServer(ip, port);
}
uint8_t ESP8266_SendData(char* Data)
{
char cmd_buffer[32];
uint16_t data_len = strlen(Data);
DEBUG_PRINT("[ESP8266] Sending %d bytes (legacy mode)\r\n", data_len);
sprintf(cmd_buffer, "AT+CIPSEND=%d", data_len);
ESP8266_SendCmd(cmd_buffer);
uint32_t start_time = Get_Time_ms();
Serial_ClearRxBuffer();
while(Get_Time_ms() - start_time < 1000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
Serial_SendString(Data);
Delay_ms(50);
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
return ESP8266_FAIL;
}