diff --git a/README.md b/README.md new file mode 100644 index 0000000..189d966 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +![](https://img.shields.io/github/v/release/tgtakaoka/libcli.svg?maxAge=3600) +![](https://img.shields.io/badge/License-Apache%202.0-blue.svg) +![badge](https://github.com/tgtakaoka/libcli/actions/workflows/ccpp.yml/badge.svg) +![badge](https://github.com/tgtakaoka/libcli/actions/workflows/arduino-ci.yml/badge.svg) +![](https://github.com/tgtakaoka/libcli/actions/workflows/platformio-ci.yml/badge.svg) + +# libcli for Arduino IDE + +Support library to help implementing asynchronous command line +interface. + +Because Arduino API for serial console is mostly synchronous and +blocking, it is not easy to write a sketch which accepts human +interaction from serial console while controlling hardware etc. Of +course we can use `Serial.available()` in `loop()` and carefully use +`Serial.read()` not to block program flow. + +So `libcli` library comes for help. This library offers a Cli instance +as a command line interface which can + + - read a letter, a word string, a line of string, decimal and + hexadecimal number from serial console asynchronously. + + - act as `Stream` and do any form of `print()` and `println()`. + + - print decimal number with left or right aligned in a specified width + format, such as `"%-6d"` or `"%6d"` in `printf()`. + + - print hexadecimal number in specified fixed-width format, such as + `"%08X"` in `printf()`. + + - print string with left or right aligned in a specified width, such + as `"%-8s"` or `"%8s"` in `printf()`. + +Asynchronous read can be implemented with ***request*** and +***callback***. For example, to read a 16-bit hexadecimal, call +`readHex()` ***request*** with a `callback` function pointer, +`UINT16_MAX` as a `limit` of input, and a `context`. When human +finishes input with space or enter key, the `callback` function will +be called with input `value`, the `context` passed in the +***request***, and a input `state`. + +You can find concrete examples at +[libasm](https://github.com/tgtakaoka/libasm/blob/main/src/arduino_example.h) +and +[retro-bionic](https://github.com/tgtakaoka/retro-bionic/blob/main/debugger/debugger.cpp). + +``` C++ +libcli::Cli cli; +using State = libcli::State; + +/** NumberCallback function */ +void handleHex16(uint32_t value, uintptr_t context, State state) { + if (state == State::CLI_SPACE || state == State::CLI_ENTER) { + cli.printHex(value, sizeof(uint16_t) * 2); + } +} + +void begin() { + Serial.begin(9600); + cli.begin(Serial); + cli.print(F("16bit Hex? ")); + cli.readHex(handleHex16, context, UINT16_MAX); +} + +void loop() { + cli.loop(); + /* do other stuff */ +} +``` + +The version 1.3 API has the following functions. + +``` C++ +/** void (*LetterCallback)(char letter, uintptr_t context); */ +using LetterCallback = libcli::LetterCallback; +void readLetter(LetterCallback callback, uintptr_t context); + +/** void (*StringCallback)(char *string, uintptr_t context, State state); */ +using StringCallback = libcli::StringCallback; +void readWord(StringCallback callback, uintptr_t context, char *buffer, size_t size, bool hasDefval = false); +void readLine(StringCallback callback, uintptr_t context, char *buffer, size_t size, bool hasDefval = false); + +/** void (*NumberCallback)(uint32_t number, uintptr_t context, State state); */ +using NumberCallback = libcli::NumberCallback; +void readHex(NumberCallback callback, uintptr_t context, uint32_t limit = UINT32_MAX); +void readHex(NumberCallback callback, uintptr_t context, uint32_t limit, uint32_t defVal); +void readDec(NumberCallback callback, uintptr_t context, uint23_t limit = UINt32_MAX); +void readDec(NumberCallback callback, uintptr_t context, uint23_t limit, uint32_t defVal); + +void printStr(const char *text, int8_t width = 0); +void printStr(const __FlashStringHelper *text, int8_t width = 0); +void printStr_P(const /*PROGMEM*/ char *text_P, int8_t width = 0); +void printHex(uint32_t number, int8_t width = 0); +void printDec(uint32_t number, int8_t width = 0); +void printlnStr(const __FlashStringHelper *text, int8_t width = 0); +void printlnStr_P(const /*PROGMEM*/ char *text_P, int8_t width = 0); +void printlnHex(uint32_t number, int8_t width = 0); +void printlnDec(uint32_t number, int8_t width = 0); +void backspace(int8_t n = 1); +``` + +
+ +More information about this library can be found at +[GitHub](https://github.com/tgtakaoka/libcli) + +
diff --git a/README.adoc b/README_.adoc similarity index 96% rename from README.adoc rename to README_.adoc index f2005fd..52f681c 100644 --- a/README.adoc +++ b/README_.adoc @@ -37,9 +37,9 @@ be called with input `_value_`, the `_context_` passed in the *_request_*, and a input `_state_`. You can find concrete examples at -https://github.com/tgtakaoka/libasm/blob/devel/src/arduino_example.h[libasm] +https://github.com/tgtakaoka/libasm/blob/main/src/arduino_example.h[libasm] and -https://github.com/tgtakaoka/RetroCyborg/blob/main/BionicMC6801/debugger/commands.cpp[RetroCyborg]. +https://github.com/tgtakaoka/retro-bionic/blob/main/debugger/debugger.cpp[retro-bionic]. [source,C++] ---- diff --git a/examples/cli/Makefile b/examples/cli/Makefile index 61874ff..2334662 100644 --- a/examples/cli/Makefile +++ b/examples/cli/Makefile @@ -16,24 +16,27 @@ help: @echo '"make clean" remove unnecessary files' @echo '"make pio" run PlatformIO CI' -PIO_ENVS=$(shell grep -Po '^\[env:\K[^]]+' platformio.ini) -LIB_ENVS=$(PIO_ENVS:%=.pio/libdeps/%) -PIO_BOARDS=$(shell grep -Po '^board *= *\K[\w]+' platformio.ini) +ifdef INSIDE_EMACS +ifeq ($(filter $(PIO_FLAGS),"--no-ansi"),) +PIO_FLAGS += --no-ansi +endif +endif -install_local_lib: - for lib in $(LIB_ENVS); do \ - test -h $${lib}/libcli && continue; \ - mkdir -p $${lib} && rm -rf $${lib}/libcli; \ - ln -s $${PWD}/../.. $${lib}/libcli; \ - done +ENVS=$(shell grep -Po '^\[env:\K[^]]+' platformio.ini) +BOARDS=$(shell grep -Po '^board *= *\K[\w]+' platformio.ini) -pio: install_local_lib - pio --no-ansi run $(PIO_ENVS:%=-e %) +define pio-ci # board + pio $(PIO_FLAGS) ci -l ../.. $(PIO_CI_FLAGS) -b $(1) cli.ino + +endef + +pio: + $(foreach board,$(BOARDS),$(call pio-ci,$(board))) pio-boards: platformio.ini - @echo $(PIO_BOARDS) + @echo $(BOARDS) pio-envs: platformio.ini - @echo $(PIO_ENVS) + @echo $(ENVS) clean: rm -rf $$(find . -type d -a -name .pio) diff --git a/examples/cli/cli.ino b/examples/cli/cli.ino index eee55c6..dc2c60a 100644 --- a/examples/cli/cli.ino +++ b/examples/cli/cli.ino @@ -17,15 +17,15 @@ #include /** Get the singleton instance. */ -libcli::Cli &Cli = libcli::Cli::instance(); -typedef libcli::Cli::State State; +libcli::Cli cli; +using State = libcli::Cli::State; static void handleCommand(char letter, uintptr_t context); /** print prompt and request letter input */ static void prompt() { - Cli.print(F("> ")); - Cli.readLetter(handleCommand, 0); + cli.print(F("> ")); + cli.readLetter(handleCommand, 0); } /** callback for readDec */ @@ -43,26 +43,26 @@ static void handleAddDec(uint32_t value, uintptr_t context, State state) { if (state == State::CLI_DELETE) { if (context == ADD_LEFT) return; - Cli.backspace(); - Cli.readDec(handleAddDec, ADD_LEFT, DEC_LIMIT, last_num); + cli.backspace(); + cli.readDec(handleAddDec, ADD_LEFT, DEC_LIMIT, last_num); return; } if (context == ADD_LEFT) { last_num = value; if (state == State::CLI_SPACE) { - Cli.readDec(handleAddDec, ADD_RIGHT, DEC_LIMIT); + cli.readDec(handleAddDec, ADD_RIGHT, DEC_LIMIT); return; } value = 1; } - Cli.println(); - Cli.print(F("add integers: ")); - Cli.printDec(last_num); - Cli.print(F(" + ")); - Cli.printDec(value); - Cli.print(F(" = ")); - Cli.printlnDec(last_num + value); + cli.println(); + cli.print(F("add integers: ")); + cli.printDec(last_num); + cli.print(F(" + ")); + cli.printDec(value); + cli.print(F(" = ")); + cli.printlnDec(last_num + value); prompt(); } @@ -80,26 +80,26 @@ static void handleAddHex(uint32_t value, uintptr_t context, State state) { if (state == State::CLI_DELETE) { if (context == ADD_LEFT) return; - Cli.backspace(); - Cli.readHex(handleAddHex, ADD_LEFT, HEX_LIMIT, last_num); + cli.backspace(); + cli.readHex(handleAddHex, ADD_LEFT, HEX_LIMIT, last_num); return; } if (context == ADD_LEFT) { last_num = value; if (state == State::CLI_SPACE) { - Cli.readHex(handleAddHex, ADD_RIGHT, HEX_LIMIT); + cli.readHex(handleAddHex, ADD_RIGHT, HEX_LIMIT); return; } value = 1; } - Cli.println(); - Cli.print(F("add hexadecimal: ")); - Cli.printHex(last_num, HEX_WIDTH); - Cli.print(F(" + ")); - Cli.printHex(value, HEX_WIDTH); - Cli.print(F(" = ")); - Cli.printlnHex(last_num + value, HEX_WIDTH); + cli.println(); + cli.print(F("add hexadecimal: ")); + cli.printHex(last_num, HEX_WIDTH); + cli.print(F(" + ")); + cli.printHex(value, HEX_WIDTH); + cli.print(F(" = ")); + cli.printlnHex(last_num + value, HEX_WIDTH); prompt(); } @@ -119,37 +119,37 @@ static void handleDump(uint32_t value, uintptr_t context, State state) { if (state == State::CLI_DELETE) { if (context == DUMP_ADDRESS) return; - Cli.backspace(); - Cli.readHex(handleDump, DUMP_ADDRESS, DUMP_ADDR_LIMIT, last_addr); + cli.backspace(); + cli.readHex(handleDump, DUMP_ADDRESS, DUMP_ADDR_LIMIT, last_addr); return; } if (context == DUMP_ADDRESS) { last_addr = value; if (state == State::CLI_SPACE) { - Cli.readDec(handleDump, DUMP_LENGTH, UINT16_MAX); + cli.readDec(handleDump, DUMP_LENGTH, UINT16_MAX); return; } value = 16; } - Cli.println(); - Cli.print(F("dump memory: ")); - Cli.printHex(last_addr, DUMP_ADDR_WIDTH); - Cli.print(' '); - Cli.printlnDec(value); + cli.println(); + cli.print(F("dump memory: ")); + cli.printHex(last_addr, DUMP_ADDR_WIDTH); + cli.print(' '); + cli.printlnDec(value); const auto end = last_addr + value; for (auto base = last_addr & ~0xF; base < end; base += 16) { - Cli.printHex(base, DUMP_ADDR_WIDTH); - Cli.print(F(": ")); + cli.printHex(base, DUMP_ADDR_WIDTH); + cli.print(F(": ")); for (auto i = 0; i < 16; i++) { const auto a = base + i; if (a < last_addr || a >= end) { - Cli.printStr(F("_"), 4); + cli.printStr(F("_"), 4); } else { - Cli.printDec(static_cast(a), 4); + cli.printDec(static_cast(a), 4); } } - Cli.println(); + cli.println(); } prompt(); } @@ -174,37 +174,37 @@ static void handleMemory(uint32_t value, uintptr_t context, State state) { if (state == State::CLI_DELETE) { if (context == MEMORY_ADDRESS) return; - Cli.backspace(); + cli.backspace(); if (index == 0) { - Cli.readHex(handleMemory, MEMORY_ADDRESS, MEMORY_ADDR_LIMIT, last_addr); + cli.readHex(handleMemory, MEMORY_ADDRESS, MEMORY_ADDR_LIMIT, last_addr); return; } index--; - Cli.readHex(handleMemory, MEMORY_INDEX(index), UINT8_MAX, mem_buffer[index]); + cli.readHex(handleMemory, MEMORY_INDEX(index), UINT8_MAX, mem_buffer[index]); return; } if (context == MEMORY_ADDRESS) { last_addr = value; - Cli.readHex(handleMemory, MEMORY_INDEX(0), UINT8_MAX); + cli.readHex(handleMemory, MEMORY_INDEX(0), UINT8_MAX); return; } mem_buffer[index++] = value; if (state == State::CLI_SPACE) { if (index < sizeof(mem_buffer)) { - Cli.readHex(handleMemory, MEMORY_INDEX(index), UINT8_MAX); + cli.readHex(handleMemory, MEMORY_INDEX(index), UINT8_MAX); return; } } - Cli.println(); - Cli.print(F("write memory: ")); - Cli.printHex(last_addr, MEMORY_ADDR_WIDTH); + cli.println(); + cli.print(F("write memory: ")); + cli.printHex(last_addr, MEMORY_ADDR_WIDTH); for (uint8_t i = 0; i < index; i++) { - Cli.print(' '); - Cli.printHex(mem_buffer[i], 2); + cli.print(' '); + cli.printHex(mem_buffer[i], 2); } - Cli.println(); + cli.println(); prompt(); } @@ -214,10 +214,10 @@ static char str_buffer[40]; static void handleLoad(char *line, uintptr_t context, State state) { (void)context; if (state != State::CLI_CANCEL) { - Cli.println(); - Cli.print(F("load file: '")); - Cli.print(line); - Cli.println('\''); + cli.println(); + cli.print(F("load file: '")); + cli.print(line); + cli.println('\''); } prompt(); } @@ -236,34 +236,34 @@ static void handleWord(char *word, uintptr_t context, State state) { size_t index = context; if (state == State::CLI_DELETE) { if (index != 0) { - Cli.backspace(); + cli.backspace(); index--; strcpy(str_buffer, words[index]); - Cli.readWord(handleWord, index, str_buffer, WORD_LEN, true); + cli.readWord(handleWord, index, str_buffer, WORD_LEN, true); } return; } strncpy(words[index], word, WORD_LEN); words[index][0] = toUpperCase(words[index][0]); - Cli.backspace(strlen(words[index]) + 1); - Cli.print(words[index]); + cli.backspace(strlen(words[index]) + 1); + cli.print(words[index]); index++; if (state == State::CLI_SPACE) { if (index < WORD_MAX) { - Cli.print(' '); - Cli.readWord(handleWord, index, str_buffer, WORD_LEN); + cli.print(' '); + cli.readWord(handleWord, index, str_buffer, WORD_LEN); return; } } - Cli.println(); + cli.println(); for (size_t i = 0; i < index; i++) { - Cli.print(F(" word ")); - Cli.printDec(i + 1); - Cli.print(F(": ")); - Cli.printStr(words[i], -10); - Cli.println(F("!")); + cli.print(F(" word ")); + cli.printDec(i + 1); + cli.print(F(": ")); + cli.printStr(words[i], -10); + cli.println(F("!")); } prompt(); } @@ -271,63 +271,63 @@ static void handleWord(char *word, uintptr_t context, State state) { /** callback for readLetter */ static void handleCommand(char letter, uintptr_t context) { if (letter == 's') { - Cli.print(F("step")); + cli.print(F("step")); } if (letter == 'a') { - Cli.print(F("add decimal ")); - Cli.readDec(handleAddDec, ADD_LEFT, DEC_LIMIT); + cli.print(F("add decimal ")); + cli.readDec(handleAddDec, ADD_LEFT, DEC_LIMIT); return; } if (letter == 'h') { - Cli.print(F("add hexadecimal ")); - Cli.readHex(handleAddHex, ADD_LEFT, HEX_LIMIT); + cli.print(F("add hexadecimal ")); + cli.readHex(handleAddHex, ADD_LEFT, HEX_LIMIT); return; } if (letter == 'l') { - Cli.print(F("load ")); - Cli.readLine(handleLoad, 0, str_buffer, sizeof(str_buffer)); + cli.print(F("load ")); + cli.readLine(handleLoad, 0, str_buffer, sizeof(str_buffer)); return; } if (letter == 'w') { - Cli.print(F("word ")); - Cli.readWord(handleWord, 0, str_buffer, WORD_LEN); + cli.print(F("word ")); + cli.readWord(handleWord, 0, str_buffer, WORD_LEN); return; } if (letter == 'd') { - Cli.print(F("dump ")); - Cli.readHex(handleDump, DUMP_ADDRESS, DUMP_ADDR_LIMIT); + cli.print(F("dump ")); + cli.readHex(handleDump, DUMP_ADDRESS, DUMP_ADDR_LIMIT); return; } if (letter == 'm') { - Cli.print(F("memory ")); - Cli.readHex(handleMemory, MEMORY_ADDRESS, MEMORY_ADDR_LIMIT); + cli.print(F("memory ")); + cli.readHex(handleMemory, MEMORY_ADDRESS, MEMORY_ADDR_LIMIT); return; } if (letter == '?') { - Cli.print(F("libcli (version ")); - Cli.print(LIBCLI_VERSION_STRING); - Cli.println(F(") example")); - Cli.println(F(" ?: help")); - Cli.println(F(" s: step")); - Cli.println(F(" a: add decimal")); - Cli.println(F(" h: add hexadecimal")); - Cli.println(F(" l: load ")); - Cli.println(F(" w: word ...")); - Cli.println(F(" d: dump
")); - Cli.print(F(" m: memory
...")); + cli.print(F("libcli (version ")); + cli.print(LIBCLI_VERSION_STRING); + cli.println(F(") example")); + cli.println(F(" ?: help")); + cli.println(F(" s: step")); + cli.println(F(" a: add decimal")); + cli.println(F(" h: add hexadecimal")); + cli.println(F(" l: load ")); + cli.println(F(" w: word ...")); + cli.println(F(" d: dump
")); + cli.print(F(" m: memory
...")); } - Cli.println(); + cli.println(); prompt(); } void setup() { Serial.begin(9600); - Cli.begin(Serial); + cli.begin(Serial); prompt(); } void loop() { - Cli.loop(); + cli.loop(); } // Local Variables: diff --git a/examples/cli/platformio.ini b/examples/cli/platformio.ini index 90b3f7b..ff25f1c 100644 --- a/examples/cli/platformio.ini +++ b/examples/cli/platformio.ini @@ -20,7 +20,7 @@ default_envs = [env] lib_deps = - libcli@1.2.5 + libcli@1.3.0 [env:promicro16] platform = atmelavr diff --git a/library.json b/library.json index c2f5f95..c1dce7d 100644 --- a/library.json +++ b/library.json @@ -18,6 +18,15 @@ ], "licence": "Apache-2.0", "homepage": "https://github.com/tgtakaoka/libcli/", - "frameworks": "Arduino", - "platforms": "*" + "frameworks": "arduino", + "platforms": "*", + "export": { + "include": [ + "src", + "examples", + "library.json", + "LICENSE.md", + "README.md" + ] + } }