Usage

#include <uuid/console.h>

Create a std::shared_ptr<uuid::console::Commands> and populate it with the commands to be available on the shell.

Create a std::shared_ptr<uuid::console::Shell> referencing the Serial stream and the commands list. Call start() on the instance and then uuid::console::Shell::loop_all() regularly. (The static set of all shells will retain a copy of the shared_ptr until the shell is stopped.)

Example (Digital I/O)

#include <Arduino.h>

#include <memory>
#include <string>
#include <vector>

#include <uuid/common.h>
#include <uuid/console.h>

using uuid::read_flash_string;
using uuid::flash_string_vector;
using uuid::console::Commands;
using uuid::console::Shell;

void setup() {
	std::shared_ptr<Commands> commands = std::make_shared<Commands>();

	commands->add_command(flash_string_vector{F("pinMode")},
		flash_string_vector{F("<pin>"), F("<mode>")},

		[] (Shell &shell, const std::vector<std::string> &arguments) {
			uint8_t pin = String(arguments[0].c_str()).toInt();
			uint8_t mode;

			if (arguments[1] == read_flash_string(F("INPUT"))) {
				mode = INPUT;
			} else if (arguments[1] == read_flash_string(F("OUTPUT"))) {
				mode = OUTPUT;
			} else if (arguments[1] == read_flash_string(F("INPUT_PULLUP"))) {
				mode = INPUT_PULLUP;
			} else {
				shell.println(F("Invalid mode"));
				return;
			}

			pinMode(pin, mode);
			shell.printfln(F("Configured pin %u to mode %s"),
					pin, arguments[1].c_str());
		},

		[] (Shell &shell, const std::vector<std::string> &current_arguments,
				const std::string &next_argument)
				-> const std::vector<std::string> {
			if (current_arguments.size() == 1) {
				/* The first argument has been provided, so return
				 * completion values for the second argument.
				 */
				return {
					read_flash_string(F("INPUT")),
					read_flash_string(F("OUTPUT")),
					read_flash_string(F("INPUT_PULLUP"))
				};
			} else {
				return {};
			}
		}
	);

	commands->add_command(flash_string_vector{F("digitalRead")},
		flash_string_vector{F("<pin>")},

		[] (Shell &shell, const std::vector<std::string> &arguments) {
			uint8_t pin = String(arguments[0].c_str()).toInt();
			auto value = digitalRead(pin);

			shell.printfln(F("Read value from pin %u: %S"),
					pin, value == HIGH ? F("HIGH") : F("LOW"));
		}
	);

	commands->add_command(flash_string_vector{F("digitalWrite")},
		flash_string_vector{F("<pin>"), F("<value>")},

		[] (Shell &shell, const std::vector<std::string> &arguments) {
			uint8_t pin = String(arguments[0].c_str()).toInt();
			uint8_t value;

			if (arguments[1] == read_flash_string(F("HIGH"))) {
				value = HIGH;
			} else if (arguments[1] == read_flash_string(F("LOW"))) {
				value = LOW;
			} else {
				shell.println(F("Invalid value"));
				return;
			}

			digitalWrite(pin, value);
			shell.printfln(F("Wrote %s value to pin %u"),
					arguments[1].c_str(), pin);
		},

		[] (Shell &shell, const std::vector<std::string> &current_arguments,
				const std::string &next_argument)
				-> const std::vector<std::string> {
			if (current_arguments.size() == 1) {
				/* The first argument has been provided, so return
				 * completion values for the second argument.
				 */
				return {
					read_flash_string(F("HIGH")),
					read_flash_string(F("LOW"))
				};
			} else {
				return {};
			}
		}
	);

	commands->add_command(flash_string_vector{F("help")},
		[] (Shell &shell, const std::vector<std::string> &arguments) {
			shell.print_all_available_commands();
		}
	);

	Serial.begin(115200);

	std::shared_ptr<Shell> shell;
	shell = std::make_shared<uuid::console::Shell>(Serial, commands);
	shell->start();
}

void loop() {
	uuid::loop();
	Shell::loop_all();
	yield();
}

Output

$ help
pinMode <pin> <mode>
digitalRead <pin>
digitalWrite <pin> <value>
help
$ digitalRead 2
Read value from pin 2: LOW
$ digitalRead 3
Read value from pin 3: HIGH
$ pinMode 4 OUTPUT
Configured pin 4 to mode OUTPUT
$ digitalWrite 4 HIGH
Wrote HIGH value to pin 4
$ pinMode 5 OUTPUT
Configured pin 5 to mode OUTPUT
$ digitalWrite 5 LOW
Wrote LOW value to pin 5
$ 

Example (WiFi network scan)

#ifdef ARDUINO_ARCH_ESP8266
# include <ESP8266WiFi.h>
#else
# include <WiFi.h>
#endif

#include <memory>
#include <string>
#include <vector>

#include <uuid/common.h>
#include <uuid/console.h>

using uuid::read_flash_string;
using uuid::flash_string_vector;
using uuid::console::Commands;
using uuid::console::Shell;

void setup() {
	std::shared_ptr<Commands> commands = std::make_shared<Commands>();

	commands->add_command(flash_string_vector{F("wifi"), F("scan")},
		[] (Shell &shell, const std::vector<std::string> &arguments) {

			int8_t ret = WiFi.scanNetworks(true);
			if (ret == WIFI_SCAN_RUNNING) {
				shell.println(F("Scanning for WiFi networks..."));

				/* This function will be called repeatedly on every
				 * loop until it returns true. It can be used to
				 * wait for the outcome of asynchronous operations
				 * without blocking execution of the main loop.
				 */
				shell.block_with([] (Shell &shell, bool stop) -> bool {
					int8_t ret = WiFi.scanComplete();

					if (ret == WIFI_SCAN_RUNNING) {
						/* Keep running until the scan completes
						 * or the shell is stopped.
						 */
						return stop;
					} else if (ret == WIFI_SCAN_FAILED || ret < 0) {
						shell.println(F("WiFi scan failed"));
						return true; /* stop running */
					} else {
						shell.printfln(F("Found %u networks"), ret);
						shell.println();

						for (uint8_t i = 0; i < (uint8_t)ret; i++) {
							shell.printfln(F("%s (%d dBm)"),
									WiFi.SSID(i).c_str(),
									WiFi.RSSI(i));
						}

						WiFi.scanDelete();
						return true; /* stop running */
					}
				});
			} else {
				shell.println(F("WiFi scan failed"));
			}
		}
	);

	Serial.begin(115200);

	std::shared_ptr<Shell> shell;
	shell = std::make_shared<uuid::console::Shell>(Serial, commands);
	shell->start();
}

void loop() {
	uuid::loop();
	Shell::loop_all();
	yield();
}

Output

$ wifi scan
Scanning for WiFi networks...
Found 3 networks

Free Public WiFi (-87 dBm)
Hacklab (-30 dBm)
ALL YOUR BASE ARE BELONG TO US (-44 dBm)
$