modbus-serialでCO2センサ(Senseair Sunrise)を使う

# 要旨
Node.jsでmodbus-serialを使用し、Raspberry PiからCO2センサにmodbusのシリアル通信を行なった。

# 背景
![D4D1145E-21E0-440D-AB27-3092C7EAAF70.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538096/30d33022-a213-5b03-4f73-bffbb5a0f650.jpeg)

在宅勤務が始まり、換気環境がオフィスより劣る自宅で最高のパフォーマンスを出すためにCO2濃度管理が必要不可欠！
などと論理武装し、一般人が買える最高性能っぽいCO2センサ[Senseair Sunrise](http://senseair.jp/product/oem/9880.html)を購入。
しかし、Raspberry PiのIC2接続で読み出すことに挫折(ArduinoのIC2は成功)。
1年ほど積んでいたCO2センサをUARTにより使用することを試みるのであった。

# 事前準備

ホストにRaspberry Pi Zero Wを用いたので、以下の工程を事前に行っています。

* Raspberry Piのセットアップ
* シリアル通信の有効化
* Node.jsのインストール

# 配線
ホストであるRaspberry PiからSenseair Sunriseに配線しました。
詳細は公式データシートを参照してください。

|No|Sunrise|Raspberry Pi|使用機能|
|-|---|------|--------|
|1|GND|GND P6|グラウンド|
|2|VBB|3.3V P1|センサ電源|
|3|VDDIO|3.3V P1|I/O電源|
|4|RxD/SDA|TXD P8 (GPIO14)|UART receive input|
|5|TxD/SCL|RXD P10 (GPIO15)|UART transmit output|
|6|COMSEL|-|HIGH: UART(floating可)|
|7|nRDY|-|電圧|
|8|DVCC|-|内部電源出力|
|9|EN|3.3V P1|HIGH: Enable(稼働)|

# コード
node moduleの[modbus-serial](https://github.com/yaacov/node-modbus-serial)を使用しています。
インストールは以下のコマンドです。

```bash:modbus-serialのインストール
npm install modbus-serial
```

[modbus-serial](https://github.com/yaacov/node-modbus-serial)のライブラリのexamplesを参考に作成しています。
公式データシート[Modbus on Senseair Sunrise TDE5514]の該当項番を値のコメントに記載しています。
他のセンサを使用される方は設定値の変更が必要です。
正常動作の動作確認以外は行っていないので、参考程度でお願いします。  

``` javascript:printCo2AndTemperature.js
const ModbusRTU = require('modbus-serial')
let client

const serialPort = '/dev/serial0'
const repeatCount = 10
const sleepMilliseconds = 1000

const networkErrors = [
  'ESOCKETTIMEDOUT',
  'ETIMEDOUT',
  'ECONNRESET',
  'ECONNREFUSED',
  'EHOSTUNREACH',
  'ENETRESET',
  'ECONNABORTED',
  'ENETUNREACH',
  'ENOTCONN',
  'ESHUTDOWN',
  'EHOSTDOWN',
  'ENETDOWN',
  'EWOULDBLOCK',
  'EAGAIN'
]

const connect = () => {
  client = new ModbusRTU()
  // 2.1 Modbus settings -> Default slave address
  client.setID(104)
  // 1.2 Bus timing -> Response time-out
  client.setTimeout(180)
}

const reconnect = () => {
  console.warn('reconnect!')
  client.close()
  connect()
}

const run = async () => {
  for (let i = 0; i < repeatCount; i++) {
    try {
      const result = await getCo2AndTemperature()
      console.log(result != null ? result : 'Error!')
      await sleep(sleepMilliseconds)
    } catch (e) {
      console.error(e)
    }
  }
  // 無限ループ
  // setImmediate(() => {
  //   run();
  // })
}

const getCo2AndTemperature = async () => {
  try {
    if (!client.isOpen) {
      // 2.1 Modbus settings
      await client.connectRTUBuffered(serialPort, {
        baudRate: 9600,
        parity: 'none',
        dataBits: 8,
        stopBits: 1
      })
    }
    // Table 1:Input Registers(IR)
    // IR4 0x03 CO2 Value Filtered Pressure Compensated [ppm]
    // IR5 0x04 Temperature [℃×100]
    const response = await client.readInputRegisters(3, 2)
    return {
      co2: response.data[0],
      temperature: response.data[1]
    }
  } catch (e) {
    console.error(e)
    if (e.errno && networkErrors.includes) reconnect()
  }
}

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

const main = async () => {
  connect()
  await run()
  client.close()
}

main()

```

# 終わり
CO2センサの値読み取りに成功したので、次は以下を考えています。

* Cloud DBにCO2濃度をPOST
* ブラウザで閲覧可能なサイト作成