ESP32ファームウェアをESP Web Toolsで共有する

Visual Studo CodeのPlatformIOで作成したESP32用のファームウェアをESP32に書き込むにはコンパイラやダウンローダのセットアップが必要と思われるかもしれませんが、ブラウザで動作するESP Web Toolsを使えば、ブラウザとUSBポートがあれば書き込むことができます。

https://esphome.github.io/esp-web-tools/

W3Cで標準化されたWeb Serial APIを使って、ブラウザからシリアルポートを直接操作できます。ですので、USBケーブルでESP32デバイスに仮想Comポートとして接続することができます。
ちなみに、Web Serial APIに対応しているブラウザはChromeやEdgeであり、Safariは対応していないようです。

ブラウザのみで書き込むことができるので、ファームウェアファイルをリポジトリとして複数作成しておいて、ブラウザから切り替えて書き込んだり、作成したファームウェアファイルを複数の人と共有したりすることができるようになります。

ソースコードもろもろをGitHubに上げておきました。

https://github.com/poruruba/EspWebDownloader

## ファームウェアファイルの構成

今回対象とするESPのCPUは、ESP32にします。古いESP8266は対象から外します。

ファームウェアファイルはたいてい複数のファイルで構成されます。
書き込む先のESP32の不揮発メモリは複数のパーティションで構成され、それぞれのパーティションの先頭にファームウェアイメージを配置する必要があるためです。

以下の4つのファイルで構成されます。構成は、コンパイラのバージョンによって多少異なるようですが、本日(2024/02/12)時点では、以下の構成でした。

|名称|ファイル名|アドレス|
|-|-|-|
|セカンドステージローダ|bootloader.bin|0x00000 or 0x01000|
|パーティションテーブル|partitions.bin|0x08000|
|ブートスイッチ|boot_app0.bin|0x0e000|
|ファームウェア|firmware.bin|0x10000|

ESP32のCPUの種類が複数ありますが、ESP Web Toolsでは、「ESP32」「ESP32-S3」「ESP32-S2」「ESP32-C3」で分けているように思います。（この辺りは想像が入っています。。。）
ESP32-S2が手元にないので、それ以外の3種類について確認してみました。

ブートスイッチは、ファイルやアドレスがESP32で共通のように見えます。
また、セカンドステージローダのアドレスは、ESP32の場合は0x0100、ESP32-S3・ESP32-C3では0x0000でした。ですので、CPUの種類に合わせて配置場所を合わせる必要があります。
パーティションテーブルやファームウェアファイルは、作成するアプリごとに変わるようです。
ですので、セカンドステージローダは、CPUの種類ごとにあらかじめ持っておいて、アプリケーションごとにパーティションテーブルとファームウェアをアップロードすればよさそうです。（ですが、念のためセカンドステージローダもアプリケーションごとにアップするようにしてます）
ちなみに、コンパイラをバージョンアップしたら、セカンドステージローダやブートスイッチは変わると思いますので、要注意です。

各ファイルは、アプリのコンパイル時の処理内容を見ると場所がわかります。以下M5StickCの場合の例です。

```
> python esptool.py 
--chip esp32 
--port "COM4" 
--baud 1500000 
--before default_reset 
--after hard_reset write_flash 
-z
--flash_mode dio
--flash_freq 40m
--flash_size 4MB
0x1000 .pio\build\m5stick-c\bootloader.bin 
0x8000 .pio\build\m5stick-c\partitions.bin 
0xe000 C:\Users\XXXXX\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin 
0x10000 .pio\build\m5stick-c\firmware.bin
```

.pioフォルダは、Visual Studio Codeでの代表的なコンパイルツールであるPlatformIOで生成されるフォルダです。

## ESP Web Toolsでのマニフェストファイル

ESP Web Toolsでファームウェアファイルを書き込むにはマニフェストファイルを作成する必要があります。
以下のような内容です。
あまり説明はいらないですね。各ファームウェアファイルは、マニフェストファイルと同じフォルダに置くことを想定しました。

```json:manifest.json
{
	"name": "M5Stick-C",
	"builds": [
		{
			"chipFamily": "ESP32",
			"improv": false,
			"parts": [
				{
					"path": "bootloader.bin",
					"offset": 4096
				},
				{
					"path": "partitions.bin",
					"offset": 32768
				},
				{
					"path": "boot_app0.bin",
					"offset": 57344
				},
				{
					"path": "firmware.bin",
					"offset": 65536
				}
			]
		}
	]
}
```

chipFamilyのところに、作成したファームウェアの対象CPUに合わせて「ESP32」「ESP32-S3」「ESP32-S2」「ESP32-C3」「ESP8366」を指定します。

## ファームウェアファイルの生成

ファームウェアファイルの生成には以下を意識する必要があります。

①CPUの種類ごとにファームウェアファイルをコンパイルし分ける必要がある
②ボードの種類ごとにソースコードを作り分ける必要がある。

①は、先ほど説明した通り、CPUの種類ごとにバイナリや配置が異なるので、コンパイルし分ける必要があります。
②は、CPUの細かな種類ごとにCPU周波数やメモリサイズが異なりますし、周辺デバイスが接続しているGPIOや機能のポート番号が異なるためです。

CPUやボードの種類は、platformio.iniファイルで以下のように定義しているものです。

```ini:platformio.ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
monitor_speed = 115200
upload_port = COM4
monitor_port = COM4

[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
upload_port = COM3
monitor_port = COM3
```

