from usr.httpota import httpota
from usr.tcpserver import TcpServiceInit
+from usr.httpfiledownload import download_file_spi
# 设置日志输出级别
log.basicConfig(level=log.INFO)
clog = log.getLogger("cat1")
elif cmd== "1D":
TcpService = TcpServiceInit()
TcpService.start_socket_thread()
+ elif cmd== "1E":
+ download_file_spi(str(parm))
elif cmd == "AC":#
if self.ack_sem==1:
self.ack_sem=0
gpioWakeBT.write(1)
# 提前创建1024字节SPI发送缓冲区(全局初始化,避免重复创建)
+# 包头格式:前4字节为数据长度(uint32_t,小端序),后1020字节为数据
SPI_TX_BUFFER = bytearray(1024)
+PACKET_HEADER_SIZE = 4 # 包头大小:4字节(数据长度)
+PACKET_DATA_SIZE = 1020 # 数据部分大小:1020字节(1024 - 4)
# 兼容型字节拷贝函数(替代切片赋值,适配所有MicroPython版本)
def copy_bytes_to_buffer(buffer, data, offset=0):
for i in range(data_len):
buffer[offset + i] = data[i]
+def pack_uint32_le(value):
+ """
+ 将32位无符号整数打包为小端序的4字节
+ :param value: 32位无符号整数
+ :return: 4字节的bytes对象
+ """
+ return bytes([
+ value & 0xFF,
+ (value >> 8) & 0xFF,
+ (value >> 16) & 0xFF,
+ (value >> 24) & 0xFF
+ ])
+
class HttpFileDownloader:
"""
HTTP 文件下载类
combined_data = remaining_data + chunk
remaining_data = b''
- # 拆分数据为1024字节的块(提前填充缓冲区)
- while len(combined_data) >= 1024:
- # 1. 提前填充缓冲区(兼容型逐字节拷贝,替代切片赋值)
- copy_bytes_to_buffer(tx_buffer, combined_data[:1024], 0)
+ # 拆分数据为PACKET_DATA_SIZE字节的块(提前填充缓冲区,带包头)
+ while len(combined_data) >= PACKET_DATA_SIZE:
+ # 1. 填充包头(4字节数据长度,小端序)
+ data_len = PACKET_DATA_SIZE
+ header_bytes = pack_uint32_le(data_len)
+ copy_bytes_to_buffer(tx_buffer, header_bytes, 0)
+
+ # 2. 填充数据部分(1020字节)
+ copy_bytes_to_buffer(tx_buffer, combined_data[:PACKET_DATA_SIZE], PACKET_HEADER_SIZE)
# 剩余数据保留
- combined_data = combined_data[1024:]
+ combined_data = combined_data[PACKET_DATA_SIZE:]
- # 2. GPIO电平切换(原有时序)
+ # 3. GPIO电平切换(原有时序)
gpioWakeBT.write(0)
utime.sleep_ms(2)
gpioWakeBT.write(1)
utime.sleep_ms(2)
- # 3. 发送缓冲区数据(已提前填充,无拼接耗时)
+ # 4. 发送缓冲区数据(包头+数据,共1024字节)
if hasattr(self.spi_obj, 'write'):
self.spi_obj.write(tx_buffer, 1024)
elif hasattr(self.spi_obj, 'write_read'):
clog.error("SPI object does not have write or write_read method")
return False, 0.0, -1
- downloaded_size += 1024
+ downloaded_size += PACKET_DATA_SIZE
# 显示进度(每发送 64KB 打印一次进度)
if total_size > 0:
progress = (downloaded_size / total_size) * 100
- if downloaded_size % (64 * 1024) < 1024:
- clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: 1024 bytes, total padded zeros: {})".format(
- progress, downloaded_size, total_size, total_padded_zeros))
+ if downloaded_size % (64 * 1024) < PACKET_DATA_SIZE:
+ clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: {} bytes data + {} bytes header, total padded zeros: {})".format(
+ progress, downloaded_size, total_size, PACKET_DATA_SIZE, PACKET_HEADER_SIZE, total_padded_zeros))
- # 保存剩余不足1024字节的数据
+ # 保存剩余不足PACKET_DATA_SIZE字节的数据
remaining_data = combined_data
except Exception as e:
clog.error("Error sending data via SPI: {}".format(e))
return False, 0.0, -1
- # 处理最后不足1024字节的剩余数据(提前填充缓冲区补0,兼容版)
+ # 处理最后不足PACKET_DATA_SIZE字节的剩余数据(提前填充缓冲区补0,兼容版,带包头)
if len(remaining_data) > 0:
try:
# 1. 计算补0数(提前计算)
data_len = len(remaining_data)
- pad_len = 1024 - data_len
+ pad_len = PACKET_DATA_SIZE - data_len
total_padded_zeros += pad_len
- # 2. 提前填充缓冲区(兼容型逐字节拷贝,无切片赋值)
- # 清空缓冲区(可选,bytearray初始为0,这里显式清空确保兼容性)
- for i in range(1024):
+ # 2. 填充包头(4字节数据长度,小端序)
+ header_bytes = pack_uint32_le(data_len)
+ copy_bytes_to_buffer(tx_buffer, header_bytes, 0)
+
+ # 3. 提前填充缓冲区(兼容型逐字节拷贝,无切片赋值)
+ # 清空数据部分(可选,bytearray初始为0,这里显式清空确保兼容性)
+ for i in range(PACKET_HEADER_SIZE, 1024):
tx_buffer[i] = 0
- # 填充有效数据(逐字节拷贝,避免下标赋值报错)
- copy_bytes_to_buffer(tx_buffer, remaining_data, 0)
+ # 填充有效数据(从包头后开始,逐字节拷贝,避免下标赋值报错)
+ copy_bytes_to_buffer(tx_buffer, remaining_data, PACKET_HEADER_SIZE)
clog.info("=== Pre-filled SPI Buffer Info ===")
clog.info("Buffer filled with {} bytes valid data (pre-send)".format(data_len))
downloaded_size += data_len # 只统计实际数据长度
clog.info("=== Final SPI Chunk Detail ===")
+ clog.info("Last chunk header: {} bytes (data length: {})".format(PACKET_HEADER_SIZE, data_len))
clog.info("Last chunk actual data: {} bytes".format(data_len))
clog.info("Last chunk padded zeros: {} bytes (pre-send)".format(pad_len))
clog.info("Total padded zeros in transmission: {} bytes".format(total_padded_zeros))
# 最后一块进度显示
if total_size > 0:
progress = (downloaded_size / total_size) * 100
- clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: 1024 bytes, final chunk, total padded zeros: {})".format(
- progress, downloaded_size, total_size, total_padded_zeros))
+ clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: {} bytes data + {} bytes header, final chunk, total padded zeros: {})".format(
+ progress, downloaded_size, total_size, data_len, PACKET_HEADER_SIZE, total_padded_zeros))
except Exception as e:
clog.error("Error sending final SPI chunk: {}".format(e))
return False, 0.0, -1
download_time = end_time - start_time
# 打印整体传输统计
+ total_packets = (downloaded_size + total_padded_zeros + PACKET_DATA_SIZE - 1) // PACKET_DATA_SIZE
clog.info("=== SPI Transmission Summary ===")
clog.info("Total actual data sent: {} bytes".format(downloaded_size))
clog.info("Total padded zero bytes: {} bytes (all pre-send)".format(total_padded_zeros))
- clog.info("Total SPI packets sent: {} (each 1024 bytes)".format((downloaded_size + total_padded_zeros) // 1024))
+ clog.info("Total SPI packets sent: {} (each {} bytes data + {} bytes header = {} bytes total)".format(
+ total_packets, PACKET_DATA_SIZE, PACKET_HEADER_SIZE, 1024))
clog.info("Transmission time: {:.3f} seconds".format(download_time))
+ clog.info("Packet format: [4-byte header (data length)] + [1020-byte data]")
clog.info("Buffer fill mode: Byte-by-byte copy (compatible mode)")
clog.info("================================")
combined_data = remaining_data + chunk
remaining_data = b''
- # 拆分数据为1024字节的块(提前填充缓冲区,兼容版)
- while len(combined_data) >= 1024:
- # 1. 提前填充缓冲区(逐字节拷贝,替代切片赋值)
- copy_bytes_to_buffer(tx_buffer, combined_data[:1024], 0)
+ # 拆分数据为PACKET_DATA_SIZE字节的块(提前填充缓冲区,带包头)
+ while len(combined_data) >= PACKET_DATA_SIZE:
+ # 1. 填充包头(4字节数据长度,小端序)
+ data_len = PACKET_DATA_SIZE
+ header_bytes = pack_uint32_le(data_len)
+ copy_bytes_to_buffer(tx_buffer, header_bytes, 0)
+
+ # 2. 填充数据部分(1020字节)
+ copy_bytes_to_buffer(tx_buffer, combined_data[:PACKET_DATA_SIZE], PACKET_HEADER_SIZE)
# 剩余数据保留
- combined_data = combined_data[1024:]
+ combined_data = combined_data[PACKET_DATA_SIZE:]
- # 2. GPIO电平切换(原有时序)
+ # 3. GPIO电平切换(原有时序)
gpioWakeBT.write(0)
utime.sleep_ms(2)
gpioWakeBT.write(1)
utime.sleep_ms(2)
- # 3. 发送缓冲区数据(无拼接耗时)
+ # 4. 发送缓冲区数据(包头+数据,共1024字节)
chunk_len = 1024
if hasattr(self.spi_obj, 'write'):
self.spi_obj.write(tx_buffer, chunk_len)
self.close() # 关闭SPI
return False, 0.0, -1
- downloaded_size += 1024
+ downloaded_size += PACKET_DATA_SIZE
# 显示进度(每发送64KB打印一次,减少打印数量)
if total_size > 0:
progress = (downloaded_size / total_size) * 100
# 每发送64KB打印一次进度(64*1024=65536)
- if downloaded_size % (64 * 1024) < 1024:
- clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: 1024 bytes, total padded zeros: {})".format(
- progress, downloaded_size, total_size, total_padded_zeros))
+ if downloaded_size % (64 * 1024) < PACKET_DATA_SIZE:
+ clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: {} bytes data + {} bytes header, total padded zeros: {})".format(
+ progress, downloaded_size, total_size, PACKET_DATA_SIZE, PACKET_HEADER_SIZE, total_padded_zeros))
- # 保存剩余不足1024字节的数据
+ # 保存剩余不足PACKET_DATA_SIZE字节的数据
remaining_data = combined_data
except Exception as e:
self.close() # 关闭SPI
return False, 0.0, -1
- # 处理最后不足1024字节的剩余数据(提前填充缓冲区补0,兼容版)
+ # 处理最后不足PACKET_DATA_SIZE字节的剩余数据(提前填充缓冲区补0,兼容版,带包头)
if len(remaining_data) > 0:
try:
# 1. 提前计算补0数和填充缓冲区
data_len = len(remaining_data)
- pad_len = 1024 - data_len
+ pad_len = PACKET_DATA_SIZE - data_len
total_padded_zeros += pad_len
- # 2. 提前填充缓冲区(逐字节拷贝,无切片赋值)
- # 清空缓冲区(显式清空,确保兼容性)
- for i in range(1024):
+ # 2. 填充包头(4字节数据长度,小端序)
+ header_bytes = pack_uint32_le(data_len)
+ copy_bytes_to_buffer(tx_buffer, header_bytes, 0)
+
+ # 3. 提前填充缓冲区(逐字节拷贝,无切片赋值)
+ # 清空数据部分(显式清空,确保兼容性)
+ for i in range(PACKET_HEADER_SIZE, 1024):
tx_buffer[i] = 0
- # 填充有效数据(逐字节拷贝)
- copy_bytes_to_buffer(tx_buffer, remaining_data, 0)
+ # 填充有效数据(从包头后开始,逐字节拷贝)
+ copy_bytes_to_buffer(tx_buffer, remaining_data, PACKET_HEADER_SIZE)
clog.info("=== Pre-filled SPI Buffer Info ===")
clog.info("Buffer filled with {} bytes valid data (pre-send)".format(data_len))
downloaded_size += data_len # 只统计实际数据长度
# 打印最后一块补0详情
clog.info("=== Final SPI Chunk Detail ===")
+ clog.info("Last chunk header: {} bytes (data length: {})".format(PACKET_HEADER_SIZE, data_len))
clog.info("Last chunk actual data: {} bytes".format(data_len))
clog.info("Last chunk padded zeros: {} bytes (all pre-send)".format(pad_len))
clog.info("Total padded zeros in transmission: {} bytes".format(total_padded_zeros))
# 最后一块进度显示
if total_size > 0:
progress = (downloaded_size / total_size) * 100
- clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: 1024 bytes, final chunk, total padded zeros: {})".format(
- progress, downloaded_size, total_size, total_padded_zeros))
+ clog.info("SPI TX Progress: {:.2f}% ({}/{}) bytes (TX: {} bytes data + {} bytes header, final chunk, total padded zeros: {})".format(
+ progress, downloaded_size, total_size, data_len, PACKET_HEADER_SIZE, total_padded_zeros))
except Exception as e:
clog.error("Error sending final SPI chunk: {}".format(e))
self.close() # 关闭SPI
speed_mbps = speed_kbps / 1024.0 # Mbps
# 打印整体传输统计信息
+ total_packets = (downloaded_size + total_padded_zeros + PACKET_DATA_SIZE - 1) // PACKET_DATA_SIZE
clog.info("=== SPI Transmission Complete Summary ===")
clog.info("Total actual data transmitted: {} bytes ({:.2f} MB)".format(downloaded_size, downloaded_size/1024/1024))
clog.info("Total padded zero bytes: {} bytes ({:.2f} KB) (all pre-send)".format(total_padded_zeros, total_padded_zeros/1024))
- clog.info("Total SPI packets sent: {} (each 1024 bytes)".format((downloaded_size + total_padded_zeros) // 1024))
+ clog.info("Total SPI packets sent: {} (each {} bytes data + {} bytes header = {} bytes total)".format(
+ total_packets, PACKET_DATA_SIZE, PACKET_HEADER_SIZE, 1024))
+ clog.info("Packet format: [4-byte header (data length)] + [1020-byte data]")
clog.info("Total transmission time: {:.3f} seconds".format(download_time))
if speed_mbps >= 1.0:
clog.info("Effective transmission speed: {:.2f} Mbps ({:.2f} Kbps)".format(speed_mbps, speed_kbps))
test.close()
# 自动启动SPI传输速度测试(替代原来的TCP服务器)
-start_spi_speed_test()
+# start_spi_speed_test()
# 示例用法
# if __name__ == '__main__':
# # 方式1: 使用类下载并保存文件
#
# downloader.close()
+# ==================== 外部接口函数 ====================
+# 提供简单的函数接口,可以从外部传入URL进行下载和SPI传输
+
+def download_file_spi(url, timeout=30, sizeof=255, spi_obj=None):
+ """
+ 下载文件并通过SPI传输的简单接口函数
+
+ Args:
+ url: 要下载的文件URL(必需)
+ timeout: 下载超时时间(秒),默认30秒
+ sizeof: 读取缓冲区的数据块大小,默认255字节
+ spi_obj: SPI对象,用于发送数据。如果为None,则自动创建SPI(2, 0, 12)
+
+ Returns:
+ tuple: (success: bool, download_time: float, file_size: int)
+ success: 下载是否成功
+ download_time: 下载耗时(秒)
+ file_size: 下载的文件大小(字节),失败时为-1
+
+ Example:
+ # 基本用法
+ success, time, size = download_file_via_spi("https://example.com/file.zip")
+
+ # 自定义参数
+ success, time, size = download_file_via_spi(
+ url="https://example.com/file.zip",
+ timeout=60,
+ sizeof=512
+ )
+ """
+ if not url or not isinstance(url, str):
+ clog.error("download_file_via_spi: Invalid URL parameter")
+ return False, 0.0, -1
+
+ test = HttpFileDownloadSpiTest(url=url, timeout=timeout, sizeof=sizeof, spi_obj=spi_obj)
+ try:
+ success, download_time, file_size = test.start_download_and_send()
+ if success:
+ clog.info("download_file_via_spi: Download completed successfully")
+ clog.info(" URL: {}".format(url))
+ clog.info(" Size: {} bytes".format(file_size))
+ clog.info(" Time: {:.3f} seconds".format(download_time))
+ else:
+ clog.error("download_file_via_spi: Download failed")
+ clog.error(" URL: {}".format(url))
+ return success, download_time, file_size
+ except Exception as e:
+ clog.error("download_file_via_spi: Exception occurred: {}".format(e))
+ return False, 0.0, -1
+ finally:
+ # 确保SPI对象被关闭
+ test.close()
+
+
+class HttpFileDownloadSpiClient:
+ """
+ HTTP文件下载SPI传输客户端类
+ 提供面向对象的接口,支持传入URL进行下载和SPI传输
+ """
+
+ def __init__(self, timeout=30, sizeof=255, spi_obj=None):
+ """
+ 初始化SPI传输客户端
+
+ Args:
+ timeout: 下载超时时间(秒),默认30秒
+ sizeof: 读取缓冲区的数据块大小,默认255字节
+ spi_obj: SPI对象,用于发送数据。如果为None,则自动创建SPI(2, 0, 12)
+ """
+ self.timeout = timeout
+ self.sizeof = sizeof
+ self.spi_obj = spi_obj
+ self.test_instance = None
+
+ def download(self, url):
+ """
+ 下载指定URL的文件并通过SPI传输
+
+ Args:
+ url: 要下载的文件URL(必需)
+
+ Returns:
+ tuple: (success: bool, download_time: float, file_size: int)
+ success: 下载是否成功
+ download_time: 下载耗时(秒)
+ file_size: 下载的文件大小(字节),失败时为-1
+
+ Example:
+ client = HttpFileDownloadSpiClient()
+ success, time, size = client.download("https://example.com/file.zip")
+ client.close()
+ """
+ if not url or not isinstance(url, str):
+ clog.error("HttpFileDownloadSpiClient.download: Invalid URL parameter")
+ return False, 0.0, -1
+
+ # 创建测试实例(每次下载都创建新的,确保状态干净)
+ self.test_instance = HttpFileDownloadSpiTest(
+ url=url,
+ timeout=self.timeout,
+ sizeof=self.sizeof,
+ spi_obj=self.spi_obj
+ )
+
+ try:
+ success, download_time, file_size = self.test_instance.start_download_and_send()
+ return success, download_time, file_size
+ except Exception as e:
+ clog.error("HttpFileDownloadSpiClient.download: Exception occurred: {}".format(e))
+ return False, 0.0, -1
+ finally:
+ # 清理实例(start_download_and_send内部已关闭SPI)
+ if self.test_instance:
+ self.test_instance.close()
+ self.test_instance = None
+
+ def close(self):
+ """
+ 关闭客户端,释放资源
+ """
+ if self.test_instance:
+ self.test_instance.close()
+ self.test_instance = None
+
+ def __enter__(self):
+ """上下文管理器入口"""
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """上下文管理器出口"""
+ self.close()
+
+
# TCP 服务器使用示例
# 创建并启动 TCP 服务器,监听客户端连接并转发下载的文件
# tcp_server = HttpFileDownloadTcpServer(host='172.16.1.2', port=5002, timeout=30, sizeof=255)
# tcp_server.start()
#
# # 服务器会在后台运行,等待客户端连接
-# # 客户端可以通过 TCP 连接发送 URL,服务器会下载文件并通过 socket 发送数据
\ No newline at end of file
+# # 客户端可以通过 TCP 连接发送 URL,服务器会下载文件并通过 socket 发送数据
+#
+# ==================== 使用示例 ====================
+#
+# # 方式1: 使用简单函数接口(推荐用于一次性下载)
+# success, time, size = download_file_via_spi("https://example.com/file.zip")
+# if success:
+# clog.info("Download successful: {} bytes in {:.3f}s".format(size, time))
+#
+# # 方式2: 使用客户端类(推荐用于多次下载)
+# client = HttpFileDownloadSpiClient(timeout=60, sizeof=512)
+# success1, time1, size1 = client.download("https://example.com/file1.zip")
+# success2, time2, size2 = client.download("https://example.com/file2.zip")
+# client.close()
+#
+# # 方式3: 使用上下文管理器(推荐,自动资源管理)
+# with HttpFileDownloadSpiClient() as client:
+# success, time, size = client.download("https://example.com/file.zip")
+# if success:
+# clog.info("Downloaded {} bytes in {:.3f}s".format(size, time))
+#
+# # 方式4: 使用原有的测试类(更灵活的控制)
+# test = HttpFileDownloadSpiTest(url="https://example.com/file.zip", timeout=30)
+# success, time, size = test.start_download_and_send()
+# test.close()
\ No newline at end of file
UDP_MSG_TCP_OTA_INIT= 0x1D,//TCP ota升级
+ UDP_CMD_HTTP_FILE_DOWNLOAD = 0x1E, // HTTP文件下载(通过SPI传输)
+
UDP_MSG_WATCH_REPLY_ACK = 0xAC,//
// UDP_MSG_WATCH_REPLY_HANGUP = 0x21,//
};
/* HTTP File Download Client
*
- * This file implements a TCP client to connect to the HTTP file download server
- * and receive downloaded files.
+ * This file implements a HTTP file download service using SPI transmission
+ * Downloads files from URL and transfers data via SPI to MCU
*
- * Reference: cat1_tcpServer.c
+ * Reference: cat1_tcpServer.c, custom_spi.c
*/
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
#include "cat1_log.h"
#include "osal_timer.h"
#include "soc_osal.h"
#include "common_def.h"
+/* Include SPI transmission API */
+#include "custom_spi.h"
+#include "cat1_server.h"
+#include "http_file_download_client.h"
+
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
-// 172.16.1.2
-/* Default server configuration */
-#define HTTP_DOWNLOAD_SERVER_IP "172.16.1.2"
-#define HTTP_DOWNLOAD_SERVER_PORT 5002
-
-#define LOACL_SERVER_IP "172.16.1.5"
-#define LOACL_PORT 1002
-
-/* Buffer sizes */
-#define MAX_URL_LEN 512
-#define MAX_RECV_BUFFER_SIZE 4096
-#define MAX_RESPONSE_BUFFER_SIZE 256
-
-/* Timeout settings */
-#define CONNECT_TIMEOUT_MS 5000
-#define RECV_TIMEOUT_MS 30000
-
-static int g_client_sock = -1;
-static uint8_t g_client_connected = 0;
-static uint8_t g_download_task_running = 0;
-/* Task configuration */
-#define HTTP_DOWNLOAD_TASK_SIZE (0x800 * 5) // 20KB stack
-#define HTTP_DOWNLOAD_TASK_PRIO 5
-
-/* Download task parameters structure */
-typedef struct {
- char url[MAX_URL_LEN];
- char save_path[256];
-} http_download_task_params_t;
-
-/* Static parameters for async download task */
-static http_download_task_params_t s_task_params;
-
-#ifndef UNUSED
-#define UNUSED(x) ((void)(x))
+/* UDP命令定义(从cat1_server.c中引用) */
+/* 注意:UDP_CMD_HTTP_FILE_DOWNLOAD定义在cat1_server.c的enum UDP_COMMAND_HEAD中 */
+#ifndef UDP_CMD_HTTP_FILE_DOWNLOAD
+#define UDP_CMD_HTTP_FILE_DOWNLOAD 0x1E /* HTTP文件下载(通过SPI传输) */
#endif
-#define cat1_msleep(ms) osDelay(ms) /* osDelay(1) : 1ms */
+// /* 下载服务状态 */
+// typedef enum {
+// HTTP_DOWNLOAD_STATE_IDLE = 0, /* 空闲状态 */
+// HTTP_DOWNLOAD_STATE_DOWNLOADING, /* 下载中 */
+// HTTP_DOWNLOAD_STATE_COMPLETED, /* 下载完成 */
+// HTTP_DOWNLOAD_STATE_ERROR /* 下载错误 */
+// } http_download_state_t;
+
+/* 下载服务全局变量 */
+static http_download_state_t g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
+static osSemaphoreId_t g_download_complete_sem = NULL; /* 下载完成信号量 */
+static int g_download_file_fd = -1; /* 下载文件句柄 */
+static char g_download_file_path[512] = {0}; /* 下载文件路径 */
+static uint32_t g_total_received_bytes = 0; /* 总接收字节数 */
+static uint32_t g_total_received_packets = 0; /* 总接收包数 */
+
+/* 前向声明 */
+extern void udp_send_msg_toCat1(uint8_t cmd, char *content);
/**
- * Initialize TCP client socket
- *
- * @return socket file descriptor on success, -1 on failure
+ * @brief SPI数据接收回调函数(用于下载服务)
+ * @param data 接收到的数据指针(不包含包头,仅数据部分)
+ * @param data_len 数据长度(从包头解析得到)
+ * @param packet_num 包序号(从1开始递增)
+ * @return 0表示成功,非0表示失败
*/
-static int http_download_client_socket_init(void)
+static int http_download_spi_data_callback(const uint8_t *data, uint32_t data_len, uint32_t packet_num)
{
- int sock = -1;
- int addr_family = AF_INET;
- int ip_protocol = IPPROTO_IP;
- struct sockaddr_in server_addr;
- char addr_str[128];
- int opt = 1;
-
- // Create TCP socket
- sock = socket(addr_family, SOCK_STREAM, ip_protocol);
- if (sock < 0) {
- cat1_print_err("Unable to create socket: errno %d\n", errno);
+ if (data == NULL || data_len == 0) {
+ cat1_print_err("HTTP download: Invalid data in callback\n");
return -1;
}
-
- // Set socket options
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
-
- // Set receive timeout
- struct timeval timeout;
- timeout.tv_sec = RECV_TIMEOUT_MS / 1000;
- timeout.tv_usec = (RECV_TIMEOUT_MS % 1000) * 1000;
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
-
- // Configure server address
- server_addr.sin_addr.s_addr = inet_addr(LOACL_SERVER_IP);
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(LOACL_PORT);
- inet_ntoa_r(server_addr.sin_addr, addr_str, sizeof(addr_str) - 1);
- cat1_print_info("Connecting to server %s:%d\n", addr_str, LOACL_PORT);
-
- return sock;
-}
-
-/**
- * Connect to the HTTP download server
- *
- * @return 0 on success, -1 on failure
- */
-static int http_download_client_connect(int sock)
-{
- struct sockaddr_in server_addr;
+ // 更新统计信息
+ g_total_received_bytes += data_len;
+ g_total_received_packets = packet_num;
- server_addr.sin_addr.s_addr = inet_addr(HTTP_DOWNLOAD_SERVER_IP);
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(HTTP_DOWNLOAD_SERVER_PORT);
-
- int ret = connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
- if (ret < 0) {
- cat1_print_err("Unable to connect: errno %d\n", errno);
- return -1;
+ // 写入文件(如果文件已打开)
+ if (g_download_file_fd >= 0) {
+ ssize_t written = write(g_download_file_fd, data, data_len);
+ if (written < 0) {
+ cat1_print_err("HTTP download: Failed to write to file, errno=%d\n", errno);
+ return -1;
+ }
+ if ((uint32_t)written != data_len) {
+ cat1_print_err("HTTP download: Partial write: expected %u bytes, written %zd bytes\n",
+ data_len, written);
+ return -1;
+ }
+
+ // 每100个包打印一次进度(减少日志输出)
+ if (packet_num % 100 == 0) {
+ cat1_print_info("HTTP download: Packet #%u, data_len=%u, total_bytes=%u\n",
+ packet_num, data_len, g_total_received_bytes);
+ }
+ } else {
+ // 文件未打开,仅打印日志
+ cat1_print_info("HTTP download: Packet #%u, data_len=%u, total_bytes=%u (file not open)\n",
+ packet_num, data_len, g_total_received_bytes);
}
- cat1_print_info("Connected to server successfully\n");
+ if(data_len!=SPI_PACKET_DATA_SIZE)
+ {
+ http_file_download_notify_complete(1);
+ }
+
return 0;
}
/**
- * Send URL request to server
- *
- * @param sock socket file descriptor
- * @param url URL to download
- * @return 0 on success, -1 on failure
+ * @brief 初始化HTTP文件下载服务
+ * @return 0成功,-1失败
*/
-static int http_download_client_send_url(int sock, const char *url)
+int http_file_download_init(void)
{
- if (!url || strlen(url) == 0) {
- cat1_print_err("Invalid URL\n");
- return -1;
- }
-
- if (strlen(url) >= MAX_URL_LEN) {
- cat1_print_err("URL too long (max %d)\n", MAX_URL_LEN - 1);
- return -1;
+ cat1_print_info("HTTP file download service: Initializing...\n");
+
+ // 检查是否已经初始化
+ if (g_download_complete_sem != NULL) {
+ cat1_print_warning("HTTP file download service: Already initialized\n");
+ return 0;
}
-
- cat1_print_info("Sending URL: %s\n", url);
-
- int ret = send(sock, url, strlen(url), 0);
- if (ret < 0) {
- cat1_print_err("Send failed: errno %d\n", errno);
+
+ // 创建下载完成信号量(初始值为0,用于阻塞等待)
+ g_download_complete_sem = osSemaphoreNew(1, 0, NULL);
+ if (g_download_complete_sem == NULL) {
+ cat1_print_err("HTTP file download service: Failed to create semaphore\n");
return -1;
}
-
- cat1_print_info("URL sent successfully (%d bytes)\n", ret);
+
+ // 初始化SPI服务
+ custom_spi_init();
+ cat1_print_info("HTTP file download service: SPI initialized\n");
+
+ // 注册SPI数据接收回调函数
+ custom_spi_register_data_callback(http_download_spi_data_callback);
+
+ // 重置状态和统计信息
+ g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
+ g_total_received_bytes = 0;
+ g_total_received_packets = 0;
+
+ cat1_print_info("HTTP file download service: Initialized successfully\n");
return 0;
}
/**
- * Receive server response message
- *
- * @param sock socket file descriptor
- * @param buffer buffer to store response
- * @param buffer_size buffer size
- * @return number of bytes received, -1 on error
+ * @brief 反初始化HTTP文件下载服务
*/
-static int http_download_client_recv_response(int sock, char *buffer, int buffer_size)
+void http_file_download_deinit(void)
{
- if (!buffer || buffer_size <= 0) {
- return -1;
+ cat1_print_info("HTTP file download service: Deinitializing...\n");
+
+ // 取消注册SPI数据回调
+ custom_spi_register_data_callback(NULL);
+
+ // 反初始化SPI服务
+ custom_spi_deinit();
+ cat1_print_info("HTTP file download service: SPI deinitialized\n");
+
+ // 删除信号量
+ if (g_download_complete_sem != NULL) {
+ osSemaphoreDelete(g_download_complete_sem);
+ g_download_complete_sem = NULL;
}
-
- memset(buffer, 0, buffer_size);
- int len = recv(sock, buffer, buffer_size - 1, 0);
- if (len < 0) {
- cat1_print_err("Receive failed: errno %d\n", errno);
- return -1;
- } else if (len == 0) {
- cat1_print_info("Connection closed by server\n");
- return 0;
+ // 关闭文件(如果已打开)
+ if (g_download_file_fd >= 0) {
+ close(g_download_file_fd);
+ g_download_file_fd = -1;
}
-
- buffer[len] = '\0';
- cat1_print_info("Received response: %s\n", buffer);
+ memset(g_download_file_path, 0, sizeof(g_download_file_path));
+
+ // 重置状态和统计信息
+ g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
+ g_total_received_bytes = 0;
+ g_total_received_packets = 0;
- return len;
+ cat1_print_info("HTTP file download service: Deinitialized successfully\n");
}
/**
- * Receive file data and save to file
+ * @brief 开始HTTP文件下载(阻塞接口)
+ * @param url 要下载的文件URL
+ * @param file_path 保存文件的路径,NULL或空字符串表示不保存文件(仅接收数据)
+ * @param timeout_ms 超时时间(毫秒),0表示使用默认值(300秒)
+ * @return 0成功,-1失败
*
- * @param sock socket file descriptor
- * @param save_path file path to save
- * @return 0 on success, -1 on failure
+ * 此函数会:
+ * 1. 打开文件(如果提供了文件路径)
+ * 2. 通过UDP发送下载指令到CAT1(携带URL地址)
+ * 3. 使用信号量阻塞等待下载完成
+ * 4. 下载完成后关闭文件并返回
*/
-static int http_download_client_recv_file_data(int sock, const char *save_path)
+int http_file_download_start(const char *url, const char *file_path, uint32_t timeout_ms)
{
- FILE *fp = NULL;
- char recv_buffer[MAX_RECV_BUFFER_SIZE];
- uint32_t total_received = 0;
- int bytes_received = 0;
- int file_size = 0;
- char content_length_header[64] = {0};
-
- // First, receive server response (OK message and Content-Length if available)
- char response_buffer[MAX_RESPONSE_BUFFER_SIZE];
- int response_len = http_download_client_recv_response(sock, response_buffer, sizeof(response_buffer));
+ if (url == NULL || strlen(url) == 0) {
+ cat1_print_err("HTTP file download: Invalid URL\n");
+ return -1;
+ }
- if (response_len <= 0) {
- cat1_print_err("Failed to receive server response\n");
+ if (g_download_complete_sem == NULL) {
+ cat1_print_err("HTTP file download: Service not initialized, call http_file_download_init() first\n");
return -1;
}
-
- // Parse Content-Length if present
- if (strstr(response_buffer, "Content-Length:") != NULL) {
- sscanf(response_buffer, "Content-Length: %d", &file_size);
- cat1_print_info("Expected file size: %d bytes\n", file_size);
-
- // Skip the header line, read actual data
- // The server sends header followed by file data
- // We need to find where the header ends
- char *data_start = strstr(response_buffer, "\n");
- if (data_start) {
- data_start++; // Skip newline
- // Some data might already be in the buffer
- }
+
+ if (g_download_state == HTTP_DOWNLOAD_STATE_DOWNLOADING) {
+ cat1_print_warning("HTTP file download: Download already in progress\n");
+ return -1;
}
-
- // Open file for writing
- if (save_path && strlen(save_path) > 0) {
- fp = fopen(save_path, "wb");
- if (fp == NULL) {
- cat1_print_err("Failed to open file: %s\n", save_path);
+
+ cat1_print_info("HTTP file download: Starting download from URL: %s\n", url);
+
+ // 关闭之前的文件(如果已打开)
+ if (g_download_file_fd >= 0) {
+ close(g_download_file_fd);
+ g_download_file_fd = -1;
+ }
+ memset(g_download_file_path, 0, sizeof(g_download_file_path));
+
+ // 打开文件(如果提供了文件路径)
+ if (file_path != NULL && strlen(file_path) > 0) {
+ // 保存文件路径
+ strncpy(g_download_file_path, file_path, sizeof(g_download_file_path) - 1);
+ g_download_file_path[sizeof(g_download_file_path) - 1] = '\0';
+
+ // 打开文件(创建或截断)
+ g_download_file_fd = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (g_download_file_fd < 0) {
+ cat1_print_err("HTTP file download: Failed to open file '%s', errno=%d\n", file_path, errno);
return -1;
}
- cat1_print_info("Saving file to: %s\n", save_path);
+ cat1_print_info("HTTP file download: File opened: %s\n", file_path);
} else {
- cat1_print_info("No save path specified, discarding data\n");
+ cat1_print_info("HTTP file download: No file path provided, data will not be saved\n");
+ g_download_file_fd = -1;
}
-
- // Receive file data
- while (1) {
- memset(recv_buffer, 0, sizeof(recv_buffer));
- bytes_received = recv(sock, recv_buffer, sizeof(recv_buffer), 0);
-
- if (bytes_received < 0) {
- cat1_print_err("Receive error: errno %d\n", errno);
- if (fp) {
- fclose(fp);
+
+ // 重置状态和统计信息
+ g_download_state = HTTP_DOWNLOAD_STATE_DOWNLOADING;
+ g_total_received_bytes = 0;
+ g_total_received_packets = 0;
+
+ // 通过UDP发送下载指令到CAT1(携带URL地址)
+ udp_send_msg_toCat1(UDP_CMD_HTTP_FILE_DOWNLOAD, (char *)url);
+
+ cat1_print_info("HTTP file download: Download command sent, waiting for completion...\n");
+
+ // 使用信号量阻塞等待下载完成
+ // 默认超时时间:300秒(如果timeout_ms为0)
+ uint32_t wait_timeout = (timeout_ms == 0) ? (300 * 1000) : timeout_ms;
+
+ osStatus_t sem_ret = osSemaphoreAcquire(g_download_complete_sem, wait_timeout);
+
+ // 关闭文件(无论成功或失败)
+ if (g_download_file_fd >= 0) {
+ close(g_download_file_fd);
+ g_download_file_fd = -1;
+ cat1_print_info("HTTP file download: File closed: %s\n", g_download_file_path);
+ }
+
+ if (sem_ret == osOK) {
+ // 下载完成
+ if (g_download_state == HTTP_DOWNLOAD_STATE_COMPLETED) {
+ cat1_print_info("HTTP file download: Completed successfully\n");
+ cat1_print_info("HTTP file download: Total received: %u bytes, %u packets\n",
+ g_total_received_bytes, g_total_received_packets);
+ if (strlen(g_download_file_path) > 0) {
+ cat1_print_info("HTTP file download: File saved to: %s\n", g_download_file_path);
}
+ g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
+ return 0;
+ } else if (g_download_state == HTTP_DOWNLOAD_STATE_ERROR) {
+ cat1_print_err("HTTP file download: Failed with error\n");
+ g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
+ return -1;
+ } else {
+ cat1_print_warning("HTTP file download: Unexpected state: %d\n", g_download_state);
+ g_download_state = HTTP_DOWNLOAD_STATE_IDLE;
return -1;
- } else if (bytes_received == 0) {
- cat1_print_info("Connection closed by server (file transfer complete)\n");
- break;
- }
-
- total_received += bytes_received;
-
- // Check if this is a status message (OK: or ERROR:)
- recv_buffer[bytes_received] = '\0';
- if (strncmp((char *)recv_buffer, "OK:", 3) == 0 ||
- strncmp((char *)recv_buffer, "ERROR:", 6) == 0) {
- cat1_print_info("Server message: %s\n", recv_buffer);
-
- // If we've already received some data, the status might be after file data
- // In this case, we should have already written the data
- if (total_received > bytes_received) {
- // This is a final status message
- break;
- }
- continue;
- }
-
- // Write data to file
- if (fp) {
- size_t written = fwrite(recv_buffer, 1, bytes_received, fp);
- if (written != bytes_received) {
- cat1_print_err("Write error: written %d, expected %d\n", written, bytes_received);
- fclose(fp);
- return -1;
- }
- fflush(fp);
- }
-
- // Print progress every 64KB
- if (total_received % (64 * 1024) < sizeof(recv_buffer)) {
- if (file_size > 0) {
- float progress = (float)total_received / file_size * 100.0f;
- cat1_print_info("Progress: %.2f%% (%u/%d bytes)\n",
- progress, total_received, file_size);
- } else {
- cat1_print_info("Received: %u bytes\n", total_received);
- }
}
- }
-
- if (fp) {
- fclose(fp);
- cat1_print_info("File saved successfully: %s (%u bytes)\n", save_path, total_received);
- } else {
- cat1_print_info("Data discarded successfully (%u bytes)\n", total_received);
- }
-
- return 0;
-}
-
-/**
- * Download file from URL via HTTP download server
- *
- * @param url URL to download
- * @param save_path file path to save (NULL to discard data)
- * @return 0 on success, -1 on failure
- */
-int http_download_client_download_file(const char *url, const char *save_path)
-{
- int sock = -1;
- int ret = -1;
-
- if (!url || strlen(url) == 0) {
- cat1_print_err("Invalid URL parameter\n");
+ } else if (sem_ret == osErrorTimeout) {
+ // 超时
+ cat1_print_err("HTTP file download: Timeout after %u ms\n", wait_timeout);
+ g_download_state = HTTP_DOWNLOAD_STATE_ERROR;
return -1;
- }
-
- // Initialize socket
- sock = http_download_client_socket_init();
- if (sock < 0) {
- return -1;
- }
-
- // Connect to server
- if (http_download_client_connect(sock) < 0) {
- lwip_close(sock);
- return -1;
- }
-
- g_client_sock = sock;
- g_client_connected = 1;
-
- // Send URL request
- if (http_download_client_send_url(sock, url) < 0) {
- lwip_close(sock);
- g_client_sock = -1;
- g_client_connected = 0;
+ } else {
+ // 信号量错误
+ cat1_print_err("HTTP file download: Semaphore error: %d\n", sem_ret);
+ g_download_state = HTTP_DOWNLOAD_STATE_ERROR;
return -1;
}
-
- // Receive and save file data
- ret = http_download_client_recv_file_data(sock, save_path);
-
- // Cleanup
- lwip_close(sock);
- g_client_sock = -1;
- g_client_connected = 0;
-
- return ret;
}
/**
- * Close client connection
+ * @brief 通知下载完成(由外部调用,例如从CAT1收到完成消息时)
+ * @param success 是否成功(1=成功,0=失败)
*/
-void http_download_client_close(void)
+void http_file_download_notify_complete(int success)
{
- if (g_client_sock >= 0) {
- lwip_close(g_client_sock);
- g_client_sock = -1;
- g_client_connected = 0;
- cat1_print_info("Client connection closed\n");
+ if (g_download_complete_sem == NULL) {
+ cat1_print_warning("HTTP file download: Semaphore not initialized\n");
+ return;
}
-}
-
-/**
- * Check if client is connected
- *
- * @return 1 if connected, 0 otherwise
- */
-uint8_t http_download_client_is_connected(void)
-{
- return g_client_connected;
-}
-
-/**
- * Task function for HTTP download client
- * Executes the download in a separate thread
- *
- * @param pvParameters unused (parameters are stored in static variable)
- */
-static void http_download_client_task_func(void *pvParameters)
-{
- UNUSED(pvParameters);
- cat1_print_info("HTTP download client task started\n");
- cat1_print_info("URL: %s\n", s_task_params.url);
- if (s_task_params.save_path[0] != '\0') {
- cat1_print_info("Save path: %s\n", s_task_params.save_path);
- } else {
- cat1_print_info("No save path (discard mode)\n");
- }
-
- // Execute download
- const char *save_path_ptr = NULL;
- if (s_task_params.save_path[0] != '\0') {
- save_path_ptr = s_task_params.save_path;
+ if (g_download_state != HTTP_DOWNLOAD_STATE_DOWNLOADING) {
+ cat1_print_warning("HTTP file download: Not in downloading state, current state: %d\n",
+ g_download_state);
+ return;
}
-
- int ret = http_download_client_download_file(s_task_params.url, save_path_ptr);
- if (ret == 0) {
- cat1_print_info("HTTP download client task completed successfully\n");
+ // 设置完成状态
+ if (success) {
+ g_download_state = HTTP_DOWNLOAD_STATE_COMPLETED;
+ cat1_print_info("HTTP file download: Download completed, total: %u bytes, %u packets\n",
+ g_total_received_bytes, g_total_received_packets);
} else {
- cat1_print_err("HTTP download client task failed\n");
+ g_download_state = HTTP_DOWNLOAD_STATE_ERROR;
+ cat1_print_err("HTTP file download: Download failed\n");
}
-
- g_download_task_running = 0;
- cat1_print_info("HTTP download client task ended\n");
+ // 释放信号量,唤醒等待的线程
+ osSemaphoreRelease(g_download_complete_sem);
}
/**
- * Start HTTP download in an asynchronous thread
- *
- * @param url URL to download
- * @param save_path file path to save (NULL or empty to discard data)
- * @return ERRCODE_SUCC on success, ERRCODE_FAIL on failure
+ * @brief 获取下载统计信息
+ * @param total_bytes 输出参数:总接收字节数
+ * @param total_packets 输出参数:总接收包数
+ * @return 0成功,-1失败
*/
-errcode_t http_download_client_download_async(const char *url, const char *save_path)
+int http_file_download_get_stats(uint32_t *total_bytes, uint32_t *total_packets)
{
- if (!url || strlen(url) == 0) {
- cat1_print_err("Invalid URL parameter\n");
- return ERRCODE_FAIL;
- }
-
- if (strlen(url) >= MAX_URL_LEN) {
- cat1_print_err("URL too long (max %d)\n", MAX_URL_LEN - 1);
- return ERRCODE_FAIL;
- }
-
- // Check if task is already running
- // if (g_download_task_running) {
- // cat1_print_err("Download task is already running\n");
- // return ERRCODE_FAIL;
- // }
-
- // Initialize static parameters (stored globally for task function to access)
- memset(&s_task_params, 0, sizeof(http_download_task_params_t));
- strncpy(s_task_params.url, url, MAX_URL_LEN - 1);
- s_task_params.url[MAX_URL_LEN - 1] = '\0';
-
- if (save_path && strlen(save_path) > 0) {
- strncpy(s_task_params.save_path, save_path, sizeof(s_task_params.save_path) - 1);
- s_task_params.save_path[sizeof(s_task_params.save_path) - 1] = '\0';
- }
-
- // Configure task
- app_task_definition_t taskcfg = {0};
- taskcfg.create_queue_handle = NULL;
- taskcfg.attr.name = "http_download_client";
- taskcfg.attr.priority = TASK_PRIORITY_APP;
- taskcfg.attr.stack_size = HTTP_DOWNLOAD_TASK_SIZE;
- taskcfg.func = http_download_client_task_func;
- taskcfg.use_mem = USE_EXTEND_MEM;
-
- // Mark task as running before creating thread
- g_download_task_running = 1;
-
- // Create thread
- errcode_t ret = uapi_create_osThread_custom(&taskcfg);
- if (ret != ERRCODE_SUCC) {
- cat1_print_err("Failed to create HTTP download client task: %d\n", ret);
- g_download_task_running = 0;
- return ERRCODE_FAIL;
+ if (total_bytes == NULL || total_packets == NULL) {
+ return -1;
}
-
- cat1_print_info("HTTP download client task created successfully\n");
- return ERRCODE_SUCC;
-}
-
-/**
- * Check if download task is running
- *
- * @return 1 if running, 0 otherwise
- */
-uint8_t http_download_client_is_task_running(void)
-{
- return g_download_task_running;
+ *total_bytes = g_total_received_bytes;
+ *total_packets = g_total_received_packets;
+ return 0;
}
/**
- * Task function wrapper (legacy, kept for compatibility)
+ * @brief 获取当前下载状态
+ * @return 当前下载状态
*/
-void http_download_client_task(void *pvParameters)
+http_download_state_t http_file_download_get_state(void)
{
- http_download_client_task_func(pvParameters);
+ return g_download_state;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
-
/* HTTP File Download Client Header
*
- * This header file defines the interface for the HTTP file download client.
+ * This header file defines the interface for the HTTP file download client
+ * using SPI transmission.
*/
#ifndef __HTTP_FILE_DOWNLOAD_CLIENT_H
#include <stdint.h>
#include "errcode.h"
+#include "custom_spi.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
+/* 下载服务状态枚举 */
+typedef enum {
+ HTTP_DOWNLOAD_STATE_IDLE = 0, /* 空闲状态 */
+ HTTP_DOWNLOAD_STATE_DOWNLOADING, /* 下载中 */
+ HTTP_DOWNLOAD_STATE_COMPLETED, /* 下载完成 */
+ HTTP_DOWNLOAD_STATE_ERROR /* 下载错误 */
+} http_download_state_t;
+
+/**
+ * @brief 初始化HTTP文件下载服务
+ * @return 0成功,-1失败
+ */
+int http_file_download_init(void);
+
+/**
+ * @brief 反初始化HTTP文件下载服务
+ */
+void http_file_download_deinit(void);
+
+/**
+ * @brief 开始HTTP文件下载(阻塞接口)
+ * @param url 要下载的文件URL
+ * @param file_path 保存文件的路径,NULL或空字符串表示不保存文件(仅接收数据)
+ * @param timeout_ms 超时时间(毫秒),0表示使用默认值(300秒)
+ * @return 0成功,-1失败
+ *
+ * 此函数会:
+ * 1. 打开文件(如果提供了文件路径)
+ * 2. 通过UDP发送下载指令到CAT1(携带URL地址)
+ * 3. 使用信号量阻塞等待下载完成
+ * 4. 下载完成后关闭文件并返回
+ */
+int http_file_download_start(const char *url, const char *file_path, uint32_t timeout_ms);
+
+/**
+ * @brief 通知下载完成(由外部调用,例如从CAT1收到完成消息时)
+ * @param success 是否成功(1=成功,0=失败)
+ */
+void http_file_download_notify_complete(int success);
+
+/**
+ * @brief 获取下载统计信息
+ * @param total_bytes 输出参数:总接收字节数
+ * @param total_packets 输出参数:总接收包数
+ * @return 0成功,-1失败
+ */
+int http_file_download_get_stats(uint32_t *total_bytes, uint32_t *total_packets);
+
+/**
+ * @brief 获取当前下载状态
+ * @return 当前下载状态
+ */
+http_download_state_t http_file_download_get_state(void);
+
+/* ========== 以下为旧接口,保留兼容性 ========== */
+
/**
* Download file from URL via HTTP download server
*
* @param save_path file path to save (NULL or empty to discard data)
* @return ERRCODE_SUCC on success, ERRCODE_FAIL on failure
*/
-errcode_t http_download_client_download_async(const char *url, const char *save_path);
+// errcode_t http_download_client_download_async(const char *url, const char *save_path);
/**
* Check if download task is running
}
/**
- * @brief AT^HTTPDLCLIENT - HTTP文件下载客户端测试接口
- * 格式: AT^HTTPDLCLIENT=url,save_path
- * 参数: url - 要下载的文件URL
- * save_path - 保存文件的路径(可选,为空或"null"则不保存文件)
- * 示例: AT^HTTPDLCLIENT=https://example.com/file.zip,/user/file.zip
- * AT^HTTPDLCLIENT=https://example.com/file.zip,null
+ * @brief AT^HTTPDLINIT - 初始化HTTP文件下载服务(通过SPI传输)
+ * 格式: AT^HTTPDLINIT
+ * 无参数
*/
-static uint32_t at_http_download_client_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+static uint32_t at_http_download_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
{
+ UNUSED(para);
+ UNUSED(paraLen);
UNUSED(strCommonCmd);
+
+ cat1_print_info("=== AT^HTTPDLINIT: Initializing HTTP file download service (SPI) ===\n");
+
+ int ret = http_file_download_init();
+ if (ret == 0) {
+ cat1_print_info("AT^HTTPDLINIT: HTTP file download service initialized successfully\n");
+ cat1_print_info(" - SPI service initialized\n");
+ cat1_print_info(" - Data callback registered\n");
+ return ERRCODE_SUCC;
+ } else {
+ cat1_print_err("AT^HTTPDLINIT: Failed to initialize HTTP file download service\n");
+ return ERRCODE_FAIL;
+ }
+}
+/**
+ * @brief AT^HTTPDLDEINIT - 反初始化HTTP文件下载服务
+ * 格式: AT^HTTPDLDEINIT
+ * 无参数
+ */
+static uint32_t at_http_download_deinit(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ cat1_print_info("=== AT^HTTPDLDEINIT: Deinitializing HTTP file download service ===\n");
+
+ http_file_download_deinit();
+
+ cat1_print_info("AT^HTTPDLDEINIT: HTTP file download service deinitialized successfully\n");
+ cat1_print_info(" - SPI service deinitialized\n");
+ cat1_print_info(" - Resources cleaned up\n");
+
+ return ERRCODE_SUCC;
+}
+
+/**
+ * @brief AT^HTTPDLSTART - 开始HTTP文件下载(通过SPI传输)
+ * 格式: AT^HTTPDLSTART=url,file_path[,timeout_ms]
+ * 参数: url - 要下载的文件URL(必需)
+ * file_path - 保存文件的路径(必需),"null"表示不保存文件
+ * timeout_ms - 超时时间(毫秒),可选,默认300000(300秒)
+ * 示例: AT^HTTPDLSTART=https://example.com/file.zip,/user/file.zip
+ * AT^HTTPDLSTART=https://example.com/file.zip,/user/file.zip,600000
+ * AT^HTTPDLSTART=https://example.com/file.zip,null
+ */
+static uint32_t at_http_download_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(strCommonCmd);
+
uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
uint32_t argc = 0;
-
+
int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
if (ret != CUSTOM_UTILS_SUCCESS) {
- cat1_print_err("HTTPDLCLIENT ATCMD Convert Command Failed\n");
+ cat1_print_err("HTTPDLSTART ATCMD Convert Command Failed\n");
return ERRCODE_FAIL;
}
-
- if (argc < 1 || argc > 2) {
- cat1_print_err("HTTPDLCLIENT ATCMD Set Parameter Failed argc %d\nshould input:\nAT^HTTPDLCLIENT=url[,save_path]\n", argc);
- cat1_print_info("Example: AT^HTTPDLCLIENT=https://example.com/file.zip,/user/file.zip\n");
- cat1_print_info("Example: AT^HTTPDLCLIENT=https://example.com/file.zip,null\n");
+
+ if (argc < 2 || argc > 3) {
+ cat1_print_err("HTTPDLSTART ATCMD Set Parameter Failed argc %d\nshould input:\nAT^HTTPDLSTART=url,file_path[,timeout_ms]\n", argc);
+ cat1_print_info("Example: AT^HTTPDLSTART=https://example.com/file.zip,/user/file.zip\n");
+ cat1_print_info("Example: AT^HTTPDLSTART=https://example.com/file.zip,/user/file.zip,600000\n");
+ cat1_print_info("Example: AT^HTTPDLSTART=https://example.com/file.zip,null\n");
return ERRCODE_FAIL;
}
-
+
const char *url = (const char*)argv[0];
- const char *save_path = NULL;
-
- if (argc >= 2) {
- // 如果第二个参数不为"null"或空,则使用它作为保存路径
- char *path_arg = (char*)argv[1];
- if (strlen(path_arg) > 0) {
- // 检查是否为 "null" (不区分大小写)
- int is_null = (strcmp(path_arg, "null") == 0 ||
- strcmp(path_arg, "NULL") == 0 ||
- strcmp(path_arg, "Null") == 0);
- if (!is_null) {
- save_path = (const char*)path_arg;
+ const char *file_path = (const char*)argv[1];
+ uint32_t timeout_ms = 0; // 0表示使用默认值(300秒)
+
+ // 检查file_path是否为"null"(不保存文件)
+ const char *actual_file_path = NULL;
+ if (strcmp(file_path, "null") != 0 && strcmp(file_path, "NULL") != 0 && strcmp(file_path, "Null") != 0) {
+ actual_file_path = file_path;
+ }
+
+ if (argc >= 3) {
+ timeout_ms = (uint32_t)atoi((const char*)argv[2]);
+ if (timeout_ms == 0) {
+ timeout_ms = 300000; // 默认300秒
+ }
+ }
+
+ cat1_print_info("=== AT^HTTPDLSTART: Starting HTTP file download ===\n");
+ cat1_print_info("URL: %s\n", url);
+ if (actual_file_path) {
+ cat1_print_info("File path: %s\n", actual_file_path);
+ } else {
+ cat1_print_info("File path: (not saving, discard mode)\n");
+ }
+ cat1_print_info("Timeout: %u ms (%u seconds)\n", timeout_ms, timeout_ms / 1000);
+
+ // 开始下载(阻塞接口)
+ int result = http_file_download_start("https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4", actual_file_path, timeout_ms);
+
+ if (result == 0) {
+ // 获取统计信息
+ uint32_t total_bytes, total_packets;
+ if (http_file_download_get_stats(&total_bytes, &total_packets) == 0) {
+ cat1_print_info("AT^HTTPDLSTART: Download completed successfully\n");
+ cat1_print_info(" - Total bytes received: %u\n", total_bytes);
+ cat1_print_info(" - Total packets received: %u\n", total_packets);
+ if (total_bytes > 0) {
+ cat1_print_info(" - Average packet size: %u bytes\n", total_bytes / (total_packets > 0 ? total_packets : 1));
}
}
+ return ERRCODE_SUCC;
+ } else {
+ cat1_print_err("AT^HTTPDLSTART: Download failed or timeout\n");
+ return ERRCODE_FAIL;
}
+}
- cat1_print_info("HTTPDLCLIENT: URL=%s\n", url);
- if (save_path) {
- cat1_print_info("HTTPDLCLIENT: Save path=%s\n", save_path);
+/**
+ * @brief AT^HTTPDLSTATUS - 查询HTTP文件下载状态
+ * 格式: AT^HTTPDLSTATUS
+ * 无参数
+ */
+static uint32_t at_http_download_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ cat1_print_info("=== AT^HTTPDLSTATUS: HTTP file download status ===\n");
+
+ http_download_state_t state = http_file_download_get_state();
+ const char *state_str[] = {
+ "IDLE",
+ "DOWNLOADING",
+ "COMPLETED",
+ "ERROR"
+ };
+
+ if (state >= 0 && state < 4) {
+ cat1_print_info("State: %s\n", state_str[state]);
+ } else {
+ cat1_print_info("State: UNKNOWN(%d)\n", state);
+ }
+
+ // 获取统计信息
+ uint32_t total_bytes, total_packets;
+ if (http_file_download_get_stats(&total_bytes, &total_packets) == 0) {
+ cat1_print_info("Total bytes received: %u\n", total_bytes);
+ cat1_print_info("Total packets received: %u\n", total_packets);
+ if (total_bytes > 0 && total_packets > 0) {
+ cat1_print_info("Average packet size: %u bytes\n", total_bytes / total_packets);
+ }
+ }
+
+ return ERRCODE_SUCC;
+}
+
+/**
+ * @brief AT^HTTPDLSTATS - 查询HTTP文件下载统计信息
+ * 格式: AT^HTTPDLSTATS
+ * 无参数
+ */
+static uint32_t at_http_download_stats(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ cat1_print_info("=== AT^HTTPDLSTATS: HTTP file download statistics ===\n");
+
+ uint32_t total_bytes, total_packets;
+ int ret = http_file_download_get_stats(&total_bytes, &total_packets);
+
+ if (ret == 0) {
+ cat1_print_info("Total bytes received: %u (%.2f KB, %.2f MB)\n",
+ total_bytes,
+ total_bytes / 1024.0f,
+ total_bytes / 1024.0f / 1024.0f);
+ cat1_print_info("Total packets received: %u\n", total_packets);
+
+ if (total_packets > 0) {
+ cat1_print_info("Average packet size: %u bytes\n", total_bytes / total_packets);
+ cat1_print_info("Largest packet size: %u bytes (assuming max 1020 bytes per packet)\n",
+ (total_bytes / total_packets > 1020) ? 1020 : (total_bytes / total_packets));
+ }
} else {
- cat1_print_info("HTTPDLCLIENT: No save path (discard mode)\n");
+ cat1_print_err("AT^HTTPDLSTATS: Failed to get statistics\n");
+ return ERRCODE_FAIL;
}
+
+ return ERRCODE_SUCC;
+}
- // 调用HTTP文件下载客户端
- int result = http_download_client_download_async("https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4", save_path);
+/**
+ * @brief AT^HTTPDLCLIENT - HTTP文件下载客户端测试接口(保留兼容性)
+ * 格式: AT^HTTPDLCLIENT=url
+ * 参数: url - 要下载的文件URL
+ * 示例: AT^HTTPDLCLIENT=https://example.com/file.zip
+ *
+ * 注意:此命令已改为使用SPI传输接口
+ */
+static uint32_t at_http_download_client_test(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(strCommonCmd);
+
+ uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 };
+ uint32_t argc = 0;
+
+ int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
+ if (ret != CUSTOM_UTILS_SUCCESS) {
+ cat1_print_err("HTTPDLCLIENT ATCMD Convert Command Failed\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (argc < 1) {
+ cat1_print_err("HTTPDLCLIENT ATCMD Set Parameter Failed argc %d\nshould input:\nAT^HTTPDLCLIENT=url\n", argc);
+ cat1_print_info("Example: AT^HTTPDLCLIENT=https://example.com/file.zip\n");
+ cat1_print_info("Note: This command uses SPI transmission interface\n");
+ return ERRCODE_FAIL;
+ }
+
+ const char *url = (const char*)argv[0];
+
+ cat1_print_info("HTTPDLCLIENT: URL=%s\n", url);
+ cat1_print_info("HTTPDLCLIENT: Using SPI transmission interface\n");
+ cat1_print_info("HTTPDLCLIENT: No file path provided, data will not be saved\n");
+
+ // 使用新的SPI传输接口(不保存文件)
+ int result = http_file_download_start(url, NULL, 0); // 使用默认超时时间,不保存文件
if (result == 0) {
- cat1_print_info("HTTPDLCLIENT: Download completed successfully\n");
+ uint32_t total_bytes, total_packets;
+ if (http_file_download_get_stats(&total_bytes, &total_packets) == 0) {
+ cat1_print_info("HTTPDLCLIENT: Download completed successfully\n");
+ cat1_print_info("HTTPDLCLIENT: Total bytes: %u, Packets: %u\n", total_bytes, total_packets);
+ }
return ERRCODE_SUCC;
} else {
cat1_print_err("HTTPDLCLIENT: Download failed\n");
{ (char *)"AT^PPPTCPSTOP", at_4g_tcp_test_stop, (char *)" " },
{ (char *)"AT^PPPTCPSTATS", at_4g_tcp_test_stats, (char *)" " },
{ (char *)"AT^CAT1TCPINIT", at_cat1_TCP_init, (char *)" " },
- { (char *)"AT^HTTPDLCLIENT", at_http_download_client_test, (char *)"url[,save_path]" },
+ /* HTTP文件下载服务(通过SPI传输) */
+ { (char *)"AT^HTTPDLINIT", at_http_download_init, (char *)" " },
+ { (char *)"AT^HTTPDLDEINIT", at_http_download_deinit, (char *)" " },
+ { (char *)"AT^HTTPDLSTART", at_http_download_start, (char *)"url,file_path[,timeout_ms]" },
+ { (char *)"AT^HTTPDLSTATUS", at_http_download_status, (char *)" " },
+ { (char *)"AT^HTTPDLSTATS", at_http_download_stats, (char *)" " },
+ { (char *)"AT^HTTPDLCLIENT", at_http_download_client_test, (char *)"url" },
};
static uint32_t pkg_recv_cnt = 0; // 新增:接收包计数
static uint32_t total_byte_error_cnt = 0;
+/* SPI数据接收回调函数指针 */
+static custom_spi_data_callback_t g_spi_data_callback = NULL;
+
+/**
+ * @brief 从4字节小端序数据中解析32位无符号整数
+ * @param data 4字节数据指针
+ * @return 解析得到的32位无符号整数
+ */
+static uint32_t unpack_uint32_le(const uint8_t *data)
+{
+ if (data == NULL) {
+ return 0;
+ }
+ return (uint32_t)data[0] |
+ ((uint32_t)data[1] << 8) |
+ ((uint32_t)data[2] << 16) |
+ ((uint32_t)data[3] << 24);
+}
+
void custom_spi_read(void)
{
ret = uapi_spi_slave_read(SPI_BUS_1, &spi_data, 50);
- printf("total_cnt:%d,spi_data.rx_bytes:%d\n",total_cnt,spi_data.rx_bytes);
-
- if(ret == 0)
+ if(ret == 0 && spi_data.rx_bytes == SPI_PACKET_TOTAL_SIZE)
{
- total_cnt += spi_data.rx_bytes;
- printf("ret:%x,total_cnt:%d,spi_data.rx_bytes:%d\n",ret,total_cnt,spi_data.rx_bytes);
- // printf("spi_data.rx_buff[0]:0x%x,total_cnt:%d\n",spi_data.rx_buff[0],total_cnt);
- // printf("spi_data.rx_bytes : %d, spi_data.rx_data[0]:0x%02X,spi_data.rx_data[1023]:0x%02X\n",
- // spi_data.rx_bytes, current_seq, spi_data.rx_buff[1023]);
+ // 解析包头:前4字节为数据长度(小端序)
+ uint32_t data_len = unpack_uint32_le(g_rx_buffer);
- // 包计数自增(从第一个包开始计数)
+ // 验证数据长度合理性(应该在0到SPI_PACKET_DATA_SIZE之间)
+ if (data_len > SPI_PACKET_DATA_SIZE) {
+ printf("SPI packet error: invalid data length %u (max: %u)\n",
+ data_len, SPI_PACKET_DATA_SIZE);
+ total_byte_error_cnt++;
+ return;
+ }
+
+ // 包计数自增
pkg_recv_cnt++;
-
- // --------------- 逐字节严格递增判断(无跳过,首个包就校验)---------------
- // uint8_t byte_error_cnt = 0; // 单包内字节错误计数
- // uint8_t prev_byte = spi_data.rx_buff[0]; // 初始化为第一个字节
- // // printf("head\n");
- // // 遍历1024字节(从第2个字节开始校验)
- // for (int i = 1; i < 1024; i++)
- // {
- // // if(i < 20)
- // // printf("(0x%02X)",spi_data.rx_buff[i]);
- // // if(i == 20)
- // // printf("\n");
- // uint8_t curr_byte = spi_data.rx_buff[i];
- // // 8位字节递增规则:0xFF后应为0x00,其余为前一字节+1
- // uint8_t expected_byte = (prev_byte == 0xFF) ? 0x00 : (prev_byte + 1);
-
- // // 校验当前字节是否等于期望的递增字节(无跳过)
- // if (curr_byte != expected_byte)
- // {
- // byte_error_cnt++;
- // // 打印错误详情(调试用,正式环境可注释)
- // printf("字节[%d]不连续:当前0x%02X, 期望0x%02X(前一字节0x%02X)\n",
- // i, curr_byte, expected_byte, prev_byte);
- // }
-
- // // 更新前一字节,用于下一轮校验
- // prev_byte = curr_byte;
- // }
- // printf("该包无错误\n");
- // // 单包字节校验结果汇总(首个包就统计)
- // if (byte_error_cnt > 0)
- // {
- // printf("【包%d】字节递增错误数:%d | 累计字节错误数:%d\n",
- // pkg_recv_cnt, byte_error_cnt, total_byte_error_cnt += byte_error_cnt);
- // }
+
+ // 统计总接收字节数(仅统计实际数据长度,不包括包头)
+ total_cnt += data_len;
+
+ printf("SPI packet #%u: data_len=%u, total_cnt=%u, rx_bytes=%u\n",
+ pkg_recv_cnt, data_len, total_cnt, spi_data.rx_bytes);
+
+ // 调用注册的回调函数处理数据(传递数据部分,不包含包头)
+ if (g_spi_data_callback != NULL) {
+ const uint8_t *data_ptr = g_rx_buffer + SPI_PACKET_HEADER_SIZE;
+ int callback_ret = g_spi_data_callback(data_ptr, data_len, pkg_recv_cnt);
+ if (callback_ret != 0) {
+ printf("SPI data callback returned error: %d\n", callback_ret);
+ }
+ } else {
+ // 如果没有注册回调函数,使用默认处理(打印调试信息)
+ printf("No SPI data callback registered, using default handling\n");
+ }
}
else
{
- printf("error ret:%x,spi_data.rx_bytes:%d\n",ret,spi_data.rx_bytes);
+ printf("SPI read error: ret=0x%x, rx_bytes=%u (expected: %u)\n",
+ ret, spi_data.rx_bytes, SPI_PACKET_TOTAL_SIZE);
+ total_byte_error_cnt++;
}
- // else
- // {
- // // 新增:包计数自增(失败/长度不符的包也计数,若仅统计成功包可移到上面if内)
- // pkg_recv_cnt++;
-
- // uapi_gpio_set_dir(S_AGPIO_L13, GPIO_DIRECTION_OUTPUT);
- // uapi_gpio_set_val(S_AGPIO_L13, GPIO_LEVEL_HIGH);
- // uapi_tcxo_delay_ms(2);
- // uapi_gpio_set_val(S_AGPIO_L13, GPIO_LEVEL_LOW);
- // uapi_tcxo_delay_ms(5);
- // uint8_t data = expected_seq;
- // spi_data.tx_buff = &data;
- // spi_data.tx_bytes = 1;
- // ret = uapi_spi_slave_write(SPI_BUS_1, &spi_data, 50);
- // printf("uapi_spi_slave_write ret : 0x%02X \n", ret);
- // // printf("接收失败/长度不符,当前接收包数:%d\n", pkg_recv_cnt);
- // }
-
-
}
void custom_spi_service_task(void *arg)
uapi_gpio_set_val(S_AGPIO_L13, GPIO_LEVEL_HIGH);
uapi_tcxo_delay_ms(2); /* delay 10 ms */
uapi_gpio_set_val(S_AGPIO_L13, GPIO_LEVEL_LOW);
+}
+
+/**
+ * @brief 注册SPI数据接收回调函数
+ * @param callback 回调函数指针,NULL表示取消注册
+ */
+void custom_spi_register_data_callback(custom_spi_data_callback_t callback)
+{
+ g_spi_data_callback = callback;
+ if (callback != NULL) {
+ printf("SPI data callback registered successfully\n");
+ } else {
+ printf("SPI data callback unregistered\n");
+ }
}
\ No newline at end of file
#include "errcode.h"
#include "platform_core.h"
+/* SPI数据包格式定义 */
+#define SPI_PACKET_HEADER_SIZE 4 /* 包头大小:4字节(数据长度,小端序) */
+#define SPI_PACKET_TOTAL_SIZE 1024 /* 总包大小:1024字节 */
+#define SPI_PACKET_DATA_SIZE (SPI_PACKET_TOTAL_SIZE - SPI_PACKET_HEADER_SIZE) /* 数据部分大小:1020字节 */
+
+/* SPI数据接收回调函数类型 */
+/**
+ * @brief SPI数据接收回调函数
+ * @param data 接收到的数据指针(不包含包头,仅数据部分)
+ * @param data_len 数据长度(从包头解析得到)
+ * @param packet_num 包序号(从1开始递增)
+ * @return 0表示成功,非0表示失败
+ */
+typedef int (*custom_spi_data_callback_t)(const uint8_t *data, uint32_t data_len, uint32_t packet_num);
+
void custom_spi_init(void);
void custom_spi_deinit(void);
void custom_spi_read(void);
void custom_spi_rx_callback(pin_t pin, uintptr_t param);
+/**
+ * @brief 注册SPI数据接收回调函数
+ * @param callback 回调函数指针,NULL表示取消注册
+ */
+void custom_spi_register_data_callback(custom_spi_data_callback_t callback);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */