Windows10 からスタックチャンを書き込む

## 課題の整理

スタックチャンを開発する環境の要件は次の通り
- Windows
- Mac
- Linux

バージョンは明記されていませんでしたが、Windows10 の WSL で試していたところ、最後の書き込み部分でWSL側にスタックチャンデバイスをUSB経由で見えるようにしないとならないのですが、Windows10ではそれができなさそうというところがこの記事の出発点です。

https://github.com/stack-chan/stack-chan/blob/dev/v1.0/firmware/docs/getting-started_ja.md


## どうするか

現在Windows10のPCから確認できているのは、株式会社アールティが公開している Web Flashという書き込み方法です。

https://rt-net.jp/humanoid/archives/5907

オリジナルである stack-chan のGitHubページにも同じような書き込み用のWebサイトがありましたが、そちらから書き込みをしてもバイナリが合わないのかうまく動きませんでした。

https://github.com/stack-chan/stack-chan/blob/dev/v1.0/firmware/README_ja.md

とはいえ、Webから書き込むことができるので、Windows10でビルドさえできれば、このWebからの書き込みができるのではないかと考えてます。

## 調査

### ディレクトリ構成

https://github.com/stack-chan/stack-chan/tree/dev/v1.0/web/flash

```
flash.css # CSSファイル
index.html # 書き込み画面
manifest_esp32_m5stack fire.json
manifest_esp32_m5stack.json
manifest_esp32_m5stack_core2.json
manifest_esp32_m5stack_cores3.json
```

### index.html

#### 中を見る
まずは `index.html` の中身を確認します。

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Flash firmware</title>
  <link rel="stylesheet" href="../global.css">
  <link rel="stylesheet" href="flash.css">
  <script type="module" src="https://unpkg.com/esp-web-tools@9/dist/web/install-button.js?module"></script>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const button = document.querySelector('esp-web-install-button')
      const select = document.querySelector('.select-target')
      select.addEventListener('change', (event) => {
        const target = event.target.value
        button.setAttribute("manifest", `manifest_${target}.json`)
      })
    })
  </script>
</head>

<body>
  <div class="app">
    <select class="select-target">
      <option value="esp32_m5stack">M5Stack</option>
      <option value="esp32_m5stack_core2">M5Stack Core2</option>
      <option value="esp32_m5stack_fire">M5Stack Fire</option>
      <option value="esp32_m5stack_cores3">M5Stack CoreS3</option>
    </select>
    <esp-web-install-button manifest="manifest_esp32_m5stack.json" class="button-flash-container">
      <button slot="activate" class="button-flash">
        Flash Stack-chan firmware [・＿・]
      </button>
      <span slot="unsupported">Ah snap, your browser doesn't work!</span>
      <span slot="not-allowed">Ah snap, you are not allowed to use this on HTTP!</span>
    </esp-web-install-button>
  </div>

</body>

