Android Device 做 WebServer,讓 ESP32 下載 BIN 檔後更新韌體(OTA)(一)

春麗 S.T.E.M.
9 min readMar 13, 2024

--

目錄
1. 前情提要
2. Android Studio
2-1 MyWebServer Class
2-2 將檔案放到 assets
2-3 MainActivity

前情提要
Android Studio
MyWebServer Class
將檔案放到 assets
MainActivity

前情提要

前一篇文章中,介紹了 mDNS 這個內網的域名系統,它不需透過 DNS 伺服器,即可做到讓內網多個設備知道這個 host name 對應的 IP 位址,使用 mDNS 的好處是若設備接收 DHCP 分發的 IP,當你重啟路由器,這些 IP 會再變動一次,如果有一個設備原先是做 Web Server,那麼這時,其他的設備就找不到它了。

當然,如果要做 Web Server 的設備最好是固定 IP,不過你要想想看,其他設備做溝通時也是要寫死這個固定 IP,比方說 call API 是透過與 http://192.168.16.6:8081/someFile.json 連線去下載,這個寫死 IP 的方式就沒什麼彈性。

如果可以做到 http://android.local:8081/someFile.json 就很活用,因為這表示設備可以綁到其他 IP 上,甚至設備是浮動 IP 的狀況可能也適用。不過前一篇只有連上 Web Page,這篇文章要嘗試透過某個網址下載檔案,好!開始吧!

回目錄

繼續閱讀|回目錄

Android Studio

首先,我們將程式碼改一改,在 MyService 這個檔案中。

MyWebServer Class

import android.content.Context
import fi.iki.elonen.NanoHTTPD
import java.io.File
import java.io.FileInputStream

class MyWebServer(private val context: Context, port: Int) : NanoHTTPD(port) {

override fun serve(session: IHTTPSession): Response {
val uri = session.uri

if (uri == "/countrycode.json") {
val firmwareFile = File(context.filesDir, "countrycode.json")
val fis = FileInputStream(firmwareFile)
return newFixedLengthResponse(
Response.Status.OK,
"application/octet-stream",
fis,
firmwareFile.length()
)
}

if (uri == "/serverfile.txt") {
val firmwareFile = File(context.filesDir, "serverfile.txt")
val fis = FileInputStream(firmwareFile)
return newFixedLengthResponse(
Response.Status.OK,
"application/octet-stream",
fis,
firmwareFile.length()
)
}
return newFixedLengthResponse("Hello from Android Test Server!")
}
}

這裡使用了 NanoHTTPD 這個套件,我們知道在 Linux 中有 httpd 這個關於 Web 的套件可以使用,在 Android 開發中也有 NanoHTTPD。

首先,在 MyWebServer 這個 Class 中加入一個參數 context,這是為了要使用它存取 filesDir,將來 MyWebServer 若在 Activity 中建立,context 就是這個 Activity,即是在這個 Activity 中將檔案開出來。

雖然我們存取了檔名為 “xxxx.json”“xxxx.txt”filesDir,但真正要將它作為響應的檔案就必須使用 FileInputStream

總結上面,所以 import 裡面我們就會看到 ContextNanoHTTPDFileFileInputStream

再來是將讀出來的 FileInputStreamfis)變成特定 uri 的響應結果,即是當 WebServer 建立後,Client 透過對某個 uri 的請求等操作,最後會得到的東西,所以 if (uri) 括號裡面的回傳的就是將 fis 帶入的 newFixedLengthResponse

接著從 newFixedLengthResponse 的定義來看:

public static Response newFixedLengthResponse(
IStatus status,
String mimeType,
InputStream data,
long totalBytes
) {
return new Response(status, mimeType, data, totalBytes);
}

第一個是 Response 的 status,即是 HTTP Status Code,200 即為 OK;application/octet-stream 是 MIME Type,表示八位(1 Byte)二進位檔案;Data 就是用 FileInputStream 讀出來的 fis;最後是位元長度

最後除了 Handle 特定的 uri,server 本身也要回傳一個具有相同參數的 Response,只要給個特定訊息 Hello from Android Test Server! 即可。

將檔案放到 assets

不過現在我們並沒有放檔案進去,我們在 app => New => Folder => Assets Folder,如下:

接著把你要的檔案複製到 assets 中。

MainActivity

同樣加入如下:

class MainActivity : AppCompatActivity() {

private lateinit var server: MyWebServer

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
server = MyWebServer(this, 8081)
}

override fun onDestroy() {
super.onDestroy()
server.stop()
}
}

在 Activity 中,這時 MyWebServer 要加入 context 為 this,這裡代表 MainActivity,並且使用 8081 這個 port。

接著,使用你其他的行動裝置,連上這個 Web Server 看看,在瀏覽器列輸入 IP:port/serverfile.txt,這裡的 IP 端看你的 Android Device 取得的 IP 去做修改,前面有提到 Web Server 固定 IP 是較恰當的做法,只是輸入固定 IP 顯得不夠靈活。

結果卻出現這樣的錯誤!我們看看錯誤訊息。

SERVER INTERNAL ERROR: IOException: /data/user/0/com.example.myapplication/files/serverfile.txt: open failed: ENOENT (No such file or directory)

HTTP 的回應告訴我們並沒有 serverfile.txt 這樣的檔案在這個路徑中,好!試著在 MainActivity 中加入下面函式。

private fun copyFileToInternalStorage(fileName: String) {
val assetManager = assets
val inputStream = assetManager.open(fileName)
val outputFile = File(filesDir, fileName)
val outputStream = FileOutputStream(outputFile)

inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
}

我們在 oncreate 中呼叫這個 Function,copyFileToInternalStorage(“serverfile.txt”),記得把檔案名稱放入,這個 Function 的意思將 inputStream 複製到 outputStream 讓它開始對外,接著重新 build 看看。

重整剛才出現錯誤的網頁,

下載成功!

不過到這邊只完成前段的動作,以韌體更新的架構來說,必須是 ESP32 向 Android 行動裝置做 HTTP 請求,當 ESP32 確定可以下載 BIN 檔回來,我們還需要讓這個 BIN 檔能夠進一步作為 ESP32 的韌體並更新。

透過無線更新硬體的韌體的方式,我們叫做 OTA(),而 OTA 的步驟繁雜一些,所以留到下次再分享,好了!

這次就分享到這,感謝您的閱讀。

回目錄

繼續閱讀|回目錄

Reference:

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet