IoT何をいまさら(115) ESP32C3版Xiaoで車輪の再発明的な「行入力」

Joseph Halfmoon

Arduino IDEにて複数 .ino ファイルの落とし穴に転落?から復活し、ESP32C3版Xiaoでの作業を再開しました。調べてみると既存ライブラリが複数あるというのに、UART経由でのコンソール的なものを自力作成するつもりです。車輪の再発明的な無駄な努力かと。今回はreadlineモドキから。

※「IoT何をいまさら」投稿順Indexはこちら

ローカルエコーでないreadlineが欲しいノダ

まさにArduino IDE内蔵のシリアルモニタがそうだと思うのです。マイコン側のシリアルポートから送られてくるデータは次々と画面に現れますが、シリアルポートに向けて送り出す文字列は「所定の場所で」PC側で行編集後RETURNを押すとまとめて送られる仕組みです。まあこれでもいいっちゃいいのですが、いわゆるシリアル端末(例えばTeraterm)相手に入力された1文字づつ解釈して行える最低限の行編集機能を持たせたいです。理想をいえば、ほぼほぼ毎日Linuxでお世話になっているGnu readline(readlineについての記事はこちら)的なもの。流石にあれほど高機能なものを実装するとマイコンのメモリを食いつぶしてしまいそうなので、モドキですが。

今回作成したblockingReadLine関数は、以下のようなちんまりしたものです。

    1. 7ビットアスキー文字のみ対応。
    2. 所定のバッファ内に0終端の文字列としてASCII文字を格納。
    3. LFもしくはCRコードで入力終了。trueを返す。LF、CRは文字列に含まない。
    4. BSキーでカーソルの左の文字をバッファからも画面からも消去できる
    5. CTRL+Aキーでバッファを全クリアして左端から再入力(ただし現バージョンは端末に表示されている行はのこったまま)
    6. CTRL+Cキーで入力中断して戻る。このときはfalseを返す。

そのソース .ino 形式が以下に。

char blgetASCII(int opt) {
  int incomingByte = 0;
  char selchar = 0;
  while (selchar == 0) {
    if (Serial.available() > 0) {
      incomingByte = Serial.read();
      if((incomingByte > 0) && (incomingByte < 128)) {
        selchar = (char)incomingByte;
        if ((opt & 0x1) && (isLowerCase(selchar))) {
          selchar -= 32;
        }
      }
    }
  }
  if (opt & 0x2) {     
    if ((selchar < 0x20) || (selchar > 126)) {
      Serial.print("0x");
      Serial.print(selchar, HEX);
    } else {
      Serial.print(selchar);
    }
  }
  return selchar;
}

char blockingMenu(char *buf) {
  Serial.print(buf);
  char selchar = blgetASCII(0x3);
  Serial.println("");
  return selchar;
}

bool endOfLineChar(char arg, const char* term, const int termax) {
  for (int i=0; i<termax; i++) {
    if (term[i] == arg) {
      return true;
    }
  }
  return false;
}

bool blockingReadLine(char *buf, const int maxBuf) {
  char bufIndex = 0;
  char selchar = 0;
  const int tcMax = 3;
  const char term[tcMax] = {0x3, 0xA, 0xD};
  while (!endOfLineChar(selchar, term, tcMax)) {
    selchar = blgetASCII(0);
    if (selchar == 0x08) { //BS key
      if (bufIndex > 0) {
        bufIndex--;
        Serial.print(selchar);
        Serial.print(' ');
      } else {
        selchar = 0;
      }
    } else if (selchar == 0x1) { //CTRL+A
      Serial.print('\r');
      selchar = 0;
      bufIndex= 0;
    } else if (bufIndex < (maxBuf+1)) {
      buf[bufIndex++] = selchar;
    }
    if (selchar != 0) {
      Serial.print(selchar);
    }
  }
  buf[bufIndex] = '\0';
  if (selchar == 0x3) {
    return false;
  }
  return true;
}
上記をテストするためのメインプログラム

上記のテストは以下です。最初、メニューを表示し、cと打つと、コマンド入力モード(いまのところ実体は上記のblockingReadLineのテスト用)になり、行入力が可能となります。上述のルールで行入力すると入力した文字列を表示して終わるか、CTRL+Cで終わるかします。

なお、Qで抜けるとSerialポートを手放すので、電源いれたまま再書き換えするときに便利です。

#define BUFMAX  (60)

void setup(){
    Serial.begin(9600);
    //その他のsetup
}


void loop(){
  bool menuEnable = true;
  int choice = 0;
  char buf[BUFMAX];
  char menuStr[] = "MENU> C(md D(rive F(ile Q(uit ";
  while (menuEnable) {
    choice = blockingMenu(menuStr);
    switch (choice) {
      case 'C':
        if (blockingReadLine(buf, BUFMAX)) {
          Serial.print("Input Str: ");
          Serial.println(buf);
        } else {
          Serial.println("<CTRL>+C");
        }
        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);
  }
  Serial.end();
}
ビルドして実行

通信相手は、ローカルエコーをせず、VT100互換のESCシーケンスをサポートしている端末ソフトを想定しています。

先頭にMENU>とあるのが頭文字一文字で選択できるメニューです。ゆくゆくはメニューの表示は画面の上端に固定をする予定(TurboPascal風というか、もっと古くはUCSD Pascal風、古代の偉大な処理系へのオマージュ?)

Cうつと1行下にカーソルが移動するのでここで任意の文字列を入力します。ただし、編集機能は実質BSキーで後ろから削るか、CTRL+CかCTRL+Aで最初からやり直すしかないのであまり面白くないです。readlineTest

このくらいの入力ができれば EDLIN(初期のMS-DOS「標準添付」のラインエディタ。ラインエディタなど使ったことどころか見たことない子ばかりかと)もどきは作れそう?

IoT何をいまさら(114) ESP32C3版XiaoにmicroSDカード接続 へ戻る

IoT何をいまさら(116) ESP32C3版Xiaoで車輪の再発明、行入力その2 へ進む