- 概要
- Arduino IDEの変更
- 開発環境の準備
- VSCodeを準備する
- Node.jsを入れる
- 必要なツールのインストール
- Arduino連携用プラグイン追加
- 雛形作成
- What type of extension do you want to create? (Use arrow keys)
- What’s the name of your extension?
- What’s the identifier of your extension?
- What’s the description of your extension?
- Initialize a git repository?
- Bundle the source code with webpack?
- Which package manager to use?
- Do you want to open the new folder with Visual Studio Code?
- フォルダ移動
- README.md編集
- ライセンスファイル作成
- gitリポジトリ追加
- プラグインフォルダー作成
- パッケージのテスト実行
- 転送
- Arduino IDE起動
- パッケージからArduino IDE起動までを自動実行
- Arduino IDEの内部情報取得
- ボタンの追加
- 起動時にボタンを自動追加
- Arduino IDEにログ出力
- コマンド実行
- 完成物
- 完成したソース
- まとめ
概要
昔にerase_flashを実行するだけのプラグインを作成しましたが、Arduino IDEのバージョンが2系にあがり、まったく別の開発環境のなったために使えなくなっています。
そこでArduino IDE2系での拡張機能開発を調査しつつ、ESP32のフラッシュ削除ツールを作ってみました。
Arduino IDEの変更
Arduino IDEはバージョン1系はJavaで作成されており、2系はEclipse Theiaで作成されています。これはVisual Studio Codeと同じ拡張機能が利用できます。
上記に詳細が書いてありますが、ざっくりいうとWindowsの場合には「Ctrl+ Shift+P」でコマンドパレットが開き、任意のコマンドが実行できます。
拡張機能やテーマに関しては「C:\Users\%username%\.arduinoIDE\」以下に「plugins」というフォルダを作成して保存して、Arduino IDEを再起動すると読み込まれます。
開発環境の準備
VSCodeを準備する
VSCode上で開発しますので、環境を準備します。通常の拡張機能に関してはそのままデバッグまでできるのですが、今回はArduino IDE上で動かすのでちょっと面倒です。
Node.jsを入れる
バージョンはなんでもよいと思いますが、私は長期安定版であるLTSを入れました。
必要なツールのインストール
npm install -g yo generator-code
雛形を作成するためにYeomanとgenerator-codeをいれます。
Arduino連携用プラグイン追加
npm install vscode-arduino-api --save
Arduino IDEの内部の情報を取得するパッケージを追加。開発中のコード補完などにも利用されるはず。
雛形作成
yo code
対話型の雛形作成コマンドを実行します。
What type of extension do you want to create? (Use arrow keys)
→ New Extension (TypeScript)
を選択。JavaScriptよりはTypeScriptが一般的なはず。
What’s the name of your extension?
→ arduino-esp32-erase
作成する拡張機能名を入力します。
What’s the identifier of your extension?
→ Enter
通常は拡張機能名と同じはずなのでそのままエンターで決定。
What’s the description of your extension?
→ ESP32 Erase Flash
拡張機能の説明。適当でよいはず。
Initialize a git repository?
→ n
Gitリポジトリの初期化。あとでやるので私はnを選択。
Bundle the source code with webpack?
→ n
たぶん使わないはずなのでn。
Which package manager to use?
→ npm
とりあえずデフォルトのnpm。
Do you want to open the new folder with Visual Studio Code?
→ Skip
VSCodeで開くかはあとで自分で開くのでSkip。そのまま開いても構いません。
フォルダ移動
cd arduino-esp32-erase
とりあえず作成したフォルダに移動します。
README.md編集
README.mdは編集しないとパッケージに失敗するので、適当に編集する。
ライセンスファイル作成
何かしらのライセンスファイルを入れておかないとパッケージの際に確認されるのでとりあえず入れておく。
bitsadmin /TRANSFER download https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt %CD%\LICENSE.txt
空でもよいのでファイルが必要なはず。MITライセンスなどを使うのが無難だと思います。
gitリポジトリ追加
"repository": {
"url": "git+ssh://git@github.com/"
},
package.jsonにrepository項目を追加する。何か書いていないとパッケージの際に確認されるのでとりあえず埋めておく。
プラグインフォルダー作成
mkdir %USERPROFILE%\.arduinoIDE\plugins
Windowsの場合には上記の場所にフォルダを作成する。
mkdir ~/.arduinoIDE/plugins
LinuxとmacOSの場合には上記のはず。ただし、今回はWindows決め打ちでコマンドを実行しています。
パッケージのテスト実行
npx vsce package
とりあえずビルドしてパッケージが成功するか確認します。途中でYかNかを聞かれた場合にはYを答えることでビルドは進みます。ライセンスとgitリポジトリが未設定だと確認が入るはずです。README.mdを未編集の場合には問答無用で失敗します。
転送
copy /y *.vsix %USERPROFILE%\.arduinoIDE\plugins\
パッケージ化された.vsixをpluginsフォルダにコピーします。
Arduino IDE起動

