ML307R OTA升级问题总结

基于ML307R_OpenCPU_Standard的代码例程,这里我重新写了ota.c和ota.h文件,主要是去除了例程中cmd传命令和解析命令方式,改成通过opencpu api接口直接调用的方式

ML307R OTA升级问题总结

遇到的问题分析:

1.http回调函数会连接超时,这个查了好久,一开始怀疑是模组问题,还有本地连接的地址问题,还有网络pdp配置问题,按照ai流程了,全部查了一遍,修改测试问题还是存在。然后认真分析了代码的流程,添加了一些报错打印信息,方便定位问题,其实也是没招了,怀疑代码有问题的地方,都在AI中过滤一遍。

比如

调用 cm_ota_firmware_erase()后,会影响网络连接吗

AI回答:不会。cm_ota_firmware_erase() 只擦除用来存放 OTA 升级包的文件系统/Flash 分区,不会触碰 PDP、TCP/IP 栈或网络连接。除非你的存储分区和网络相关数据共享(常规方案不会),否则不会影响网络。

cm_ota_init之后必须擦除旧包吗,会不会影响网络连接

AI回答:不会影响网络连接。cm_ota_firmware_erase() 只作用于模组内部用于暂存升级包的文件系统/Flash 分区,不会动到 PDP/网络栈。

是否必须擦除:

如果你要重新下载新包,建议在下载前擦除旧包,避免空间不足或旧数据干扰。
如果只是继续使用同一个已下载的包(或存储空间足够且不担心残留),可以不擦除。
cm_ota_init() 只是初始化 OTA 状态,本身不擦除数据,也不影响网络。擦除与否由你控制。

最后发现是创建任务的时候,因为升级任务一般只执行一次,在任务入口函数的最后加了osThreadExit(),去除后,发现就好了,最后问了AI的解释:

cm_httpclient 的异步回调需要把事件投递到你创建请求的那个任务上下文(或依赖该任务的消息队列/栈)。你在 ota_upgrade_task 里发起请求后立即 osThreadExit(),该任务被销毁,队列/栈随之回收,底层就无法把连接进展和回调送上来,最终在内部超时(错误码 3=connect timeout)。

处理方式:

不要在发起请求后直接退出任务。让任务存活到升级结束(例如等待一个完成信号量或简单延时循环),然后再退出。
如果一定要退出,改成在回调里发信号,主循环里收到信号后做收尾再结束任务;不要提前 osThreadExit()。

2.升级过程中会出现 write fail问题,导致已接收长度大于写入FLASH的长度,升级中止



[ cmlog ][OPENCPU]:[OTA] write fail ret=-4 curr=1460 sum=4134 total=6152 written=2674
 
httpclient_response_parse OK
 
 
readOnce: 1140 
 
 
asocket_cb
 
[2025-12-09 15:37:47.596]# RECV ASCII>
 3 1
 
readOnce: 320 
 
 
[ cmlog ][OPENCPU]:ota event:3
 
[2025-12-09 15:37:47.677]# RECV ASCII>
[ cmlog ][OPENCPU]:Writed: 4134/6152 - 90% (chunk=1460)
 
httpclient_response_parse OK
 
 
readOnce: 558 
 
 
[ cmlog ][OPENCPU]:ota event:3
 
[ cmlog ][OPENCPU]:Writed: 4692/6152 - 100% (chunk=558)
 
[ cmlog ][OPENCPU]:download ota pkg err!,sum_len:6152,written_size:4692,total:6152

添加分片上传代码



    int before_written = cm_ota_get_written_size();
    ret = cm_ota_firmware_write((const char*)ota_pkg->response_content, ota_pkg->current_len);
    if(ret)
    {
        cm_log_printf(0, "[OTA] write fail ret=%d curr=%d sum=%d total=%d written(before)=%d
",
            ret, ota_pkg->current_len, ota_pkg->sum_len, ota_pkg->total_len, before_written);
 
        /* fallback: 分片重试,避免大块写失败 */
        const char *p = (const char *)ota_pkg->response_content;
        int remaining = ota_pkg->current_len;
        int step = 512;
        while (remaining > 0)
        {
            int chunk = (remaining > step) ? step : remaining;
            int r = cm_ota_firmware_write(p, chunk);
            if (r)
            {
                cm_log_printf(0, "[OTA] retry chunk fail ret=%d chunk=%d remaining=%d written(now)=%d
",
                            r, chunk, remaining, cm_ota_get_written_size());
                return;
            }
            p += chunk;
            remaining -= chunk;
        }
        cm_log_printf(0, "[OTA] fallback chunked write ok, written=%d (+%d)
",
            cm_ota_get_written_size(), cm_ota_get_written_size() - before_written);
    }

