Groovy-IoTをNode.jsから操ってみた：もう少しArduinoっぽいAPI

前回の記事 [Groovy-IoTをNode.jsから操ってみた](https://qiita.com/poruruba/items/9d8358e18805d5540a61) で、Groovy-IoTをNode.jsから扱えるようにしたのですが、Node.js向けのAPIがまだArduinoっぽくなかったので、今回はもう少しArduinoに近づけます。

例えば、
　Serial.println()
とするべきところを、
　Serial_println()
となっていました。これを直します。

これで、Arduinoにより近づいたので、いくつかのGroveのドライバを移植しました。試したのは以下の4つです。すべてマルツ秋葉原本店( https://www.marutsu.co.jp/GoodsListNavi.jsp?path=1100020003 )で購入しました。

* Grove - LED Bar
* Grove - PIR Motion Sensor
* Grove - Digital Light Sensor
* Grove - Light Sensor(P)

（2つLight Sensorがあるのですが、前者がI2C接続、後者がアナログGPIO接続です）
（ちなみに、Grove - Temperature, Humidity, Pressure and Gas Sensor(BME680)も買ったのですが、C言語すぎて移植を中断中）

ソースコードもろもろはGithubに上げてあります。
　https://github.com/poruruba/node-groovy-iot

# ArduinoっぽいAPI

ネイティブ拡張モジュールの実装では、すべてのAPIは横並びでした。
たとえば、Serial_printlnだったりWire_beginTransmissionしました。
一方、Arduinoでは、Serial.println となっていて、「.」で表現しています。これは、世にいうオブジェクト指向のClass/Objectに相当します。
Node.jsにもあるので、横並びのAPIを呼び出すNode.jsのクラスを実装しようと思います。

さっそくソースコードです。

```js:mcp2221lib.js
const mcp2221 = require('mcp2221native');
var mcp2221_initialized = false;

function mcp2221_initialize(){
  var ret = mcp2221.initialize();
  if( ret != 0 )
    throw 'mcp2221.Serial_initialize error';
    
  mcp2221_initialized = true;
}

class Serial{
  initialize(sport){
    if( !mcp2221_initialized )
      mcp2221_initialize();

    var ret = mcp2221.Serial_initialize(sport);
    if( ret != 0 )
      throw 'mcp2221.Serial_initialize error';

    this.DEC = -1;
    this.BIN = -2;
    this.OCT = -3;
    this.HEX = -4;
  }

  begin(speed){
    return mcp2221.Serial_begin(speed);
  }

  end(){
    mcp2221.Serial_end();
  }

  available(){
    return mcp2221.Serial_available();
  }

  read(){
    return mcp2221.Serial_read();
  }

  peek(){
    return mcp2221.Serial_peek();
  }

  flush(){
    mcp2221.Serial_flush();
  }

  write(param, len){
    if( len === undefined)
      return mcp2221.Serial_write(param);
    else
      return mcp2221.Serial_write(param, len);
  }

  write(buf, len){
  }

  convert(data, format){
    var str;

    if( typeof data == 'string'){
      str = data;
    }else{
      if( format >= 0 ){
        str = data.toFixed(format);
      }else{
        switch(format){
          case this.BIN:
            str = data.toString(2);
            break;
          case this.OCT:
            str = data.toString(8);
            break;
          case this.HEX:
            str = data.toString(16);
            break;
          default:
            str = data.toString(10);
            break;
        }
      }
    }

    return str;
  }

  print(data, format){
    var str = this.convert(data, format);
    return mcp2221.Serial_print(str);
  }

  println(data, format){
    var str = this.convert(data, format);
    return mcp2221.Serial_println(str);
  }
}

class GPIO{
  initialize(){
    if( !mcp2221_initialized )
      mcp2221_initialize();

    var ret = mcp2221.GPIO_initialize();
    if( ret != 0 )
      throw 'mcp2221.GPIO_initialize error';

    this.INPUT = 0;
    this.OUTPUT = 1;

    this.LOW = 0;
    this.HIGH = 1;

    this.DEFAULT = 0;
  }

  pinMode(pin, mode){
    mcp2221.GPIO_pinMode(pin, mode);
  }

  digitalWrite(pin, value){
    mcp2221.GPIO_digitalWrite(pin, value);
  }

  digitalRead(pin){
    return mcp2221.GPIO_digitalRead(pin);
  }

  analogRead(pin){
    return mcp2221.GPIO_analogRead(pin);
  }

  analogWrite(pin, value){
    mcp2221.GPIO_analogWrite(pin, value);
  }

  analogReference(type){
    mcp2221.GPIO_analogReference(type);
  }

  analogReadResolution(bits){
    mcp2221.GPIO_analogReadResolution(bits);
  }
  
  analogWriteResolution(bits){
    mcp2221.GPIO_analogWriteResolution(bits);
  }
}

class Wire{
  initialize(){
    if( !mcp2221_initialized )
      mcp2221_initialize();

    var ret = mcp2221.Wire_initialize();
    if( ret != 0 )
      throw 'mcp2221.Wire_initialize error';
  }

  beginTransmission(address){
    mcp2221.Wire_beginTransmission(address);
  }

  endTransmission(){
    mcp2221.Wire_endTransmission();
  }

  requestFrom(address, count){
    return mcp2221.Wire_requestFrom(address, count);
  }

  available(){
    return mcp2221.Wire_available();
  }

  read(){
    return mcp2221.Wire_read();
  }

  write(param, len){
    if( len === undefined )
      return mcp2221.Wire_write(param);
    else
      return mcp2221.Wire_write(param, len);
  }
}

var gpio = new GPIO();

module.exports = {
  initialize : mcp2221_initialize,
  Serial : new Serial(),
  GPIO: gpio,
  Wire: new Wire(),
  pinMode : gpio.pinMode,
  digitalWrite: gpio.digitalWrite,
  digitalRead: gpio.digitalRead,
  analogRead: gpio.analogRead,
  analogWrite: gpio.analogWrite,
  analogReference: gpio.analogReference,
  analogReadResolution: gpio.analogReadResolution,
  analogWriteResolution: gpio.analogWriteResolution
}
```

# 使い方

まずは、先ほど作成したNode.jsのソースコードをrequireします。

```
const { Serial, GPIO, Wire, pinMode, digitalRead, analogRead } = require('./mcp2221lib');
```

ここで、利用したいクラスと、利用したいGPIO関係の関数を指定します。
次に、初期化します。

```
	Serial.initialize(0);
	GPIO.initialize();
	Wire.initialize();
```

Serial.initializeに指定している数字は/dev/ttyACM0 の数字です。複数の仮想COMポートを持ったデバイスをマイコンに接続していると、Groovy-IoTに割り当たる数字が変わってきますので、確認して指定してください。

あとは、Arduinoでよく見るAPI呼び出しができるようになります。Serial.printlnやWire.beginTransactionなど。
以下を参考にさせていただいて真似ました。

Arduino 日本語リファレンス
　http://www.musashinodenpa.com/arduino/ref/

# ドライバの移植

ドライバが必要なのは、以下の2つです。

* Grove - LED Bar
* Grove - Digital Light Sensor

それぞれ、Grove_LED_Bar.js、Digital_Light_TSL2561.js というファイル名で作成しました。
Arduinoにライブラリインストールされたcまたはcpp言語ファイルを移植したものです。
ソースコードは、GitHubを参照してください。

以下の2つは、Digital InputとAnalog Inputですので、ドライバは不要です。

* Grove - PIR Motion Sensor
* Grove - Light Sensor(P)

使い方はこんな感じです。

```js:index.js
const { Serial, GPIO, Wire, pinMode, digitalRead, analogRead } = require('./mcp2221lib');
const ledbar = require('./Grove_LED_Bar');
const TSL2561 = require('./Digital_Light_TSL2561');

async function test(){
	Serial.initialize(0);
	GPIO.initialize();
	Wire.initialize();

/* UART */
/*
	Serial.begin(9600);

	var ret = Serial.println("Hello World");
	console.log(ret);
*/

/* Grove - PIR Motion Sensor */
/*	
	pinMode(2, GPIO.INPUT);
	while(true){
		if( digitalRead(2) )
			console.log("Hi, people is comming");
	}
*/

/* Grove - Digital Light Sensor */
/*
	TSL2561.init();
	console.log(await TSL2561.readVisibleLux());
*/

/* Grove - LED Bar */
/*
	var Grove_LED_Bar = new ledbar(1, 0, 0, 10);
	Grove_LED_Bar.setLevel(3);
*/

/* Grove - Light Sensor(P) */
/*
	console.log( analogRead(2) );
*/

	return;
}

test();
```

ちょっと、補足すると、digitalReadやanalogReadで指定している番号は、PIN番号になります。
どのPIN番号が、Groovy-IoTのどのポートに割り当たっているかは、以下の方のページが非常に参考になります。

　[Groovy-IoT + kikori (2. 標準ハードウェア)](https://qiita.com/ooo_6502/items/e9e13250092bf75495be)

結局、以下の通りです。

・GPIO1(GPIO/ADC/CLKR)
　Pin1：GP0 ★これがPIN番号0
　Pin2：GP1/ADC1/CLKR ★これがPIN番号1

・GPIO2(GPIO/ADC/DAC)
　Pin1：GP2/ADC2/DAC1 ★これがPIN番号2
　Pin2：GP3/ADC3/DAC2 ★これがPIN番号3

digitalRead(2) や analogRead(2)  では2を指定しているので、GPIO2のGrove端子のPin1に対応します。（GPIO1のPin1も使いたかったのですが、DACやADCとしては使えないようです。）

以上