[網絡篇]ESP8266-NonOS學習筆記(十二)之FOTA無線固件更新升級


其實FOTA(Firmware Over-The-Air)技術,已經是一個相對比較成熟的技術了,不管是硬件還是軟件都已經很完善了,就比如我們的手機系統更新,帶的手環或者智能手表的更新,又或者是智能音響的升級,相信大家現在在各種電子產品中都會見到固件更新,這種固件更新大家都可以理解為是FOTA,只不過叫法不是很統一。那么它有什么地方值得我們學習呢?其實最重要的一點就是,假如我們是做的真正的產品,當你賣出去了很多產品,但是這時候你發現產品有Bug,那怎么辦呢?這時候就需要我們通過FOTA來對產品進行固件升級,這樣就可以完美的解決掉Bug了,那么假如沒有FOTA技術,難不成退貨?難不成去用戶家里升級?這都是不現實的。

呃呃,閑話少說,我們還是開始正題,FOTA其實就是在樂鑫給的Demo中有,但是很多人都不是很注重樂鑫給的Demo,或者官方給的文檔,遇到問題的時候總是在尋求他人的幫助,但是這些問題自己多讀幾遍文檔啥的都可以完全解決,再者說別人說的也不一定正確,所以還是建議大家多讀文檔,多看官方給的Demo。

那么先給大家一個本篇文章必看的文檔鏈接,看一下官方對FOTA更權威的講解,而不是聽我這~

https://www.espressif.com/sites/default/files/documentation/99c-esp8266_fota_upgrade_cn.pdf

大家可以跟着第二章利用樂鑫的雲平台進行一次FOTA升級測試,當然這一種方式比較繁瑣的,工程也比較大,閱讀起來還是有一定的難度,那么大家就可以再看一下第三章的升級原理,仔細閱讀一下,你就可以自己寫了~

我們先來看一下Flash布局,幫助我們更好的理解,這里為了方便和大家一起分析,就把文檔中重要的部分截圖展示,還是建議大家去仔細閱讀一下的:

可以看到圖中,我用紅框圈出來了兩部分,這兩部分就是FOTA當中我們的初始化固件(user1.bin)和升級固件(user2.bin)在flash中的存儲位置,也就是說user2.bin是我們通過FOTA無線熱更新升級的,但是這里面最重要的一個部分我並沒有圈出來,大家猜一下是哪一部分呢?相信大家肯定都知道,那就是最前面的boot信息部分,也就是我們常說的bootloader部分,那么這一部分到底有什么作用呢?

是的,這問題明顯文檔中也提到了,它的作用就是決定運行user1.bin部分還是user2.bin部分,你把整個user2.bin的固件升級完成了,但是一重啟還是運行user1.bin部分,那是不是就沒啥用了呢?所以第一次燒寫的時候千萬不要忘記燒寫boot_Vxxx.bin。

在前面我講大家可以跟着第二章利用樂鑫的雲平台進行一次測試,那么我們自己做不利用樂鑫的雲平台,是不是需要一個服務器呢?回答:是的。假如沒有呢?那我們還有很多方式可以實現,比如本地搭建,缺點是只能局域網內升級,說白了,我們只需要將固件放在一個可以通過url訪問的位置,並且可以下載就可以完成我們的FOTA升級了。

這一點樂鑫也有提我們利用自定義服務器的要求:

如果你對web這塊比較熟悉,那么就可以很容易的實現了,不熟悉也沒關系,我們還有萬能的python,利用python的web框架,我們就可以很簡單的實現一個web server了,那么這里我們寫一個最簡單的,只提供下載固件的API就可以了,最核心的功能實現了就可以,話不多說,看代碼?

 1 import os
 2 from werkzeug.utils import secure_filename
 3 from flask import Flask,request,send_from_directory
 4 
 5  
 6 app = Flask(__name__)
 7 
 8 @app.route('/api/download_firmware',methods=['GET'])
 9 def download():
10     filename = request.args.get('firmware_bin')
11 
12     print("\n")
13     print("[IAMLIUBO] file name: " + filename)
14     print("[IAMLIUBO] file size: " + str(os.path.getsize('bin/upgrade/'+filename)))
15     print("\n")
16 
17     if os.path.isfile(os.path.join('bin/upgrade', filename)):
18         return send_from_directory('bin/upgrade',filename,as_attachment=True)
19 
20 if __name__ == '__main__':
21     app.run(host='0.0.0.0',debug=True)

 

這里我們只實現了一個最簡單的web server,並且對外提供固件下載功能,這個腳本直接放在工程目錄下運行就可以了,FOTA生成的固件都在“bin/upgrade”文件夾下,所以你只要正確編譯好了固件是不會有問題的,需要說明的是Flask框架默認端口是5000。

