Arduino と PC でシリアルポート通信する

概要
====

Raspberry Pi に似ているが、Raspberry Pi と違って頭脳が無い。
ゆえにむしろ機械制御に集中できる。
デモを作る場合に PC に繋いで制御したい場合に非常に扱いやすい。

PCとシリアルポートの双方向通信ができるので、それで制御するのが良い。


必須インストール/入手
===============

- [Arduino IDE](https://www.arduino.cc/en/main/software)
- Arduino 本体 ＋ 必要なセンサー類


実装例
======

下記の実装で次の動きを実現する。

- PCからLEDのON/OFFを送る。
- Arduinoからタッチセンサーの情報を送る

```C:Arduino
const int LED = 13;
const int TOUCH = 2;

int receiveByte = 0;
String bufferStr = "";
String STR_ON = "ON";
String STR_OFF = "OFF";

void setup() {
  pinMode(LED, OUTPUT); // LED（出力）
  pinMode(TOUCH , INPUT); // タッチセンサー（入力）
  Serial.begin(9600); // シリアルポート通信開始
}

void loop() {
  // PCからの受信
  bufferStr = "";
  while (Serial.available() > 0) {
    receiveByte = Serial.read();
    if (receiveByte == (int)'\n') break;
    bufferStr.concat((char)receiveByte);
  }

  // 受け取ったデータがあるならスイッチ操作
  if (bufferStr.length() > 0) {
    if (STR_ON.compareTo(bufferStr) == 0) {
      digitalWrite(LED, HIGH);
    } else {
      digitalWrite(LED, LOW);
    }
  }

  // タッチセンサーが反応したらPCに送る
  int touchValue = digitalRead(TOUCH);
  if (touchValue == HIGH){
    Serial.println("T");
  }

  // wait
  delay(500);
}
```

```js:PC側(Arduino.js)
const SerialPort = require('serialport');

class Arduino {
    /**
     * @param {object} config
     * @param {string} config.serialPort
     * @param {int} config.baudRate
     * @param {int} config.waitMsecAfterOpen
     */
    constructor(config) {
        this.config = config;
        this.port = null;
        this._onSerialList = [];
    }

    /**
     * 非同期でArduinoを立ち上げる。他のタスクを妨げない。立ち上がってなければなにもしない。
     */
    initArduino() {
        return new Promise((resolve) => {
            this.port = new SerialPort(this.config.serialPort, {
                baudRate: this.config.baudRate
            });
            this.port.on('open', () => {
                // すぐだとシリアル通信受け付けないので一定時間遅らせる。
                setTimeout(() => resolve(), this.config.waitMsecAfterOpen);
            });
            const parser = this.port.pipe(new SerialPort.parsers.Readline({delimiter: '\n'}));
            parser.on('data', (data) => {
                data = data.replace(/\r/, '');
                this._onData(data);
            });
        });
    }

    _onData(data) {
        for (let onSerial of this._onSerialList) {
            let matches = data.match(onSerial.condReg);
            if (matches) {
                onSerial.callback(...matches);
                return;
            }
        }
        if (data.match(/^\[log]/)) return;
        throw new Error(`未知のデータ: ${data}`);
    }

    /**
     * @callback Arduino_onSerial
     * @param {string} data
     */

    /**
     * @param {Object.<string, Arduino_onSerial>} callbacks
     */
    onSerial(callbacks) {
        for (let cond of Object.keys(callbacks)) {
            let condReg = new RegExp('^' +  cond.replace(/\*/g, '.*?') + '$');
            this._onSerialList.push({condReg, callback: callbacks[cond]});
        }
    }

    sendToArduino(data) {
        this.port.write(new Buffer(data + "\n"), (err, results) => {
            if(err) {
                throw new Error('Err: ' + err);
            }
        });
    }
}

module.exports = Arduino;
```

```js:PC側(main.js)
const Arduino = require('./Arduino');
const arduino = new Arduino({serialPort: 'COM5', baudRate: 9600, waitMsecAfterOpen: 2000});
arduino.onSerial({
    if (data === 'T') {
        console.log('タッチ');
    }
});

// 5秒後にLEDをON
setTimeout(() => {
    arduino.sendToArduino('ON');
}, 5000);
// 10秒後にLEDをOFF
setTimeout(() => {
    arduino.sendToArduino('OFF');
}, 10000);

(async function () {
    await lib.Arduino.initArduino();
})();
```