前回までで、多少「行編集」っぽい入力ができるようになったので、今回はメニューの見た目を改良してみたいと思います。やっぱりメニューはコンソール画面の一番上に固定したいです。ついでにちょいと目立つようにお化粧もしたい。また、エラーとかステータスとかの出力は画面の下の方に別表示としたいです。古き良き時代をリスペクト?
※「IoT何をいまさら」投稿順Indexはこちら
制御コード、エスケープシーケンス、CSIシーケンス
この年寄が若いころ、シリアル端末の業界標準、VT100やVT220の現物を使ってました。VT100(ブイティーハンドレッド)は白黒画面で結構デカかった記憶。それに比べるとVT220(ブイティートゥートゥエニイ)はコンパクトで、アンバーイエローのディスプレイだった記憶。いずれにせよ、80文字x24行くらいの画面で作業をしてました。
VT100やVT220の現物は消えても、それが起源の制御コード、エスケープシーケンスは現代の仮想端末ソフトの中にしっかりいきのこってます。今回使用させてもらっている定番ソフト Teraterm Proもそのようなソフトウエアの一つです。
前回までは画面上で編集をするのに、0x01から0x1Fまでの1バイトの制御コード、および、0x1Bに続いて1バイトのコードを送る2バイトのエスケープシーケンスを使ってました。
今回からはエスケープシーケンスの中でもCSIと呼ばれるらしい制御シーケンスを使っていきます。エスケープ(0x1B)の後に ‘[‘ が来て、その後ダラダラと制御パラメータが続き、最後に制御コマンド文字で〆るもの。文字に色をつけたり非常に自由度が高い制御ができるコードです。オリジナルのVT100は白黒2色の端末だったので、色などはどこかで拡張された?のだと思いますが、制御コードの素人にはその歴史は分からず。
今回追加した機能とそのソース
今回追加するのは以下のような機能です。とりあえず画面はVT100式の80文字x24行を想定してます。
-
- メニュー行を画面の最上位行に表示する
- メニューと他の入出力行の違いを一目瞭然とするために、メニューはリバース表示とする
- 何か処理した結果や、エラーなどは画面の下の方(24行目)に表示する
- 上記の出力も他との違いを目立たせるためにリバースとする
そこで、以下のようなCSIシーケンスを仮想端末に送る関数を追加するとともに、メニューを表示するblockingMenu()関数に若干の変更を加えました。こんな感じ。
void CSIseq(const char arg[]) { Serial.print((char)0x1B); Serial.print('['); for (int i=0; i<strlen(arg); i++) { Serial.print(arg[i]); } } void scrHome() { CSIseq("1;1H"); } void setRev() { CSIseq("07m"); } void setNorm() { CSIseq("0m"); } void eraseLine() { CSIseq("2K"); } void dispStat() { saveCursor(); CSIseq("23;1H"); eraseLine(); setRev(); } void dispMain() { restoreCursor(); setNorm(); } void dispSPC(int arg) { for (int i=0; i<arg; i++) { Serial.print(' '); } } char blockingMenu(char *buf) { scrHome(); setRev(); eraseLine(); Serial.print(buf); dispSPC(76-strlen(buf)); setNorm(); char selchar = blgetASCII(0x3); Serial.println(""); return selchar; }
また、ステータス/エラー行の表示に対応させるため、menuを呼び出しているメインループ側も若干修正してます。
void loop(){ bool menuEnable = true; int choice = 0; char buf[BUFMAX]; bool temp; char menuStr[] = "MENU> C(md D(rive F(ile Q(uit "; while (menuEnable) { choice = blockingMenu(menuStr); eraseLine(); switch (choice) { case 'C': temp = blockingReadLine(buf, BUFMAX); dispStat(); if (temp) { Serial.print("Input Str: "); Serial.println(buf); } else { Serial.println("<CTRL>+C"); } dispMain(); break; case 'F': Serial.println("File"); break; case 'D': Serial.println("Drive"); break; case 'Q': Serial.println("Quit"); menuEnable = false; break; default: Serial.println("Unknown."); break; } delay(200); digitalWrite(led, HIGH); delay(200); digitalWrite(led, LOW); } Serial.end(); }
実機上の実行結果
Arduino IDE(今回から2.0.4を使わせていただいております)からビルドし、ESP32C3 Xiao に書き込んで動作させ、Teraterm Proで接続した画面が以下に。
一番上のMENU>とある行がメニュー行です。そこでCと一文字打てば、下の行で行入力ルーチンが動作。1行入力してEnterキーを押せば下の方に反転したInput Str: 入力した文字列、が出力されます。
だんだん雰囲気でてきた?先は長いな。