board= のところです。その参照先に細かな定義があります。あまり中身を見ることはないとは思いますが。

ボードごとのポート番号の違いは以下のように、C言語のソースコードの中で切り替えます。
ARDUINO_M5Stick_C のような定義文は、platformio.iniのboardごとに定義されています。

```c
#if defined(ARDUINO_M5Stick_C)
#define PORT_LED    30
#elif defined(ARDUINO_M5STACK_Core2)
#define PORT_LED    2
#elif defined(ARDUINO_ESP32C3_DEV)
#define PORT_LED    10
#elif defined(ARDUINO_ESP32S3_DEV)
#define PORT_LED    21
#endif
```

ということで、まとめると以下のようになります。

１．platformio.iniにボードごとに定義を記載します。
２．ボードごとにコンパイルしてファームウェアファイルを生成します。
３．ファームウェアファイル（boot_app0.binを除く）をcpuFamilyの選択とともに、アップロードします。

## ファームウェア格納フォルダ構成

ファームウェアは、アプリごとに異なるのはもちろん、ボードごとに作り分けます。
ファームウェアをサーバに格納する際に、以下のように整理して配置します。

```
/firmwares/
 /Appフォルダ[AppのUUID]*
　 app.json
　 /Modelフォルダ[ModelのUUID]*
　 　manifest.json
　　 bootloader.bin
　　 partitions.bin
　　 boot_app0.bin
　　 firmware.bin
```

アプリケーションごとにAppフォルダを作成します。
そして、そのフォルダの中に、ボードごとにModelフォルダを作成します。各フォルダ名は、UUIDにしてかぶらないようにしています。

Modelフォルダに、生成したファームウェアファイルとブートスイッチ(boot_app0.bin)を置きます。ESP Web Toolsで必要なマニフェストファイルも置きます。
各ファームウェアファイルは、マニフェストファイルと同じフォルダに置くようにしました。
アプリケーションに関するメモ等は、Appフォフォルダにapp.jsonに残せるようにしました。

```json:app.json
{
	"name": "アプリケーション",
	"uuid": "fa621ef9-19a7-4eaa-9d26-80668ca24db0",
	"memo": "サンプルアプリケーションです。",
	"created_at": 1707657409123,
	"model_list": [
		{
			"name": "M5Core2",
			"uuid": "01fc8288-8ed2-4d20-ae60-25d9950d00ef",
			"chip_family": "ESP32",
			"created_at": 1707698850206,
			"memo": "M5Core2用です。"
		},
		{
			"name": "M5StickC",
			"uuid": "df1e51cc-d5a8-42d4-a28a-11ef67ab37fe",
			"chip_family": "ESP32",
			"created_at": 1707700127643,
			"memo": "M5StickC用です"
		}
	]
}
```

## サーバAPI

あとは、上記の構成になるようにサーバAPIを組んで、クライアント側のJavascriptでUIを実装すればよいです。

サーバ側は以下にまとめてあります。

api/controllers/espweb-api
　index.js
　swagger.yaml

- /espweb-list-app
アプリ一覧を取得します。具体的にはすべてのapp.jsonを取得します。

- /espweb-add-app
アプリを登録します。サーバ側にAppフォルダを作成します。

- /espweb-update-app
アプリの情報を更新します。

- /espweb-remove-app
アプリを削除します。アプリに紐づいたすべてのモデルも削除されます。

- /espweb-add-model
アプリにモデルを追加します。サーバ側にModelフォルダを作成し、ファームウェアファイルをアップロードし、manifest.jsonを作成します。

- /espweb-update-model
モデル情報を更新します。

- /espweb-update-model
モデルを削除します。

## クライアント側

以下のフォルダにあります。

public/
　index.html
　js/start.js

Bootstrap3、Vue2を使っています。

まず、以下のようにscriptタグを追加します。

```html:nodejs/public/index.html
<script type="module" src="https://unpkg.com/esp-web-tools@9/dist/web/install-button.js?module"></script>
```

ファームウェア書き込みを始めるボタンは以下のようにHTMLで記述します。属性manifestにマニフェストファイルの場所を指定します。(一部Vue記法になっています)

```html:nodejs/public/index.html
<esp-web-install-button v-bind:manifest="`firmwares/${app.uuid}/${model.uuid}/manifest.json`"></esp-web-install-button> {{model.name}}({{model.chip_family}})
```


## 起動方法

GitHubから一式をZIPでダウンロードします。

https://github.com/poruruba/EspWebDownloader

```
> unzip EspWebDownloader-master.zip
> cd EspWebDownloader-master
> cd nodejs
> npm install
> node app.json
```

あとは、ブラウザから開くだけです。
ポート番号はデフォルトで20080、.envファイルにPORT=XXXXを指定して変更することもできます。

## 参考

- [ESP Web Tools](https://esphome.github.io/esp-web-tools/)
- https://github.com/esphome/esp-web-tools
- [PlatformIOで作成したESP32ファームウェアをESP Web Toolsで書き込む](https://qiita.com/poruruba/items/b72a77bace5c59ee38b8)
- [PlatformIOでのIniファイル指定例](https://qiita.com/poruruba/items/ef83e684df11439e259a)
- [Amplify CLIを使って、ローカルで実行＆AWSにデプロイ](https://qiita.com/poruruba/items/0030a40f148369c4130d)

以上