Arduinoでテンプレートライブラリを使って、シリアル通信で動くシェルを作る



せっかくC++で書けるのだから、ちょっと大きめのスケッチやライブラリを作っていると、やはりSTL(Standard Template Library)が使いたくなりますよね？
そんな人のためのSTLのAVRマイコンへの移植版が、この

[The STL for AVR with C++ streams](http://andybrown.me.uk/wk/2011/01/15/the-standard-template-library-stl-for-avr-with-c-streams/)

です。
C++の標準ライブラリの元にもなった[SG	I STL](https://www.sgi.com/tech/stl/)をベースに移植を行ったらしく、SGI STLにはストリームクラスが無いのでわざわざ[uClibc++](http://cxx.uclibc.org/)から取ってきたり、更にはArduinoユーザーのためにシリアル入出力ストリームクラスや、LCD出力クラスなども用意してくれる親切っぷり。
これを使わない手はありません。



## 1. インストール

トップページからDownloads -> Arduino downloads と進んだ先にあります。2015/5/25時点での最新版は1.1.1です。
開発環境がeclipseなら、適当な場所に解凍して`avr-stl/include`をインクルードパスに追加しましょう。
Arduino IDEの場合は、ヘッダファイルを丸ごと`${arduinoのインストール先}/hardware/tools/avr/avr/include`にぶちこめばOKです。



## 2. シェルクラスの作成
Arduino向けのシリアル入出力ストリームクラスを使って、ホストとシリアル通信しながら動く簡単なシェルを作ってみます。
プロンプトを表示し、入力されたコマンドをパースして引数のリストを取得できるようにします。


### 2-1. 前準備
シェルクラスを書く前に、ちょこっとこのAVR-STLに書き足します。
Arduinoでシリアル通信で入力を行う時は、`HardwareSerial`クラスのインスタンスである`Serial`を使ってこのように書くと思います。

```c++:HardwareSerialクラスを使う場合

void loop() {
    char c;
    if (Serial.available() > 0) {
        c = Serial.read();
        Serial.print("input: ");
        Serial.println(c);
    }
}
```

実は、`serstream`に定義されている入力シリアルストリームクラス`ihserialstream`には、この`Serial.available()`に相当するメンバ関数がありません。これだと、「何か入力があった時だけ処理する」ということができず大変不便なので、以下のように追記してやります。


```c++:serstream
/*
 * Input stream
 */

	template <class charT, class traits, class Tserial> class basic_iserialstream
		: public basic_istream<charT,traits>
	{
	public:
	
	~~~ここから~~~
	
	/*
	 * get how many characters available
	 */
		int available() {
			return sb.serial().available();
		}
	
	~~~ここまで~~~
	
	}
```


`ihserialstream`が内部で保持している`HardwareSerial`のインスタンスから`available()`を呼ぶようにしました。
これを用いて先ほどのコードを書き換えると、こうなります。


```c++:入出力ストリームクラスを使う場合

// 定義済みのHardwareSerialインスタンスのSerialを使って初期化
std::ohserialstream serout(Serial);
std::ihserialstream serin(Serial);

void loop() {

    char c;
    if (serin.available() > 0) {
    	serin >> c;
    	serout << "input: " << c << std::crlf;
    }
}

```


### 2-2. SerialShellクラス

SerialShellクラスの中身はこんな感じです。

* キーボード入力を受け付け、リターンが押されるまでの文字列を保持
* 空白文字で分割し、引数として配列に格納

が主な動作になります。

```c++:SerialShell.h
#ifndef SERIAL_SHELL_
#define SERIAL_SHELL_

class SerialShell {
private:
	std::ohserialstream& serout;	// 出力用ストリームの参照
	std::ihserialstream& serin;		// 入力用ストリームの参照
	std::string buffer;
	std::vector<std::string> args;
	bool is_prompting;

public:
	SerialShell(std::ohserialstream* serout, std::ihserialstream* serin);
	virtual ~SerialShell();

	int readCommand();
	std::vector<std::string> getArgs();
	void printArgs();

private:
	void parseCommand();
};

#endif /* SERIAL_SHELL_ */
```


```c++:SerialShell.cpp
#include <Arduino.h>
#include <serstream>
#include <iterator>
#include <vector>
#include <string>
#include "SerialShell.h"


SerialShell::SerialShell(std::ohserialstream* serout, std::ihserialstream* serin)
	: serout(*serout),
	  serin(*serin),
	  buffer(""),
	  args(0),
	  is_prompting(false) {

}

SerialShell::~SerialShell() {
}

// コマンドの引数を表示する
void SerialShell::printArgs()
{
    serout << args.size() << " args: ";
    for (std::vector<std::string>::iterator it = args.begin() ; it  < args.end() ; ++it) {
        serout << *it;
        if (it < args.end() - 1) {
        	serout << ", ";
        }
    }
    serout << std::crlf;
}

// コマンドの引数のリストを返す
std::vector<std::string> SerialShell::getArgs()
{
	return args;
}

// コマンドを読み込む
int SerialShell::readCommand() {

	char c = 0;

	// プロンプトの表示
    if (! is_prompting) {
    	serout << "# ";
    	is_prompting = true;
    }

    if (serin.available() > 0) {
    	serin.get(c);
		if (c == '\r') {
			// CRをNLで置き換えて、ヌル文字を追加
			buffer += "\n\0";
			// コマンドのパース
			parseCommand();
			// バッファをクリア
			buffer = "";
			// エコーバック
			serout << std::crlf;
			is_prompting = false;
			return 1;
		}
		else {
			// バッファに追加
			buffer += c;
			// エコーバック
			serout << c;
			return 0;
		}
    }
    else {
    	return -1;
    }
}

// コマンドをパースして引数をvectorに格納する
void SerialShell::parseCommand() {

    unsigned int start =0, end = 0;

    args.clear();
    while (end < buffer.length()) {
        start = buffer.find_first_not_of(" \t\n", end);
        end = buffer.find_first_of(" \t\n", start);
        if (end-start > 0) {
            args.push_back(buffer.substr(start, end-start));
        }
    }
}
```

## 3. 使い方

使い方はこんな感じです。
`readCommand()`の返り値を見て0より大きかったら、`getArgs()`で引数のリストが得られます。

```c++:SerialShellTest.cpp
#include <Arduino.h>
#include <pnew.cpp>
#include <serstream>
#include <iterator>
#include <vector>
#include "SerialShell.h"
#include "SerialShellTest.h"

// 定義済みのHardwareSerialインスタンスのSerialを使って初期化
std::ohserialstream serout(Serial);
std::ihserialstream serin(Serial);

SerialShell* shell;

void setup()
{
	Serial.begin(9600);
	// 入出力シリアルストリームクラスの参照で初期化
	shell = new SerialShell(&serout, &serin);
}

void loop()
{
    int ret;
    std::vector<std::string> args;  // コマンド引数のリスト

    // コマンド読み取り
    ret = shell->readCommand();
    // ret　>　0 なら有効データあり
    if (ret > 0) {
    	// 引数のリストを表示
        shell->printArgs();
        // 引数のリストをGet
        args = shell->getArgs();
        // 第1引数で処理分岐
        if (args[0] == "hoge") {
            serout << "func hoge" << std::crlf;
        }
        else if (args[0] == "piyo") {
            serout << "func piyo" << std::crlf;
        }
    }
}

```


## 4. 実行結果

```
$ sudo cu -l /dev/cu.usbmodem1411  -s 9600
Connected.
# ai ue   o
3 args: ai, ue, o
# The quick brown fox jumps over the lazy dog
9 args: The, quick, brown, fox, jumps, over, the, lazy, dog
# 
0 args:
```

入力したコマンドの引数が表示されます。

```
# hoge
1 args: hoge
func hoge
# piyo 123 
2 args: piyo, 123
func piyo
```

処理分岐もできていますね。


## 5. 所感

うーんやっぱりSTLは便利ですね！```Serial.println()```だと文字列と変数を出力したい時に

```
Serial.print("The value is: ");
Serial.println(value);
```

みたいに複数行書かなきゃいけませんが、ストリームクラスのおかげですっきり書けました。
```std::crlf```はAVR-STLの独自定義で、キャリッジリターン(CR)とラインフィード(LF)を表しています。
vector, stringクラスも特に問題なく普通に使えます。

GitHubの方にソースコードをあげときましたので、よろしければどうぞ。
→[arduino-serial-shell](https://github.com/kota65535/arduino-serial-shell)

### 動作環境
* mac OS X Yosemite 10.10.3
* Arduino IDE 1.0.5
* Arduino eclipse extensionsArduino plugin 1.2.4
* eclipse LUNA 4.4.2