3.问题失败的处理

因为牵扯到网络,出现连接失败,连接超时后,是很不好查询的,所以代码的异常处理和日志上报要做好,每个函数调用失败,都要加一些log上报,方便是哪里出现了问题。

比如httpclient回调函数,我这里加了一系列异常打印



/**
 *  rief http下载回调函数
 *  param [in] client_handle http句柄
 *  param [in] event http回调类型
 *  param [in] param http数据,需要按事件类型把 param 转成对应的结构体/枚举使用
 *  param 由 HTTP 客户端库在触发回调时填好并传入。不是用户传的:库根据不同事件传不同的内容指针,例如:
*   CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND:    传 cm_httpclient_callback_rsp_header_param_t*
*   CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND:   传 cm_httpclient_callback_rsp_content_param_t*
*   CM_HTTP_CALLBACK_EVENT_ERROR_IND:         传 cm_httpclient_error_event_e*(枚举值地址)
*   CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND:通常传 NULL
*   CM_HTTP_CALLBACK_EVENT_RSP_END_IND:       通常传 NULL
*
 *  
eturn None
 *  details 此示例下,http content内容即为差分包内容
 */
static void __cm_ota_download_cb(cm_httpclient_handle_t client_handle, cm_httpclient_callback_event_e event, void *param)
{
    (void)client_handle;
    int ret = 0;
 
    // 先打印事件,非内容事件也记录,便于定位无回调问题
    cm_log_printf(0, "ota event:%d
", event);
 
    if (event == CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND)
    {
        return;
    }
    else if (event == CM_HTTP_CALLBACK_EVENT_ERROR_IND)
    {
        if (param)
        {
            cm_httpclient_error_event_e err = *(cm_httpclient_error_event_e *)param;
            cm_log_printf(0, "ota error event:%d (3=connect timeout)
", err);
        }
        else
        {
            cm_log_printf(0, "ota error event:param NULL
");
        }
        return;
    }
    else if (event == CM_HTTP_CALLBACK_EVENT_RSP_HEADER_IND)
    {
        cm_httpclient_callback_rsp_header_param_t *h = (cm_httpclient_callback_rsp_header_param_t *)param;
        if (h)
        {
            cm_log_printf(0, "ota rsp code:%d
", h->response_code);
        }
        return;
    }
 
    // HTTP 异步下载回调,只有在事件为 CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND 时处理数据
    if(param == NULL || event != CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND)
    {
        return; 
    }
 
    /*写入升级包*/
    cm_httpclient_callback_rsp_content_param_t *ota_pkg = (cm_httpclient_callback_rsp_content_param_t *)param;
    
    //已接收长度等于当前包的长度,表示本次http包接收刚开始,接收数据包可能多次回调,但数据包总大小是不变的
    if(ota_pkg->current_len == ota_pkg->sum_len)
    {
        cm_log_printf(0, "set total
");
        cm_ota_set_otasize(ota_pkg->total_len); //更新升级包总大小
    }
    
    //把本次数据块写入升级包文件,用于暂存 OTA 升级包(差分/整包)。
    //下载的数据先写到这个文件系统文件中,待下载完整后再由 OTA 逻辑从该文件读取并烧录升级区域
    int before_written = cm_ota_get_written_size();
    ret = cm_ota_firmware_write((const char*)ota_pkg->response_content, ota_pkg->current_len);
    if(ret)
    {
        cm_log_printf(0, "[OTA] write fail ret=%d curr=%d sum=%d total=%d written(before)=%d
",
            ret, ota_pkg->current_len, ota_pkg->sum_len, ota_pkg->total_len, before_written);
 
        /* fallback: 分片重试,避免大块写失败 */
        const char *p = (const char *)ota_pkg->response_content;
        int remaining = ota_pkg->current_len;
        int step = 512;
        while (remaining > 0)
        {
            int chunk = (remaining > step) ? step : remaining;
            int r = cm_ota_firmware_write(p, chunk);
            if (r)
            {
                cm_log_printf(0, "[OTA] retry chunk fail ret=%d chunk=%d remaining=%d written(now)=%d
",
                            r, chunk, remaining, cm_ota_get_written_size());
                return;
            }
            p += chunk;
            remaining -= chunk;
        }
        cm_log_printf(0, "[OTA] fallback chunked write ok, written=%d (+%d)
",
            cm_ota_get_written_size(), cm_ota_get_written_size() - before_written);
    }
 
    //显示下载进度
    cm_log_printf(0, "Writed: %d/%d - %d%% (chunk=%d)
",
        cm_ota_get_written_size(),
        ota_pkg->total_len, ota_pkg->sum_len * 100 / ota_pkg->total_len,
        ota_pkg->current_len);
 
    if(ota_pkg->sum_len >= ota_pkg->total_len)//下载完成
    {
        //已接收长度大于写入flash长度,表示写入异常
        int written = cm_ota_get_written_size();
        if(ota_pkg->sum_len > written)
        {
            cm_log_printf(0, "download ota pkg err!,sum_len:%d,written_size:%d,total:%d
",
                ota_pkg->sum_len, written, ota_pkg->total_len);
        }
        else
        {
            cm_log_printf(0, "download ota pkg complete!
");
            if (!s_ota_upgrade_started)
            {
                s_ota_upgrade_started = 1;
                cm_log_printf(0, "[OTA] start upgrading...
");
                cm_ota_upgrade();
            }
        }
    }
}

4.通过远程服务器升级OTA和本机电脑方式升级OTA区别

比如我问了AI一个问题

ota升级,4G模组网络本机电脑可以禁用吗

回答如下:

可以,但取决于你的 OTA 服务器在哪里:

服务器在公网(如阿里云 47.99.xx.xx):模组走 4G 直接访问公网,PC 不参与转发,PC 网卡可以禁用(只需保持供电/串口调试即可)。
服务器在你本地 PC/局域网(包括 PC 开的 HTTP 服务,或通过 USB RNDIS/NCM 共享上网):不能禁用 PC 的网络/共享,否则模组就无法访问到固件或出网。
如果你只是用 PC 做日志/串口终端,不承担网络/服务器角色,禁用 PC 网络不影响模组 OTA。

问这个问题目的是,4G模组有自己的网络,如果通过公网,本机电脑可以继续使用自己的wifi或者手机热点,如果通过本机电脑创建本地http服务器,本机电脑只能连接4G模组的网络,192.168.0.x网段的

ML307R OTA升级问题总结

另外还有不同点是http服务器的创建,本机电脑,比如window,可以直接通过cmd或powershell,通过命令创建


 python -m http.server 8000

注意要先进入指定目录,这个目录就是本地http服务器的根目录了

ML307R OTA升级问题总结

ML307R SDK代码里连接的网址和端口,就改成本机的ip和端口



    const char *server_url = "http://192.168.0.100:8000";  // 使用本地电脑地址
    const char *url_path   = "/system_patch.bin";    

如果是通过公网地址,比如阿里云服务器,可以通过apt install apache2安装包的方式创建http服务器。默认是80端口,这样在阿里云服务器上启动Apache2服务就可以了

ML307R OTA升级问题总结

http服务器的默认路径是在/var/www/html目录下,需要手动将打包好的固件文件放到这个位置

ML307R OTA升级问题总结

验证http服务端,可以直接在浏览器上打开试试,如果可以通过浏览器操作成功,http服务端就配置好了。

4G模组段,主要是一个4G网络的初始化配置,主要是检查SIM卡的状态和PDP是否激活成功

ML307R OTA升级问题总结

ML307R OTA升级问题总结

© 版权声明

相关文章

暂无评论

none
暂无评论...