「Ctrl+ Shift+P」でコマンドパレットを開き、「Hello World」コマンドを実行します。
上記のように右下にインフォメーションが表示されれば成功です。
パッケージからArduino IDE起動までを自動実行
call npx vsce package
copy /y *.vsix %USERPROFILE%\.arduinoIDE\plugins\
"%USERPROFILE%\AppData\Local\Programs\Arduino IDE\Arduino IDE.exe"
上記の内容をバッチファイルの保存して実行することでパッケージ、コピー、Arduino IDEの起動ができるはずです。私は「run.bat」という名前で作成をしました。
コマンドプロンプトからArduino IDEを立ち上げることでconsole.logが取得できます。手で自動するとログが出力されないのでかなりデバッグが面倒ですので、コマンドラインからの実行をおすすめします。
Arduino IDEの内部情報取得
上記のプラグインを利用するのですが、なにかおかしいです。GitHubの説明を確認していきます。
インポート
import type { ArduinoContext } from 'vscode-arduino-api';
上記でインポートします。ここは問題ありません。
拡張取得
export function activate(context: vscode.ExtensionContext) {
const context: ArduinoContext = vscode.extensions.getExtension(
'dankeboy36.vscode-arduino-api'
)?.exports;
if (!context) {
// Failed to load the Arduino API.
return;
}
vscode.extensionsから、このツールを読み込みます。ここでcontextという名前が多重宣言されているのでエラーになります。
export function activate(context: vscode.ExtensionContext) {
const arduinoContext: ArduinoContext = vscode.extensions.getExtension(
'dankeboy36.vscode-arduino-api'
)?.exports;
if (!arduinoContext) {
return;
}
上記のようにarduinoContextと別の名前をつけてあげましょう。
内部情報取得
console.log('arduinoContext? ' + JSON.stringify(arduinoContext));
上記のように先程取得したarduinoContextから情報を取得できます。
取得できる情報は上記にドキュメントがありました。
arduinoContext?.sketchPath
スケッチがあるpathになります。しかしながらDeprecatedの非推奨のため、「arduinoContext?.currentSketch?.sketchPath」を取得しろとあります。
ただし、currentSketchが私のArduino IDE2.2.1だと取得できませんでした。今後のバージョンアップで取得できるようになる可能性がありますが、DeprecatedのarduinoContext.sketchPathを現在は利用したほうが良さそうです。
arduinoContext?.port?.address
シリアルポートを取得します。こちらもDeprecatedで「arduinoContext?.currentSketch?.port」を取得しろとありますが現在は取得できませんでした。
あとは起動した直後では常に未定義のundefinedで取得できません。一度他のシリアルポートに変更して戻すなどの処理をしないとだめでした。もしくはボードを変更ですね。こちらもバグのような気がしますので今後修正を期待したいと思います。
arduinoContext?.boardDetails
ボード定義を取得します。こちらもDeprecatedで「arduinoContext?.currentSketch?.boardDetails」以下略。
ここの情報は非常に大量にあるので注意してください。
私の環境で取得したものを参考のため上記に貼り付けます。この中から必要そうなものを抜き出します。
"tools.esptool_py.path": "C:\\Users\\tanaka\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\esptool_py\\4.5.1",
"tools.esptool_py.cmd.linux": "esptool.py",
"tools.esptool_py.cmd": "esptool.exe",
eraseコマンドはesptoolを実行しますので、pathを調べる必要があります。上記からわかりました。今回はWindows決め打ちなのでtools.esptool_py.pathとtools.esptool_py.cmdを使います。
- arduinoContext?.boardDetails.buildProperties[‘tools.esptool_py.path’]
- arduinoContext?.boardDetails?.buildProperties[‘tools.esptool_py.cmd’]
上記で取得可能です。本当はOSを取得してコマンドを書き換える必要があります。
"tools.esptool_py.erase.protocol": "serial",
"tools.esptool_py.erase.params.verbose": "",
"tools.esptool_py.erase.params.quiet": "",
"tools.esptool_py.erase.pattern_args": "--chip esp32s3 --port \"{serial.port}\" --baud 921600 --before default_reset --after hard_reset erase_flash",
"tools.esptool_py.erase.pattern": "\"{path}/{cmd}\" {erase.pattern_args}",
"tools.esptool_py.erase.pattern.linux": "python3 \"{path}/{cmd}\" {erase.pattern_args}",
あとは上記に細かいコマンドがありますがポートだけ指定すれば動くのでこの文字列は利用しません。本当はこの文字列を使ったほうが好ましいです。
ボタンの追加
const button = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
200
);
button.command = 'arduino-esp32-erase.erase';
button.text = '$(trash) ESP32 Erase';
context.subscriptions.push(button);
button.show();
コマンドパレットからの実行は面倒なので、ステータスバーにボタンを追加したいと思います。createStatusBarItemで右から優先度200で追加しています。優先度が大きいほど左側に位置します。

上記にアイコンの一覧がありますので$(trash)でゴミ箱のアイコンを表示しています。

これでステータスバーにボタンが追加されると嬉しいのですが、追加されないはずです。activate関数でボタンを追加しているのですが、デフォルトはコマンドパレットから呼び出した初回のみ追加されます。コマンドパレット一度呼び出すとボタンが追加されるはずです。
起動時にボタンを自動追加
"activationEvents": [
"onStartupFinished"
],
package.jsonのactivationEventsを書き換えます。アクティベーションをonStartupFinishedで実行します。

上記に説明がありますが、起動が終わったときにactivate関数が呼ばれるようになるはずです。これで起動するとボタンが増えると思います。
Arduino IDEにログ出力
const writeEmitter = new vscode.EventEmitter<string>();
let writerReady: boolean = false;
上記でEventEmitterを利用することで、Arduino IDEのターミナルを開くことができます。
上記の処理を参考にさせてもらいました。Raspberry Pi Pico RP2040とESP8266のファイル転送用拡張機能なのですが、何故かESP32には対応していない。。。対応していたらこのブログ書かなかったのに。。。
function makeTerminal(title: string) {
let w = vscode.window.terminals.find((w) => ((w.name === title) && (w.exitStatus === undefined)));
if (w !== undefined) {
w.show(false);
return;
}
const pty = {
onDidWrite: writeEmitter.event,
open: () => { writerReady = true; },
close: () => { writerReady = false; },
handleInput: () => { }
};
const terminal = (<any>vscode.window).createTerminal({ name: title, pty });
terminal.show();
}
こんな感じでターミナルを開く処理をしていました。
makeTerminal("ESP32 erase flash");
let cnt = 0;
while (!writerReady) {
if (cnt++ >= 50) {
vscode.window.showErrorMessage("Unable to open upload terminal");
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
上記のように開き終わるまで待機していないとだめでした。
writeEmitter.fire(str + '\r\n');
以降はfireで出力可能です。ただし改行コードが\r\nなので注意してください。
function log(str: string) {
writeEmitter.fire(str + '\r\n');
console.log(str);
}
私は上記のようにconsole.logとターミナルの両方に出力するようにしました。
コマンド実行
if (arduinoContext?.boardDetails) {
let esptool = String(arduinoContext?.boardDetails.buildProperties['tools.esptool_py.path'] + '/' + arduinoContext?.boardDetails?.buildProperties['tools.esptool_py.cmd']);
log(esptool);
let opts = ['--port', arduinoContext?.port?.address, 'erase_flash'];
log(JSON.stringify(opts));
let exitCode = await runCommand(esptool, opts);
if (exitCode) {
// Error
vscode.window.showInformationMessage('Failed to connect to Espressif device');
} else {
vscode.window.showInformationMessage('Chip erase completed successfully');
}
} else {
log('Please reselect the port');
vscode.window.showInformationMessage('Please reselect the port');
}
上記の処理になります。
if (arduinoContext?.boardDetails) {
上記で正しくボードが選択されているか確認しています。ここがセットされていない場合にはポートかボードを選択しなおす必要があります。
let opts = ['--port', arduinoContext?.port?.address, 'erase_flash'];
コマンドのオプションは上記で設定しています。ポート番号だけ指定してあとはデフォルトになります。
let exitCode = await runCommand(esptool, opts);
if (exitCode) {
// Error
vscode.window.showInformationMessage('Failed to connect to Espressif device');
} else {
vscode.window.showInformationMessage('Chip erase completed successfully');
}
実行は上記で、arduino-littlefs-uploadの関数を参考にして使わせてもらっています。
async function runCommand(exe: string, opts: any[]) {
const cmd = spawn(exe, opts);
for await (const chunk of cmd.stdout) {
log(String(chunk));
}
for await (const chunk of cmd.stderr) {
log(String(chunk));
}
let exitCode = await new Promise((resolve, reject) => {
cmd.on('close', resolve);
});
return exitCode;
}
こんな感じでspawnで実行して、標準出力と標準エラーをログ出力しつつ、実行完了まで待機する関数です。
完成物

上記のように下にあるボタンを押すとターミナルが開き、eraseを実行する拡張機能を作ることができました。
完成したソース
上記においてありますので参考にしてみてください。package.jsonとsrc/extension.tsしか編集する必要はないはずです。
まとめ
本当はmkspiffsとmklittlefsを使ってdataフォルダにあるファイルをESP32のフラッシュに転送するツールを作りたいのですが、いろいろ面倒なことがありそうなので単純なErase Flashを作ってみました。
いろいろ考えるとWi-Fi接続してWebDAVアクセスできるプログラムを転送してコピーとか、URLからファイルをダウンロードしてきてフラッシュに保存するだけのプログラムを実行したほうがもしかしたら楽かもしれません。
dataフォルダ転送のためのメモ
mkspiffsのコマンドは上記に説明がありますが、パーティション情報が必要になります。
- arduinoContext?.compileSummary?.buildPath
コンパイルを一度でもすると上記が取得できます。この中にpartitions.csvがあるので中身をパースすれば取得できるはずです。
arduino-littlefs-uploadをみてみたら「arduinoContext?.boardDetails?.configOptions」を探していましたがESP32だとここにSPIFFSなどの情報はないようです。コンパイル時に不要な情報だもんね。
なのでcsvをパースして、オフセットとサイズを取得して、spiffsかlittlefsは判定難しいのでダイアログか設定ファイルから取得しないとだけかもしれません。あとFatFsもサポート追加されていますがmkfatfsはまだ提供されていないようです。。。
いろいろ考えるとPlatformIOを使うほうが簡単そうですね。
ちなみにPlatformIOでFatFSをアップロードするときには上記のツールを使っているようでした。
コメント