--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+HTTP 文件下载模块
+支持下载指定 URL 的文件到本地路径
+"""
+
+import log
+
+# 设置日志输出级别
+log.basicConfig(level=log.INFO)
+clog = log.getLogger("httpdownload")
+
+import modem
+import request
+import utime
+import osTimer
+import ql_fs
+from machine import RTC
+from misc import Power
+import uos
+import ujson
+import urandom
+from usr.EventMesh import eventMesh
+import usocket
+import _thread
+
+REQUESTS_AVAILABLE = True
+
+
+class HttpFileDownloader:
+ """
+ HTTP 文件下载类
+ 支持从指定 URL 下载文件并保存到本地
+ """
+
+ def __init__(self, timeout=30, verify=True, allow_redirects=True, sizeof=255):
+ """
+ 初始化下载器
+
+ Args:
+ timeout: 请求超时时间(秒),默认 30 秒
+ verify: 是否验证 SSL 证书,默认 True
+ allow_redirects: 是否允许重定向,默认 True
+ sizeof: 读取缓冲区的数据块大小,默认 255 字节,建议 255-4096
+ """
+ self.timeout = timeout
+ self.verify = verify
+ self.allow_redirects = allow_redirects
+ self.sizeof = sizeof
+
+ def download(self, url, save_path=None, chunk_size=None, show_progress=False):
+ """
+ 下载文件从指定 URL 到本地路径
+
+ Args:
+ url: 要下载的文件 URL
+ save_path: 保存文件的本地路径,为 None 或空字符串时不保存文件(仅下载并丢弃数据)
+ chunk_size: 下载时的块大小(字节),默认使用 self.sizeof
+ show_progress: 是否显示下载进度,默认 False
+
+ Returns:
+ tuple: (success: bool, download_time: float, file_size: int)
+ success: 下载是否成功
+ download_time: 下载耗时(秒)
+ file_size: 下载的文件大小(字节),失败时为 -1
+ """
+ if not REQUESTS_AVAILABLE:
+ clog.error("Error: request module is not available")
+ return False, 0.0, -1
+
+ if not url:
+ clog.error("Error: URL cannot be empty")
+ return False, 0.0, -1
+
+ # 判断是否需要保存文件
+ save_file = False
+ if save_path != None:
+ save_file = save_path and save_path.strip()
+
+ # 使用指定的 chunk_size 或默认的 sizeof
+ read_size = chunk_size if chunk_size else self.sizeof
+
+ try:
+ if save_file:
+ clog.info("Starting download: {}".format(url))
+ clog.info("Save to: {}".format(save_path))
+ else:
+ clog.info("Starting download (discard mode): {}".format(url))
+
+ # 记录开始时间
+ start_time = utime.time()
+
+ # 发送 GET 请求
+ # request.get(url, data=None, **kwargs)
+ # sizeof: 读取缓冲区的数据块大小
+ response = request.get(
+ url,
+ sizeof=read_size,
+ decode=False # 不进行 UTF-8 解码,直接获取 bytes
+ )
+
+ # 检查响应状态码
+ if response.status_code != 200:
+ clog.error("Error: HTTP error {}: {}".format(response.status_code, url))
+ return False, 0.0, -1
+
+ # 获取文件总大小(如果服务器提供了 Content-Length)
+ total_size = 0
+ if 'Content-Length' in response.headers:
+ try:
+ total_size = int(response.headers['Content-Length'])
+ except (ValueError, TypeError):
+ total_size = 0
+
+ # 下载数据
+ downloaded_size = 0
+
+ if save_file:
+ # 保存到文件
+ with open(save_path, 'wb') as f:
+ # response.content 是 bytes 类型生成器
+ for chunk in response.content:
+ if chunk: # 过滤掉空块
+ f.write(chunk)
+ downloaded_size += len(chunk)
+
+ # 显示进度
+ if show_progress and total_size > 0:
+ progress = (downloaded_size / total_size) * 100
+ clog.info("Progress: {:.2f}% ({}/{}) bytes".format(progress, downloaded_size, total_size))
+ else:
+ # 丢弃数据(不保存到文件)
+ # response.content 是 bytes 类型生成器
+ for chunk in response.content:
+ if chunk: # 过滤掉空块
+ downloaded_size += len(chunk)
+
+ # 显示进度
+ if show_progress and total_size > 0:
+ progress = (downloaded_size / total_size) * 100
+ clog.info("Progress: {:.2f}% ({}/{}) bytes".format(progress, downloaded_size, total_size))
+
+ # 记录结束时间并计算耗时
+ end_time = utime.time()
+ download_time = end_time - start_time
+
+ file_size = downloaded_size
+
+ if save_file:
+ clog.info("Download completed: {} ({} bytes, {:.3f}s)".format(save_path, file_size, download_time))
+ else:
+ clog.info("Download completed (discarded): {} bytes downloaded in {:.3f}s".format(file_size, download_time))
+
+ return True, download_time, file_size
+
+ except Exception as e:
+ clog.error("Error: Download failed: {}".format(e))
+ return False, 0.0, -1
+
+ def download_simple(self, url, save_path=None):
+ """
+ 简单的下载方法(兼容原有实现)
+
+ Args:
+ url: 要下载的文件 URL
+ save_path: 保存文件的本地路径,为 None 或空字符串时不保存文件
+
+ Returns:
+ tuple: (success: bool, download_time: float, file_size: int)
+ """
+ return self.download(url, save_path, show_progress=False)
+
+ def get_file_size(self, url):
+ """
+ 获取远程文件大小(不下载文件)
+
+ Args:
+ url: 文件的 URL
+
+ Returns:
+ int: 文件大小(字节),失败返回 -1
+ """
+ if not REQUESTS_AVAILABLE:
+ return -1
+
+ try:
+ # 使用 GET 请求,但只读取 headers
+ response = request.get(url, sizeof=1, decode=False)
+
+ if response.status_code != 200:
+ clog.error("Error: HTTP error {} when getting file size".format(response.status_code))
+ return -1
+
+ # 从响应头获取 Content-Length
+ if 'Content-Length' in response.headers:
+ try:
+ return int(response.headers['Content-Length'])
+ except (ValueError, TypeError):
+ return -1
+
+ return -1
+
+ except Exception as e:
+ clog.error("Error getting file size: {}".format(e))
+ return -1
+
+ def close(self):
+ """关闭下载器会话(request 模块不需要关闭会话)"""
+ pass
+
+ def __enter__(self):
+ """上下文管理器入口"""
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """上下文管理器出口"""
+ self.close()
+
+
+class HttpFileDownloadTcpServer:
+ """
+ HTTP 文件下载 TCP 服务器
+ 监听 TCP 连接,接收 URL 请求,下载文件并通过 socket 发送数据
+ """
+
+ def __init__(self, host='172.16.1.2', port=5002, timeout=30, sizeof=255):
+ """
+ 初始化 TCP 服务器
+
+ Args:
+ host: 监听地址,默认 '172.16.1.2'
+ port: 监听端口,默认 5002
+ timeout: 下载超时时间(秒),默认 30 秒
+ sizeof: 读取缓冲区的数据块大小,默认 255 字节
+ """
+ self.host = host
+ self.port = port
+ self.timeout = timeout
+ self.sizeof = sizeof
+ self.sock = None
+ self.running = False
+ self.downloader = HttpFileDownloader(timeout=timeout, sizeof=sizeof)
+
+ def __socket_init(self):
+ """初始化 TCP socket"""
+ try:
+ sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
+ sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
+ bind_addr = (self.host, self.port)
+ sock.bind(bind_addr)
+ sock.listen(10)
+ clog.info("TCP server listening on {}:{}".format(self.host, self.port))
+ return sock
+ except Exception as e:
+ clog.error("Failed to create TCP socket: {}".format(e))
+ return None
+
+ def __handle_client(self, conn, addr):
+ """
+ 处理客户端连接
+
+ Args:
+ conn: 客户端连接对象
+ addr: 客户端地址 (ip, port)
+ """
+ try:
+ clog.info("Client connected: {}:{}".format(addr[0], addr[1]))
+
+ # 接收 URL 请求(最大 512 字节)
+ data = conn.recv(512)
+ if not data:
+ clog.info("Client disconnected without sending data")
+ conn.close()
+ return
+
+ # 解析 URL(可能是字符串或 JSON)
+ try:
+ url_str = data.decode('utf-8').strip()
+ # 尝试解析为 JSON
+ try:
+ url_data = ujson.loads(url_str)
+ url = url_data.get('url', url_str)
+ except:
+ url = url_str
+ except:
+ url = str(data)
+
+ if not url:
+ clog.error("Invalid URL received")
+ conn.send(b'ERROR: Invalid URL')
+ conn.close()
+ return
+
+ clog.info("Received download request for URL: {}".format(url))
+
+ # 发送确认消息
+ conn.send(b'OK: Starting download...\n')
+
+ # 下载文件并通过 socket 发送
+ success, download_time, file_size = self.__download_and_send(conn, url)
+
+ if success:
+ clog.info("File sent successfully: {} bytes in {:.3f}s".format(file_size, download_time))
+ conn.send(b'\nOK: Download completed\n')
+ else:
+ clog.error("Download failed")
+ conn.send(b'\nERROR: Download failed\n')
+
+ except Exception as e:
+ clog.error("Error handling client: {}".format(e))
+ finally:
+ try:
+ conn.close()
+ except:
+ pass
+ clog.info("Client connection closed: {}:{}".format(addr[0], addr[1]))
+
+ def __download_and_send(self, conn, url):
+ """
+ 下载文件并通过 socket 发送数据
+
+ Args:
+ conn: 客户端连接对象
+ url: 要下载的文件 URL
+
+ Returns:
+ tuple: (success: bool, download_time: float, file_size: int)
+ """
+ try:
+ # 记录开始时间
+ start_time = utime.time()
+
+ # 发送 GET 请求
+ response = request.get(
+ url,
+ sizeof=self.sizeof,
+ decode=False # 不进行 UTF-8 解码,直接获取 bytes
+ )
+
+ # 检查响应状态码
+ if response.status_code != 200:
+ clog.error("Error: HTTP error {}: {}".format(response.status_code, url))
+ return False, 0.0, -1
+
+ # 获取文件总大小(如果服务器提供了 Content-Length)
+ total_size = 0
+ if 'Content-Length' in response.headers:
+ try:
+ total_size = int(response.headers['Content-Length'])
+ except (ValueError, TypeError):
+ total_size = 0
+
+ # 发送文件大小信息(可选)
+ if total_size > 0:
+ size_info = "Content-Length: {}\n".format(total_size).encode('utf-8')
+ conn.send(size_info)
+
+ # 下载数据并通过 socket 发送
+ downloaded_size = 0
+
+ # response.content 是 bytes 类型生成器
+ for chunk in response.content:
+ if chunk: # 过滤掉空块
+ try:
+ conn.send(chunk)
+ downloaded_size += len(chunk)
+
+ # 显示进度(每发送一定数据量后打印一次)
+ if total_size > 0:
+ progress = (downloaded_size / total_size) * 100
+ # 每发送 64KB 打印一次进度
+ if downloaded_size % (64 * 1024) < self.sizeof:
+ clog.info("Progress: {:.2f}% ({}/{}) bytes".format(
+ progress, downloaded_size, total_size))
+ except Exception as e:
+ clog.error("Error sending data: {}".format(e))
+ return False, 0.0, -1
+
+ # 记录结束时间并计算耗时
+ end_time = utime.time()
+ download_time = end_time - start_time
+
+ clog.info("Download and send completed: {} bytes in {:.3f}s".format(
+ downloaded_size, download_time))
+
+ return True, download_time, downloaded_size
+
+ except Exception as e:
+ clog.error("Error downloading and sending file: {}".format(e))
+ return False, 0.0, -1
+
+ def __server_loop(self):
+ """服务器主循环"""
+ while self.running:
+ try:
+ if self.sock is None:
+ utime.sleep_ms(2000)
+ continue
+
+ # 接受客户端连接
+ cli_conn, cli_ip_addr, cli_port = self.sock.accept()
+ clog.info("Accepted connection from {}:{}".format(cli_ip_addr, cli_port))
+
+ # 在新线程中处理客户端连接
+ _thread.start_new_thread(self.__handle_client, (cli_conn, (cli_ip_addr, cli_port)))
+
+ except Exception as e:
+ if self.running:
+ clog.error("Error in server loop: {}".format(e))
+ utime.sleep_ms(1000)
+
+ def start(self):
+ """启动 TCP 服务器"""
+ if self.running:
+ clog.warning("Server is already running")
+ return False
+
+ self.sock = self.__socket_init()
+ if self.sock is None:
+ return False
+
+ self.running = True
+ # 启动服务器线程
+ _thread.start_new_thread(self.__server_loop, ())
+ clog.info("HTTP file download TCP server started on {}:{}".format(self.host, self.port))
+ return True
+
+ def stop(self):
+ """停止 TCP 服务器"""
+ self.running = False
+ if self.sock:
+ try:
+ self.sock.close()
+ except:
+ pass
+ self.sock = None
+ clog.info("HTTP file download TCP server stopped")
+
+ def is_running(self):
+ """检查服务器是否在运行"""
+ return self.running
+
+
+# 保持原有功能的简单函数(向后兼容)
+
+HttpFileDownloadTcpServer().start()
+# 示例用法
+# if __name__ == '__main__':
+# # 方式1: 使用类下载并保存文件
+# downloader = HttpFileDownloader(timeout=30)
+
+# # 下载文件并显示进度
+# success, download_time, file_size = downloader.download(
+# url='http://www.baidu.com',
+# save_path='/tmp/baidu.html',
+# show_progress=True
+# )
+
+# if success:
+# print(f"Download successful! Size: {file_size} bytes, Time: {download_time:.3f}s")
+# else:
+# print("Download failed!")
+
+# downloader.close()
+
+# # 方式2: 下载但不保存文件(仅测试下载速度)
+# downloader = HttpFileDownloader(timeout=30)
+# success, download_time, file_size = downloader.download(
+# url='http://www.baidu.com',
+# save_path=None, # 不保存文件,直接丢弃数据
+# show_progress=True
+# )
+
+# if success:
+# print(f"Download test completed! Size: {file_size} bytes, Time: {download_time:.3f}s, Speed: {file_size/download_time/1024:.2f} KB/s")
+
+# downloader.close()
+
+# # 方式3: 使用上下文管理器(推荐)
+# with HttpFileDownloader() as downloader:
+# success, download_time, file_size = downloader.download(
+# url='http://www.baidu.com',
+# save_path='/tmp/baidu2.html',
+# show_progress=True
+# )
+# if success:
+# print(f"Downloaded {file_size} bytes in {download_time:.3f}s")
+
+# # 方式4: 使用简单函数(向后兼容)
+# download_file('http://www.baidu.com', '/tmp/baidu3.html')
+
+
+# 测试代码示例(直接下载)
+# downloader = HttpFileDownloader(timeout=30, sizeof=255)
+# success, download_time, file_size = downloader.download(
+# url='https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4',
+# save_path=None, # 不保存文件,直接丢弃数据
+# show_progress=True
+# )
+#
+# if success:
+# clog.info("Download test completed! Size: {} bytes, Time: {:.3f}s, Speed: {:.2f} KB/s".format(
+# file_size, download_time, file_size/download_time/1024))
+#
+# downloader.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
print("检测文件成功!!!!!!!\r\n",'cat1service')
except Exception as e:
print("检测文件失败!!!!!!!{}\r\n",'cat1service')
+ try:
+ from usr.httpfiledownload import HttpFileDownloadTcpServer
+ print("检测文件成功!!!!!!!\r\n",'httpfiledownload')
+ except Exception as e:
+ print("检测文件失败!!!!!!!{}\r\n",'httpfiledownload')
ota_flag = 1
print("检测文件成功!!!!!!!\r\n")
${CMAKE_CURRENT_SOURCE_DIR}/Service/http_client.c
${CMAKE_CURRENT_SOURCE_DIR}/Service/sntp.c
${CMAKE_CURRENT_SOURCE_DIR}/Service/cat1_server_hal.c
+${CMAKE_CURRENT_SOURCE_DIR}/Service/http_file_download_client.c
+
# ${CMAKE_CURRENT_SOURCE_DIR}/slip/uart_slip.c
)
--- /dev/null
+/* HTTP File Download Client
+ *
+ * This file implements a TCP client to connect to the HTTP file download server
+ * and receive downloaded files.
+ *
+ * Reference: cat1_tcpServer.c
+ */
+
+#include <dirent.h>
+#include "lwip/ip.h"
+#include "lwip/netif.h"
+#include "lwip/init.h"
+#include "lwip/inet_chksum.h"
+
+#include "lwip/err.h"
+#include "lwip/sockets.h"
+#include "lwip/sys.h"
+#include <lwip/netdb.h>
+#include "lwip/inet.h"
+
+/* Define htons function if not available */
+#ifndef htons
+static inline u16_t htons(u16_t n)
+{
+ return ((n & 0xFF) << 8) | ((n & 0xFF00) >> 8);
+}
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "cat1_log.h"
+#include "osal_timer.h"
+
+#define __DEFINED_struct_timeval
+#include "cat1_runtime.h"
+#include "cat1_utils.h"
+
+#include "errcode.h"
+#include "errno.h"
+#include "cmsis_os.h"
+#include "osal_msgqueue.h"
+#include "thread_init.h"
+#include "soc_osal.h"
+#include "common_def.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))
+#endif
+
+#define cat1_msleep(ms) osDelay(ms) /* osDelay(1) : 1ms */
+
+/**
+ * Initialize TCP client socket
+ *
+ * @return socket file descriptor on success, -1 on failure
+ */
+static int http_download_client_socket_init(void)
+{
+ 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);
+ 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;
+
+ 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;
+ }
+
+ cat1_print_info("Connected to server successfully\n");
+ return 0;
+}
+
+/**
+ * Send URL request to server
+ *
+ * @param sock socket file descriptor
+ * @param url URL to download
+ * @return 0 on success, -1 on failure
+ */
+static int http_download_client_send_url(int sock, const char *url)
+{
+ 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("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);
+ return -1;
+ }
+
+ cat1_print_info("URL sent successfully (%d bytes)\n", ret);
+ 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
+ */
+static int http_download_client_recv_response(int sock, char *buffer, int buffer_size)
+{
+ if (!buffer || buffer_size <= 0) {
+ return -1;
+ }
+
+ 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;
+ }
+
+ buffer[len] = '\0';
+ cat1_print_info("Received response: %s\n", buffer);
+
+ return len;
+}
+
+/**
+ * Receive file data and save to file
+ *
+ * @param sock socket file descriptor
+ * @param save_path file path to save
+ * @return 0 on success, -1 on failure
+ */
+static int http_download_client_recv_file_data(int sock, const char *save_path)
+{
+ 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 (response_len <= 0) {
+ cat1_print_err("Failed to receive server response\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
+ }
+ }
+
+ // 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);
+ return -1;
+ }
+ cat1_print_info("Saving file to: %s\n", save_path);
+ } else {
+ cat1_print_info("No save path specified, discarding data\n");
+ }
+
+ // 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);
+ }
+ 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");
+ 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;
+ 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
+ */
+void http_download_client_close(void)
+{
+ 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");
+ }
+}
+
+/**
+ * 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;
+ }
+
+ 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");
+ } else {
+ cat1_print_err("HTTP download client task failed\n");
+ }
+
+ g_download_task_running = 0;
+
+ cat1_print_info("HTTP download client task ended\n");
+}
+
+/**
+ * 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
+ */
+errcode_t http_download_client_download_async(const char *url, const char *save_path)
+{
+ 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;
+ }
+
+ 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;
+}
+
+/**
+ * Task function wrapper (legacy, kept for compatibility)
+ */
+void http_download_client_task(void *pvParameters)
+{
+ http_download_client_task_func(pvParameters);
+}
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
--- /dev/null
+/* HTTP File Download Client Header
+ *
+ * This header file defines the interface for the HTTP file download client.
+ */
+
+#ifndef __HTTP_FILE_DOWNLOAD_CLIENT_H
+#define __HTTP_FILE_DOWNLOAD_CLIENT_H
+
+#include <stdint.h>
+#include "errcode.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * Download file from URL via HTTP download server
+ *
+ * @param url URL to download (must be valid, non-empty string)
+ * @param save_path file path to save (NULL or empty string to discard data)
+ * @return 0 on success, -1 on failure
+ */
+int http_download_client_download_file(const char *url, const char *save_path);
+
+/**
+ * Close client connection
+ */
+void http_download_client_close(void);
+
+/**
+ * Check if client is connected
+ *
+ * @return 1 if connected, 0 otherwise
+ */
+uint8_t http_download_client_is_connected(void);
+
+/**
+ * Task function for HTTP download client
+ * Can be used in a separate thread
+ *
+ * @param pvParameters parameters (can contain URL and save_path)
+ */
+void http_download_client_task(void *pvParameters);
+
+/**
+ * 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
+ */
+errcode_t http_download_client_download_async(const char *url, const char *save_path);
+
+/**
+ * Check if download task is running
+ *
+ * @return 1 if running, 0 otherwise
+ */
+uint8_t http_download_client_is_task_running(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __HTTP_FILE_DOWNLOAD_CLIENT_H */
+
#ifdef SUPPORT_TCP_TEST
#include "../tcptest/include/zhihui_tcp_test.h"
#endif
+#include "http_file_download_client.h"
static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen,
uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN], uint32_t* argc)
return ERRCODE_SUCC;
}
+/**
+ * @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
+ */
+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 || 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");
+ 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;
+ }
+ }
+ }
+
+ cat1_print_info("HTTPDLCLIENT: URL=%s\n", url);
+ if (save_path) {
+ cat1_print_info("HTTPDLCLIENT: Save path=%s\n", save_path);
+ } else {
+ cat1_print_info("HTTPDLCLIENT: No save path (discard mode)\n");
+ }
+
+ // 调用HTTP文件下载客户端
+ int result = http_download_client_download_async("https://file-virtual-person-qcl.test.leiniao.com/ambybox/Emotions/StraightEyes/Amber/WorkAndStudy.mp4", save_path);
+
+ if (result == 0) {
+ cat1_print_info("HTTPDLCLIENT: Download completed successfully\n");
+ return ERRCODE_SUCC;
+ } else {
+ cat1_print_err("HTTPDLCLIENT: Download failed\n");
+ return ERRCODE_FAIL;
+ }
+}
+
static uint32_t custom_enable_cat1_debug_log(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
{
{ (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]" },
};