</html>
```
#### ChatGTPに解説させる
それほど大きくないですが、JavascriptやらHTMLには疎いのでChatGPTに聞いてみます。

---
このHTMLファイルは、ESP32搭載デバイス（主にM5Stackシリーズ）にStack-chanのファームウェアをWebブラウザ経由で書き込む（Flashする）ためのWeb UIを提供します。その仕組みを順を追って説明します。

🔧 主な構成要素とその役割
1 . <esp-web-install-button> カスタム要素
esp-web-tools ライブラリが提供するWeb Componentsの一つ。

Web Serial APIを使って、ブラウザから直接ESP32にファームウェアを書き込めます。

manifest 属性に指定された .json ファイル（ファームウェアの構成情報）を参照して、書き込みを行います。

2 . JavaScript（DOMContentLoadedイベント内）
デバイスの種類を <select> ドロップダウンで選べるようにしています。

ユーザーが選択肢を変更すると、<esp-web-install-button> の manifest 属性を動的に変更します。

```javascript
button.setAttribute("manifest", `manifest_${target}.json`)
```
→ 例: esp32_m5stack_core2 を選んだら manifest_esp32_m5stack_core2.json が使用されます。

3 . スロット（<esp-web-install-button>の中）
slot="activate"：実際のフラッシュボタンのUI。

slot="unsupported"：ブラウザが非対応だった場合に表示。

slot="not-allowed"：HTTPでは動作しないことを警告（Web SerialはHTTPS必須）。

🧠 処理の流れ
ユーザーがページを開く。

初期状態で manifest_esp32_m5stack.json が選択された状態。

<select> でデバイスの種類を選ぶと、JavaScriptが manifest の指定を切り替える。

Flash Stack-chan firmware [・＿・] ボタンを押すと、ブラウザがシリアル接続可能なデバイスを探し、選択させる。

対応したファームウェアが選択されたESP32デバイスに書き込まれる。

🌐 必要な環境
Google Chrome, Edge など Web Serial API に対応したブラウザ

HTTPSページであること（セキュリティ制約によりHTTPでは動作不可）

manifest_xxx.json ファイルが適切にホスティングされていること

🔍 まとめ
|機能|説明|
|:---|---|
|デバイス選択|<select> により選択。JavaScriptでmanifestファイルを切り替え|
|書き込みボタン|esp-web-tools のWeb Componentを使用|
|書き込み対象|ESP32（M5Stackシリーズ）|
|利用API|Web Serial API（ブラウザ→USB経由でESP32と通信）|
---

HTTPSのページであることと、manifest_xxx.json がちゃんとホスティングされていると良さそうですね。

### json 

では、json の中身はどうなっているのでしょうか。

#### 中をみる

https://github.com/stack-chan/stack-chan/blob/dev/v1.0/web/flash/manifest_esp32_m5stack_cores3.json

```
{
  "name": "Stack-chan",
  "version": "1.0.0",
  "builds": [
    {
      "chipFamily": "ESP32-S3",
      "parts": [
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/bootloader.bin",
          "offset": 0
        },
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/partition-table.bin",
          "offset": 32768
        },
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/xs_esp32.bin",
          "offset": 65536
        }
      ]
    }
  ]
}
```


#### ChatGPT に解説させる

---
これは、esp-web-tools の <esp-web-install-button> で使用される manifest JSON ファイルで、ESP32デバイス（この場合は M5Stack CoreS3）にフラッシュするためのファームウェア情報が記述されています。

🧾 全体構成の概要
このJSONファイルは、以下のような情報を含んでいます：

|フィールド|説明|
|-------|---|
|name|プロジェクトや製品の名前（例：Stack-chan）|
|version	|ファームウェアのバージョン|
|builds	|書き込むファームウェアの構成（チップ種別や各バイナリのオフセットなど）|

🔍 詳細解説
```
{
  "name": "Stack-chan",
  "version": "1.0.0",
  "builds": [
    {
      "chipFamily": "ESP32-S3",
      "parts": [
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/bootloader.bin",
          "offset": 0
        },
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/partition-table.bin",
          "offset": 32768
        },
        {
          "path": "tech.moddable.stackchan/com.m5stack.cores3/xs_esp32.bin",
          "offset": 65536
        }
      ]
    }
  ]
}
```
📌 chipFamily: "ESP32-S3"
このファームウェアが対応しているESPチップの種類。

今回は ESP32-S3 → M5Stack CoreS3 用であることを示しています。

📌 parts 配列
各ファイルと書き込み先のアドレス（フラッシュオフセット）を定義します。

ESP32に書き込む際の典型的な構成です：

|path|offset（書き込み先）|説明|
|----|----|----|
|bootloader.bin|0x0000|ブートローダー。起動時に実行される|
|partition-table.bin|0x8000 (32768)|パーティションテーブル。フラッシュ内のレイアウトを定義|
|xs_esp32.bin|0x10000 (65536)|実際のアプリケーション（Stack-chan本体）|

💡 Web Serial API + manifest の役割
このJSONは、Web Serial経由でESP32にファームウェアを書き込む際に、どのファイルをどのアドレスに書くかをブラウザに指示するための「地図」です。
esp-web-install-button コンポーネントがこのJSONを読み取り、USB経由でESP32に自動的に書き込みを行います。

✅ 要件
上記ファイルパス (path) は、ブラウザからアクセス可能なHTTP(S) URLに解決される必要があります（相対パスとして指定されている場合、manifestのある場所が基準）。

バイナリファイルは予めビルド・配置されている必要があります。
---

### ホスト先の検討

GitHub でホストしたらHTTPSを備え、かつバイナリも保管できそうですね。試してみましょう


## GitHub でホストする。

ソースコードはほぼ同じものを使うので、リポジトリをフォークしておきます。
ブランチの管理とかはまだ考えてませんが、本家のブランチとは別に用意して管理しようと思います。ここでは `dev/1.0-kaneko` としておきます。

https://github.com/kanekoh/stack-chan

### GitHub Pages を有効にする

リポジトリの Settings から Pages を選択して Github Pages を有効にしてみました。

![GitHubPages_Enabled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155313/bc94e0a3-be04-40fe-97f5-0296c73b9207.png)

### GitHub Actions

ちゃんと見てなくて気付いていませんでしたが、バックエンドでActionsでビルドが走っていたようです。何をしているのか確認してみましょう


## GitHub Actions の調査

https://github.com/kanekoh/stack-chan/tree/dev/v1.0-kaneko/.github/workflows

### build.yml

ここではビルドをしているようです。正常に完了する限りは触らなさそうだと思いましたので放置です。

```
name: Build Stack-chan Firmware
on: [push, pull_request, workflow_dispatch]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup
      - name: Check Format
        run: npm run format
        working-directory: ./firmware
      - name: Lint
        run: npm run lint
        working-directory: ./firmware
      - name: Build
        run: source "$HOME/.local/share/xs-dev-export.sh" && npm run build
        working-directory: ./firmware
      - name: Build CoreS3
        run: source "$HOME/.local/share/xs-dev-export.sh" && npm run build --target=esp32/m5stack_cores3
        working-directory: ./firmware
```

### bundle.yml

ここでビルドの呼び出しのタイミングなどを確認していそうです。
`./firmware/` ディレクトリ以下に差分があれば、ビルドなければビルドをしない様子。
できあがったものを `gh-pages` ブランチに保存して公開しているようです。

gh-pages ブランチを用意しましょう。
また、ブランチのチェック時に本家でのデフォルトのブランチである `dev/1.0` をみています。私はブランチを `dev/1.0-kaneko` と変えたので少々修正が必要そうですね。

```
name: Bundle Stack-chan Firmware
on: 
  push:
    branches:
      - dev/v1.0
  pull_request:
    branches:
      - dev/v1.0
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for changes in firmware
        id: diff_check
        run: |
          git diff --quiet HEAD^ HEAD -- ./firmware/ || echo "diff_detected=true" >> $GITHUB_ENV
      - name: Cache build results
        id: cache
        uses: actions/cache@v4
        with:
          path: ./firmware/stackchan/tech.moddable.stackchan
          key: ${{ github.sha }}
      - name: Setup
        if: ${{ env.diff_detected}} || steps.cache.outputs.cache-hit != 'true'
        uses: ./.github/actions/setup
      - name: Bundle
        if: ${{ env.diff_detected}} || steps.cache.outputs.cache-hit != 'true'
        run: source "$HOME/.local/share/xs-dev-export.sh" && npm run bundle
        working-directory: ./firmware
      - name: Upload Firmware Bundle
        if: ${{ env.diff_detected}} || steps.cache.outputs.cache-hit != 'true'
        uses: actions/upload-artifact@v4
        with:
          name: firmware-bundle
          path: ./firmware/stackchan/tech.moddable.stackchan

  deploy:
    needs: build
    if: github.event_name == 'push' && github.ref == 'refs/heads/dev/v1.0'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Pages Branch
        uses: actions/checkout@v4
        with:
          ref: gh-pages
      - name: Download Firmware Bundle
        uses: actions/download-artifact@v4
        with:
          name: firmware-bundle
          path: ./firmware-bundle
      - name: Move Bundle
        run: |
          mkdir -p ./web/flash/tech.moddable.stackchan
          rm -rf ./web/flash/tech.moddable.stackchan/*
          mv firmware-bundle/* ./web/flash/tech.moddable.stackchan
      - name: Commit and Push
        run: |
          git config --global user.name 'GitHub Action'
          git config --global user.email 'action@github.com'
          git add .
          git commit -m "Deploy firmware bundle from ${{ github.sha }}"
          git push
```

## GitHub Actions のエラー

ブランチを修正してActionsが動くようになったものの、エラーがいくつか出てきて困りました。
遭遇したエラーと、対処を書いていきます。対処の中には適当にChatGPTがいったままを対応しているものもあるので、将来的には見直しが必要そうです。まずはビルドをパスすることから。

### sdkconfig がファイルじゃないエラー

ビルドのActionで発生したものです。

```
Requirement files:
 - /home/runner/.local/share/esp32/esp-idf/tools/requirements/requirements.core.txt
Python being checked: /home/runner/.espressif/python_env/idf5.3_py3.12_env/bin/python
Traceback (most recent call last):
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf.py", line 844, in <module>
    main()
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf.py", line 725, in main
    cli = init_cli(verbose_output=checks_output)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf.py", line 687, in init_cli
    all_actions = merge_action_lists(all_actions, extension.action_extensions(all_actions, project_dir))
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf_py_actions/dfu_ext.py", line 70, in action_extensions
    return dfu_actions if is_target_supported(project_path, SUPPORTED_TARGETS) else {}
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf_py_actions/tools.py", line 721, in is_target_supported
    return get_target(project_path) in supported_targets
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf_py_actions/tools.py", line 103, in get_target
    return get_sdkconfig_value(path, 'CONFIG_IDF_TARGET')
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.local/share/esp32/esp-idf/tools/idf_py_actions/tools.py", line 709, in get_sdkconfig_value
    with open(sdkconfig_file, 'r') as f:
         ^^^^^^^^^^^^^^^^^^^^^^^^^
IsADirectoryError: [Errno 21] Is a directory: '/home/runner/work/stack-chan/stack-chan/firmware/stackchan/sdkconfig'
```

なぜか本家のリポジトリでも同じエラーを見ますが、処理は継続してます。私の場合はエラーで止まります。悲しい。

原因を調べたところ、`npm run build` で読み込む `./firmware/stackchan/manifest.json` に `sdkconfig` がディレクトリであることを期待しているものの、その後の `esp-idf` がsdkconfig をファイルで欲しがっていることが直接原因でした。

正しい対処が分からないので、`manifeset.json` が参照するディレクトリを `sdkconfig.d` と変更し、`esp-idf` が参照するファイルを新たに登録しました。
中身は以下の通りです。（ChatGPTにきいた）`CONFIG_IDF_TARGET` を指定しろというので入れました。ほかのパラメタは `sdkconfig/sdkconfig.default` を持ってきてみました。
```
# Target MCU
CONFIG_IDF_TARGET="esp32s3"

# Flash size
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=n
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
```

### esp-idf のバージョンが期待と合わない

同じくビルドです。

```
rm: cannot remove '/home/runner/.local/share/moddable/build/tmp/esp32/m5stack/debug/stackchan/xsProj-esp32/main/idf_component.yml': No such file or directory
# bles2gatt bleservices
*** Update required to ESP-IDF v5.4 (found v5.3.1)
  See update instructions at: https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/devices/esp32.md
Expected ESP IDF v5.4, found v5.3.1
make: *** [/home/runner/.local/share/moddable/build/tmp/esp32/m5stack/debug/stackchan/makefile:1558: idfVersionCheck] Error 1
make: *** Waiting for unfinished jobs....
Error: Process completed with exit code 2.
```

v5.4 を期待しているのに v5.3.1 だったといわれました。

これは本家にすでにあがっていたので助かりました。

https://github.com/stack-chan/stack-chan/pull/318

ライブラリのバージョンを変更して完了です。

### （未解決）

こちらは`Generate Stack-chan Schematics Files`で発生しましたが何のことなのか、何に必要なのかわかってないので未解決です。

```
Current runner version: '2.323.0'
Operating System
Runner Image
Runner Image Provisioner
GITHUB_TOKEN Permissions
Secret source: Actions
Prepare workflow directory
Prepare all required actions
Getting action download info
Download immutable action package 'actions/checkout@v3'
  Version: 3.6.0
  Digest: sha256:942562b9c7d4b1f2557f1da21f4d3eb4eb4fd01f2ad78b2a6e187d1b193e0a0f
  Source commit SHA: f43a0e5ff2bd294095638e18286ca9a3d1956744
Download action repository 'INTI-CMNB/KiBot@v2_k7' (SHA:8a00b90949bad65e36658d4551820edee9c0f5d6)
Error: Missing download info for actions/upload-artifact@v2
```

## とりあえず

ビルドは成功したので、あとはＧithub Pages をホストするだけだと思います。

GitHub Pages は設定済みなので、以下にアクセスして書き込みを試してみます。

https://kanekoh.github.io/stack-chan/web/flash/

## 結果

書き込みは成功しましたが、スタックチャンは起動しませんでした。
本家の書き込みのサイトから試しても同じ状況なので、なにか潜在的な課題があるのかも知れません。

## 課題

### GitHub Actionのエラー

１つだけ失敗しているジョブがあるので原因を調査する

### スタックチャンが起動しない（こっちのが問題）

高度なことをやりたいわけではないので、例えばバージョンを落として動かないかを見てみるなど。（昨年時点のバージョンにしてみるとか）