那么我們再來看一下設備端的代碼,其實非常簡單,這些代碼是我從樂鑫給的Demo中分離出來的,沒有用的全部刪掉了,先上代碼給大家看一下:

 1 LOCAL void ICACHE_FLASH_ATTR
 2 tcp_client_connect_cb(void *arg)
 3 {
 4     struct espconn *pespconn = arg;
 5     struct upgrade_server_info *server = NULL;
 6 
 7     INFO("[IAMLIUBO] tcp client connect cb\n");
 8     if (wifi_get_opmode() ==  STATIONAP_MODE ) {
 9         wifi_set_opmode(STATION_MODE);
10     }
11 
12     espconn_regist_recvcb(pespconn, tcp_client_recv_cb);
13     espconn_regist_sentcb(pespconn, tcp_client_sent_cb);
14 
15     server = (struct upgrade_server_info *)os_zalloc(sizeof(struct upgrade_server_info));
16 
17     server->upgrade_version[15] = '\0';
18 
19     INFO("[IMALIUBO] fota upgarde start\r\n");
20     fota_upgrade_begin(pespconn, server);
21 }
22 LOCAL void ICACHE_FLASH_ATTR
23 fota_upgrade_rsp(void *arg)
24 {
25     struct upgrade_server_info *server = arg;
26     struct espconn *pespconn = server->pespconn;
27 
28     if (server->upgrade_flag == true) {
29         INFO("[IAMLIUBO] fota upgarde successfully\n");
30         system_upgrade_reboot();
31     } else {
32         INFO("[IAMLIUBO] fota upgrade failed\n");
33     }
34 
35     os_free(server->url);
36     server->url = NULL;
37     os_free(server);
38     server = NULL;
39 }
40 
41 
42 LOCAL void ICACHE_FLASH_ATTR
43 fota_upgrade_begin(struct espconn *pespconn, struct upgrade_server_info *server)
44 {
45     uint8 user_bin[30] = {0};
46 
47     server->pespconn = pespconn;
48 
49     os_memcpy(server->ip, pespconn->proto.tcp->remote_ip, 4);
50 
51     server->port = OTA_SERVER_PORT;
52 
53 
54     server->check_cb = fota_upgrade_rsp;
55     server->check_times = 120000;
56 
57     if (server->url == NULL) {
58         server->url = (uint8 *)os_zalloc(512);
59     }
60 
61     if (system_upgrade_userbin_check() == UPGRADE_FW_BIN1) {
62         os_memcpy(user_bin, "user2.4096.new.6.bin", 21);
63     } else if (system_upgrade_userbin_check() == UPGRADE_FW_BIN2) {
64         os_memcpy(user_bin, "user1.4096.new.6.bin", 21);
65     }
66 
67     os_sprintf(server->url, "GET /api/download_firmware?firmware_bin=%s HTTP/1.0\r\nHost: "IPSTR":%d\r\n"pheadbuffer"",
68                user_bin,IP2STR(server->ip));
69     
70     INFO("[IAMLIUBO] %s\n",server->url);
71 
72 
73     if (system_upgrade_start(server) == false) {
74         INFO("[IAMLIUBO] upgrade is already started\n");
75     }
76 }
77 
78 
79 void ICACHE_FLASH_ATTR
80 fota_init(void)
81 {
82     user_conn.proto.tcp = &user_tcp;
83     user_conn.type = ESPCONN_TCP;
84     user_conn.state = ESPCONN_NONE;
85 
86     uint32 ota_server_ip = ipaddr_addr(OTA_SERVER_IP);
87     
88     os_memcpy(user_conn.proto.tcp->remote_ip, &ota_server_ip, 4);
89     user_conn.proto.tcp->local_port = espconn_port();
90 
91     user_conn.proto.tcp->remote_port = OTA_SERVER_PORT;
92 
93     espconn_regist_connectcb(&user_conn, tcp_client_connect_cb);
94     espconn_regist_reconcb(&user_conn, tcp_client_recon_cb);
95 
96     espconn_connect(&user_conn);
97 }

 

主要的就這幾個函數吧,第一步是初始化,就是我們先跟服務器建立TCP連接,可能有人會問為什么還要建立TCP連接呢?這就涉及到網絡知識了,大家可以自行百度學習一下哦~建立TCP連接成功后我們就開始升級了,可以看到我們在連接成功回調函數里調用了fota_upgrade_begin()函數,在這里我們還聲明了一個結構體,用於保存升級信息,包括服務器IP,端口,升級前版本信息,此次升級版本信息,固件的URL地址等等,不過我們這里只是簡單的去實現,所以很多並沒有用到,但這不影響我們使用。

開始升級后,我們需要先檢查當前的userbin,然后去拼裝我們的URL,如果你有別的版本命名方式,就需要根據自己的命名方式去拼裝URL了。

1     if (system_upgrade_userbin_check() == UPGRADE_FW_BIN1) {
2         os_memcpy(user_bin, "user2.4096.new.6.bin", 21);
3     } else if (system_upgrade_userbin_check() == UPGRADE_FW_BIN2) {
4         os_memcpy(user_bin, "user1.4096.new.6.bin", 21);
5     }
6 
7     os_sprintf(server->url, "GET /api/download_firmware?firmware_bin=%s HTTP/1.0\r\nHost: "IPSTR":%d\r\n"pheadbuffer"",
8                user_bin,IP2STR(server->ip));

升級完成后,應該做什么呢?當然是重啟了,我們在回調函數中檢查升級是否成功,如果成功,我們就重啟運行新的固件:

1     if (server->upgrade_flag == true) {
2         INFO("[IAMLIUBO] fota upgarde successfully\n");
3         system_upgrade_reboot();
4     } else {
5         INFO("[IAMLIUBO] fota upgrade failed\n");
6     }

至此,就完成了,是不是很簡單呢?大家可以直接拉取我的例程運行,如果你不做改動,你會發現會不停的在兩個固件中互相拉取重啟運行,而且你也可以不用用串口下載程序了,直接通過FOTA下載程序,簡單方便又快捷~

快來測試一下吧,附上Github倉庫地址:

https://github.com/imliubo/makingfunxyz-esp8266

歡迎大家去我的倉庫點個star,有問題或者Bug可以提交issues,我看到后會第一時間回復,如果您對我的代碼有改進意見,也歡迎fork后提交PR,我會及時采納大家的意見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM