概要
pingのサンプルコードをみていたら、対話型コンソールを実現するesp_consoleを利用していて、使いやすそうだったので紹介したいと思います。
Consoleの概要
上記に公式ドキュメントがあります。コマンドラインなどで利用できるようにカーソルやバックスペースなどのラインエディタの機能と、GNUっぽいコマンド引数を解析する部分があり好きに組み合わせて利用できるみたいです。
Arduinoでのスケッチ例
#include "esp_console.h"
#include "esp_event.h"
#include "argtable3/argtable3.h"
#include <WiFi.h>
static esp_console_repl_t *s_repl = NULL;
void setup() {
esp_console_cmd_t command = {};
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
#if CONFIG_ESP_CONSOLE_UART
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_CDC
esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
esp_console_dev_usb_serial_jtag_config_t usbjtag_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&usbjtag_config, &repl_config, &repl));
#endif
// reset
command = {};
command.command = "reset";
command.help = "Resetting ESP32";
command.func = [](int argc, char **argv) -> int {
ESP.restart();
return 0;
};
esp_console_cmd_register(&command);
// wifi
static struct {
struct arg_str *ssid = arg_str0(NULL, NULL, "<ssid>", "SSID");
struct arg_str *key = arg_str0(NULL, NULL, "<key>", "KEY");
struct arg_end *end = arg_end(1);
} wifi_args;
command = {};
command.argtable = &wifi_args;
command.command = "wifi";
command.help = "connect to wifi";
command.func = [](int argc, char **argv) -> int {
int nerrors = arg_parse(argc, argv, (void **)&wifi_args);
if (wifi_args.ssid->count != 0) {
printf("SSID = %s\n", wifi_args.ssid->sval[0]);
printf("KEY = %s\n", wifi_args.key->sval[0]);
WiFi.begin(wifi_args.ssid->sval[0], wifi_args.key->sval[0]);
} else {
printf("SSID = [last ssid]\n");
printf("KEY = [last key]\n");
WiFi.begin();
}
return 0;
};
esp_console_cmd_register(&command);
// ip
command = {};
command.command = "ip";
command.help = "IP Address";
command.func = [](int argc, char **argv) -> int {
printf("IP Address : %s\n", WiFi.localIP().toString().c_str());
return 0;
};
esp_console_cmd_register(&command);
// time
command = {};
command.command = "time";
command.help = "local time";
command.func = [](int argc, char **argv) -> int {
struct tm timeInfo;
if (getLocalTime(&timeInfo)) {
printf("Local Time : ");
printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
}
return 0;
};
esp_console_cmd_register(&command);
// ntp
static struct {
struct arg_int *tz = arg_int0("tz", "tz,timezone", "<tz>", "TIME ZONE(-9)");
struct arg_str *host = arg_str0(NULL, NULL, "<host>", "NTP Server");
struct arg_end *end = arg_end(1);
} ntp_args;
command = {};
command.argtable = &ntp_args;
command.command = "ntp";
command.help = "set ntp";
command.func = [](int argc, char **argv) -> int {
int nerrors = arg_parse(argc, argv, (void **)&ntp_args);
printf("ntp_args.tz->count = %d\n", ntp_args.tz->count);
printf("ntp_args.host->count = %d\n", ntp_args.host->count);
int tz = 9;
if (ntp_args.tz->count != 0) {
tz = (uint32_t)(ntp_args.tz->ival[0]);
}
printf("timezone = %d\n", tz);
if (ntp_args.host->count != 0) {
printf("host = %s\n", ntp_args.host->sval[0]);
configTime(tz * 60 * 60, 0, ntp_args.host->sval[0]);
} else {
printf("host = ntp.jst.mfeed.ad.jp, ntp.nict.jp, time.google.com\n");
configTime(tz * 60 * 60, 0, "ntp.jst.mfeed.ad.jp", "ntp.nict.jp", "time.google.com");
}
return 0;
};
esp_console_cmd_register(&command);
// start console REPL
ESP_ERROR_CHECK(esp_console_start_repl(s_repl));
}
void loop() {
delay(1);
}
かなりシンプルにしたスケッチ例になります。
esp_console_cmd_t command = {};
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
#if CONFIG_ESP_CONSOLE_UART
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_CDC
esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &s_repl));
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
esp_console_dev_usb_serial_jtag_config_t usbjtag_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&usbjtag_config, &repl_config, &repl));
まず、ESP-IDFの機能なのでSerialクラスが利用できません。そのため呪文のようなコードはそのまま利用する必要がありました。Serialクラスで初期化するとバッティングするのでSerialは呼び出さないようにしてください。
// reset
command = {};
command.command = "reset";
command.help = "Resetting ESP32";
command.func = [](int argc, char **argv) -> int {
ESP.restart();
return 0;
};
esp_console_cmd_register(&command);
上記がコマンドを登録する最小部分です。commandでコマンド名を設定して、helpにコマンドの概要を登録します。funcにコマンドの内容を定義して、esp_console_cmd_registerで登録します。
これでresetコマンドを呼び出すことができます。この仕組みで便利なのがhelpコマンドを打つことで呼び出すことができるコマンドの一覧と引数の情報も自動生成されることです。
// wifi
static struct {
struct arg_str *ssid = arg_str0(NULL, NULL, "<ssid>", "SSID");
struct arg_str *key = arg_str0(NULL, NULL, "<key>", "KEY");
struct arg_end *end = arg_end(1);
} wifi_args;
command = {};
command.argtable = &wifi_args;
command.command = "wifi";
command.help = "connect to wifi";
command.func = [](int argc, char **argv) -> int {
int nerrors = arg_parse(argc, argv, (void **)&wifi_args);
if (wifi_args.ssid->count != 0) {
printf("SSID = %s\n", wifi_args.ssid->sval[0]);
printf("KEY = %s\n", wifi_args.key->sval[0]);
WiFi.begin(wifi_args.ssid->sval[0], wifi_args.key->sval[0]);
} else {
printf("SSID = [last ssid]\n");
printf("KEY = [last key]\n");
WiFi.begin();
}
return 0;
};
esp_console_cmd_register(&command);
つぎにコマンドライン引数がある例になります。「wifi <ssid> <key>」でwifiに接続するコマンドになります。
static struct {
struct arg_str *ssid = arg_str0(NULL, NULL, "<ssid>", "SSID");
struct arg_str *key = arg_str0(NULL, NULL, "<key>", "KEY");
struct arg_end *end = arg_end(1);
} wifi_args;
上記がコマンドライン引数の定義です。細かいところはあとで説明しますが、文字列を2つ受け取っています。最後のarg_endを設定しないとハングアップするので注意してください。
int nerrors = arg_parse(argc, argv, (void **)&wifi_args);
if (wifi_args.ssid->count != 0) {
printf("SSID = %s\n", wifi_args.ssid->sval[0]);
printf("KEY = %s\n", wifi_args.key->sval[0]);
WiFi.begin(wifi_args.ssid->sval[0], wifi_args.key->sval[0]);
} else {
printf("SSID = [last ssid]\n");
printf("KEY = [last key]\n");
WiFi.begin();
}
引数を取得するのはarg_parseを呼び出すだけです。ただし注意しないといけないのは複数のパラメータが指定される可能性があるので、まずは何個のパラメータがあるかをチェックして、個数分の処理を行う必要があります。「command -i 1 -i 2 -i 3」たとえばこのようなコマンドだと-iのパラメータが3つ設定されて呼び出されます。
引数の指定方法について
上記が内部で利用しているargtableの公式サイトだと思われますが、わかりにくいです。
上記のmanページが比較的わかりやすかったです。
引数の種類
種類 | 備考 |
---|---|
arg_lit | リテラル(オプションのみで引数はなし) |
arg_int | 数値 |
arg_dbl | 浮動小数点(REAL/DOUBLE) |
arg_str | 文字列 |
arg_rex | 正規表現にマッチした文字列 |
arg_file | pathを含むファイル名の文字列 |
arg_date | 日付/時刻 |
arg_rem | コメント |
arg_end | 引数の最後を表す |
ちょっとわかりにくいのですが、arg_lit(リテラル)は-bとかオプションのみで引数を取らないものになります。ほかはなんとかわかるかな?
// ntp
static struct {
struct arg_int *tz = arg_int0("tz", "tz,timezone", "<tz>", "TIME ZONE(-9)");
struct arg_str *host = arg_str0(NULL, NULL, "<host>", "NTP Server");
struct arg_end *end = arg_end(1);
} ntp_args;
上記はntpの時刻合わせコマンドですが、intでタイムゾーンを受け取っていますが本来は一時間未満のタイムゾーンもありますのでintで受け取る場合は秒単位で受け取るか、arg_dblかarg_dateで受け取るのが正しいです。
引数のパラメータ
// ntp
static struct {
struct arg_int *tz = arg_int0("tz", "tz,timezone", "<tz>", "TIME ZONE(-9)");
struct arg_str *host = arg_str0(NULL, NULL, "<host>", "NTP Server");
struct arg_end *end = arg_end(1);
} ntp_args;
command = {};
command.argtable = &ntp_args;
command.command = "ntp";
command.help = "set ntp";
再びntpコマンドですが、上記の設定はhelpでは以下の表示となります。
ntp [-t <tz>] [<host>]
set ntp
-t, -z, --tz, --timezone=<tz> TIME ZONE(-9)
<host> NTP Server
1つ目の引数は-tで<tz>、2つ目の引数は<host>を表しています。タイムゾーンでは「”tz”, “tz,timezone”」と指定していますが、それが下の方にある「-t, -z, –tz, –timezone=<tz>」を表しています。”tz”は1文字での省略形になります。-tzではなく、-tと-zに対応しています。”tz,timezone”は省略していないオプション名になります。複数ある場合にはカンマで区切ります。–tzと–timezoneが指定されています。
パラメータの数について
arg_int0 | 指定されないことがあるオプション |
arg_int1 | 必ず1つ指定されることを前提とするオプション |
arg_intn | 複数個指定される可能性があるオプション |
数値型のarg_intでもさらに3つオプションの指定があります。
static struct {
struct arg_int *i0 = arg_int0("i", NULL, "<i>", "i");
struct arg_int *i1 = arg_int1("j", NULL, "<j>", "j");
struct arg_int *in = arg_intn("k", NULL, "<k>", 2, 4, "k");
struct arg_end *end = arg_end(1);
} i_args;
上記の指定をした場合にはhelp出力は以下になります。
test [-i <i>] -j <j> -k <k> -k <k> [-k <k>] [-k <k>]
test
-i <i> i
-j <j> j
-k <k> k
上記のような出力になります。-iはarg_int0ですので、省略可能なオプションのため[-i <i>]とカッコで囲われて出力されています。-jはarg_int1で1つだけ指定しますので-j <j>と表示されています。-kはarg_intnで複数指定できますので複数個表示されています。ただし、arg_intn(“k”, NULL, “<k>”, 2, 4, “k”)と2と4を指定していますので、最低2個指定されて最大4個までのオプションになるようです。
まとめ
esp_consoleはいろいろできそうなのですが、結構癖があります。DOSっぽいシステムを作って、SDカードなどにプログラムを入れておいて、LovyanLauncherなどのようにプログラムを切り替えて実行するシェル部分に使い勝手が良さそうです。
ESP-IDFには複数のサンプルがあるので中をみてみるのもいいと思います。上記はI2Cを対話型で利用するためのツールになります。
コメント