Files
bishe-STM32/Hardware/esp8266.c
T
2026-05-04 21:17:42 +08:00

704 lines
23 KiB
C
Raw 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.
/*
* 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 "Crypto.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);
}
static uint8_t ESP8266_CopyDecryptedBody(char* response,
char* body_start,
uint16_t body_len,
char* response_buffer,
uint16_t buffer_size)
{
uint8_t encrypted = 0;
char saved_char;
uint32_t plain_len;
if (strstr(response, "X-Encrypted: AES128-CBC") != NULL) {
encrypted = 1;
}
if (!encrypted) {
if (body_len >= buffer_size) {
body_len = buffer_size - 1;
}
strncpy(response_buffer, body_start, body_len);
response_buffer[body_len] = '\0';
return ESP8266_SUCCESS;
}
saved_char = body_start[body_len];
body_start[body_len] = '\0';
plain_len = Crypto_DecryptJSON(body_start, response_buffer, buffer_size);
body_start[body_len] = saved_char;
if (plain_len == 0) {
DEBUG_ERROR("[ESP8266] Failed to decrypt encrypted HTTP response");
return ESP8266_FAIL;
}
return ESP8266_SUCCESS;
}
/* --- 公共接口函数实现 --- */
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];
char response_body[192];
uint32_t content_length = strlen(encrypted_data);
char* response;
uint8_t send_ok = 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 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"
"X-Encrypted-Response: true\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);
break;
}
Delay_ms(10);
}
if (Get_Time_ms() - start_time >= 2000 && strstr(Serial_GetRxBuffer(), ">") == NULL) {
DEBUG_ERROR("[ESP8266] No '>' prompt");
return ESP8266_FAIL;
}
start_time = Get_Time_ms();
while(Get_Time_ms() - start_time < 8000) {
response = Serial_GetRxBuffer();
if (strstr(response, "SEND FAIL") != NULL || strstr(response, "ERROR") != NULL) {
DEBUG_ERROR("[ESP8266] POST send failed");
return ESP8266_FAIL;
}
if (strstr(response, "SEND OK") != NULL) {
send_ok = 1;
}
if (send_ok && strstr(response, "HTTP/1.1 200") != NULL) {
char* ipd_marker = strstr(response, "+IPD,");
if (ipd_marker != NULL) {
char* data_start = strchr(ipd_marker, ':');
if (data_start != NULL) {
response = data_start + 1;
}
}
char* body_start = strstr(response, "\r\n\r\n");
if (body_start != NULL) {
uint16_t body_len;
body_start += 4;
body_len = strlen(body_start);
char* cl_str = strstr(response, "Content-Length:");
if (cl_str != NULL) {
uint32_t post_content_length;
cl_str += 15;
while (*cl_str == ' ') cl_str++;
post_content_length = (uint32_t)atoi(cl_str);
if (post_content_length > 0 && body_len > post_content_length) {
body_len = (uint16_t)post_content_length;
}
}
char* closed_marker = strstr(body_start, "\r\nCLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
closed_marker = strstr(body_start, "CLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
if (ESP8266_CopyDecryptedBody(response, body_start, body_len,
response_body, sizeof(response_body)) == ESP8266_SUCCESS
&& strstr(response_body, "\"success\":true") != NULL) {
DEBUG_OK("[ESP8266] POST accepted by server");
return ESP8266_SUCCESS;
}
}
}
if (strstr(response, "HTTP/1.1 401") != NULL ||
strstr(response, "HTTP/1.1 400") != NULL ||
strstr(response, "HTTP/1.1 413") != NULL ||
strstr(response, "HTTP/1.1 500") != NULL ||
strstr(response, "\"success\":false") != NULL) {
DEBUG_PRINT("[ESP8266] POST rejected: %s\r\n", response);
return ESP8266_FAIL;
}
Delay_ms(10);
}
DEBUG_PRINT("[ESP8266] POST response timeout: %s\r\n", Serial_GetRxBuffer());
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[320];
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"
"X-Encrypted-Response: true\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. 如果服务端给了Content-Length,必须收完整;否则才使用超时兜底
if ((found_header && body_len >= content_length) ||
(!found_header && Get_Time_ms() - start_time > 3000 && body_len > 0)) {
if (found_header && body_len > content_length) {
body_len = (uint16_t)content_length;
}
if (ESP8266_CopyDecryptedBody(response, body_start, body_len, response_buffer, buffer_size) != ESP8266_SUCCESS) {
return ESP8266_FAIL;
}
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;
}