增加http下载通过spi传输接口
authorhcj <1224667075@qq.com>
Mon, 8 Dec 2025 07:24:20 +0000 (15:24 +0800)
committerhcj <1224667075@qq.com>
Mon, 8 Dec 2025 07:24:20 +0000 (15:24 +0800)
cat1modem/src/LeiNiao/cat1Service.py
cat1modem/src/LeiNiao/httpfiledownload.py
code/sdk/zhihui/4g/cat1Service/Service/cat1_server.c
code/sdk/zhihui/4g/cat1Service/Service/http_file_download_client.c
code/sdk/zhihui/4g/cat1Service/include/http_file_download_client.h
code/sdk/zhihui/4gTest/cat1_at_cmd.c
code/sdk/zhihui/utils/custom_spi.c
code/sdk/zhihui/utils/custom_spi.h

index 0a2bb9b..0490b4b 100644 (file)
@@ -56,6 +56,7 @@ from usr.EventMesh import eventMesh
 
 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")
@@ -377,6 +378,8 @@ class Cat1Service:
         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
index 9a59ef6..f7702c1 100644 (file)
@@ -67,7 +67,10 @@ gpioWakeBT.set_dir(Pin.OUT)
 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):
@@ -91,6 +94,19 @@ 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 文件下载类
@@ -470,20 +486,25 @@ class HttpFileDownloadTcpServer:
                         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'):
@@ -496,36 +517,40 @@ class HttpFileDownloadTcpServer:
                                 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))
@@ -551,6 +576,7 @@ class HttpFileDownloadTcpServer:
                     
                     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))
@@ -560,8 +586,8 @@ class HttpFileDownloadTcpServer:
                     # 最后一块进度显示
                     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
@@ -571,11 +597,14 @@ class HttpFileDownloadTcpServer:
             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("================================")
             
@@ -752,20 +781,25 @@ class HttpFileDownloadSpiTest:
                         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)
@@ -780,17 +814,17 @@ class HttpFileDownloadSpiTest:
                                 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:
@@ -798,20 +832,24 @@ class HttpFileDownloadSpiTest:
                         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))
@@ -839,6 +877,7 @@ class HttpFileDownloadSpiTest:
                     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))
@@ -848,8 +887,8 @@ class HttpFileDownloadSpiTest:
                     # 最后一块进度显示
                     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
@@ -864,10 +903,13 @@ class HttpFileDownloadSpiTest:
             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))
@@ -903,7 +945,7 @@ def start_spi_speed_test():
         test.close()
 
 # 自动启动SPI传输速度测试(替代原来的TCP服务器)
-start_spi_speed_test()
+# start_spi_speed_test()
 # 示例用法
 # if __name__ == '__main__':
 #     # 方式1: 使用类下载并保存文件
@@ -964,10 +1006,167 @@ start_spi_speed_test()
 # 
 # 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
index 5052461..4e9a59e 100644 (file)
@@ -118,6 +118,8 @@ enum UDP_COMMAND_HEAD {
 
     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,//
 };
index 502d066..30b5b6c 100644 (file)
@@ -1,9 +1,9 @@
 /* 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>
@@ -30,6 +30,10 @@ static inline u16_t htons(u16_t n)
 #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"
@@ -46,483 +50,332 @@ static inline u16_t htons(u16_t n)
 #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 */
-
index 59a9ef1..6b720ce 100644 (file)
@@ -1,6 +1,7 @@
 /* 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
@@ -8,11 +9,68 @@
 
 #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
  * 
@@ -49,7 +107,7 @@ void http_download_client_task(void *pvParameters);
  * @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
index f7f949d..14dfa74 100644 (file)
@@ -411,62 +411,249 @@ static uint32_t custom_start_ota_download(uint8_t *para, uint32_t paraLen, char
 }
 
 /**
- * @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");
@@ -1108,7 +1295,13 @@ static const at_cmd_table_t g_cat1_cmd[] = {
     { (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" },
     
 };
 
index f8d64b1..093057f 100644 (file)
@@ -32,6 +32,25 @@ static uint8_t g_rx_buffer[1024] = {0};
 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)
 {
@@ -44,77 +63,46 @@ 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)
@@ -312,4 +300,18 @@ void custom_spi_start(void)
     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
index 78bda96..ff83e45 100644 (file)
@@ -25,11 +25,32 @@ extern "C" {
 #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 */