IoT ハッカソン Wio LTE デバイス環境 設定手順

# はじめに

本コンテンツは、IoT ハッカソンで使用する技術ハンズオンの資料です。
環境は参加者が自由選択できますので、参考としてご活用ください。

## 概要

SORACOM Air サービスを使用して、センサーから取得して、クラウドにデータを転送するまでの手順を説明します。

## 目次

<!-- TOC -->

- [IoT ハッカソン Wio LTE デバイス環境 設定手順](#iot-ハッカソン-wio-lte-デバイス環境-設定手順)
  - [はじめに](#はじめに)
  - [概要](#概要)
  - [目次](#目次)
  - [準備](#準備)
    - [WioLTE ハンズオンページ](#wiolte-ハンズオンページ)
    - [公式の Wiki](#公式の-wiki)
      - [外部ライブラリ導入](#外部ライブラリ導入)
    - [プログラム](#プログラム)
    - [解説](#解説)
      - [初期化(setup)](#初期化setup)
      - [定期処理(loop)](#定期処理loop)
      - [送信処理(sendData)](#送信処理senddata)
      - [文字列へ変換(JsonStringify)](#文字列へ変換jsonstringify)
  - [トラブルシュート](#トラブルシュート)
    - [D38 ポート以外がうまく機能しない](#d38-ポート以外がうまく機能しない)
  - [参考](#参考)
    - [公式 Wiki](#公式-wiki)
  - [各種センサの値取得方法](#各種センサの値取得方法)

<!-- /TOC -->

## 準備

Wio LTE for Arduino 公式の Wiki ページ、SORACOM の [WioLTE ハンズオンページ](https://github.com/soracom/handson/wiki/Wio-LTE-%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3)を参考にインストールを進めて行きます。

### WioLTE ハンズオンページ

以下のページより、"開発環境の準備" 〜 "ステップ 3: 温湿度センサーのデータを SORACOM Harvest で可視化"までの手順を実施してください。

[https://github.com/soracom/handson/wiki/Wio-LTE-%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3](https://github.com/soracom/handson/wiki/Wio-LTE-%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3)

### 公式の Wiki

デバイスのハード仕様、"Wio LTE for Arduino"ライブラリに関する情報を参照する場合は、以下の Seeed の Wiki ページを参照してください。

[https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html)

#### 外部ライブラリ導入

温度センサーを取得するために、Arduino IDE に Seeed 社(Grove センサ製造元)が公開しているライブラリを導入します。

- [https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor](https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor)

### プログラム

クラウドにセンシングした値を送信するためのスケッチ(コード)を書いていきます。

```Cpp:temperature.ino
#include <WioLTEforArduino.h>
#include <DHT.h>            // https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor
#include <stdio.h>

// データ送信先選択
#define TARGET_SORACOM_BEAM       0
#define TARGET_SORACOM_FUNNEL     1
#define TARGET_SORACOM_HARVEST    2
#define USE_TARGET                TARGET_SORACOM_FUNNEL

#if USE_TARGET == TARGET_SORACOM_BEAM
  // Beam(UDP)を使う場合
  constexpr char *TARGET_HOST = "beam.soracom.io";
  constexpr uint16_t TARGET_PORT = 23080;
#elif USE_TARGET == TARGET_SORACOM_FUNNEL
  // Funnelを使う場合
  constexpr char *TARGET_HOST = "funnel.soracom.io";
  constexpr uint16_t TARGET_PORT = 23080;
#elif USE_TARGET == TARGET_SORACOM_HARVEST
  constexpr char *TARGET_HOST = "harvest.soracom.io";
  constexpr uint16_t TARGET_PORT = 8514;
#else
# error "not selected use protocol."
#endif

//
// 送信データ定義
//
struct SendPacket
{
  float temp;

  // 送信データをJSON文字列へ変換
  String JsonStringify() const
  {
    String result("");
    char buf[32];

    sprintf(buf, "%d", static_cast<int>(this->temp));
    result += "{\"tmpr_c\":";
    result += buf;
    result += "}";

    return result;
  }
};

//
// グローバル変数
//
WioLTE g_wio;
DHT g_temp_hum(WIOLTE_D38, DHT11);

//
// 初期化処理
//
void setup()
{
  delay(200);

  SerialUSB.println("start: I/O Initialize.");
  g_wio.Init();

  SerialUSB.println("start: Power ON.");
  g_wio.PowerSupplyLTE(true);

  SerialUSB.println("start: Module Initialize.");
  if(!g_wio.TurnOnOrReset()){
    SerialUSB.println("error:  Module init failed.");
    return;
  }

  SerialUSB.println("start: APN activate.");
  if(!g_wio.Activate("soracom.io", "sora", "sora", 3000)){
    SerialUSB.println("error: APN activate failed.");
  }

  g_wio.PowerSupplyGrove(true);
  delay(500);
  g_temp_hum.begin();

  SerialUSB.println("setup OK.");

  SerialUSB.print("target host: ");
  SerialUSB.println(TARGET_HOST);
}

//
// 定期処理
//
void loop()
{
  SendPacket data;

  data.temp = g_temp_hum.readTemperature(); // 温度データ取得
  SerialUSB.print("temp:"); SerialUSB.println(data.temp);

  sendData(data);

  delay(5000);
}

//
// データ送信処理
//
void sendData(const SendPacket &packet)
{
  int connectId;
  connectId = g_wio.SocketOpen(TARGET_HOST, TARGET_PORT, WIOLTE_UDP);
  if (connectId < 0) {
    SerialUSB.println("error: cannot connect.");
    return;
  }

  String data = packet.JsonStringify();
  SerialUSB.print("Send:");
  SerialUSB.print(data);
  SerialUSB.println("");
  if (!g_wio.SocketSend(connectId, data.c_str())) {
    SerialUSB.println("error: cannot send data.");
  }

  if (!g_wio.SocketClose(connectId)) {
    SerialUSB.println("error: cannot close socket.");
  }
}
```

### 解説

プログラムは大きく分けて 4 つに別れています。

- 初期化(setup) : LTE 回線への接続、Grove センサの初期化
- 定期処理(loop) : 温度線センサーから温度値を読み出し、送信処理(sendData)を呼び出す
- 送信処理(sendData) : SORACOM へ接続し、温度データを文字列に変換(JsonStringify)して、SORACOM との接続を切断
- 文字列へ変換(JsonStringify) : float 型からクラウドでデータ処理しやすい JSON 形式の文字列へ変換

#### 初期化(setup)
<!-- prettier-ignore-start -->

WioLTE 本体の処理を呼び出すための変数をグローバル変数で準備します。

```Cpp
WioLTE g_wio;
```

温度センサーを使用するために、D38 ポートへ接続する定義と、センサーの種類(今回資料する温度センサーは DHT11 という型)を割り当てをしています。

```Cpp
DHT g_temp_hum(WIOLTE_D38, DHT11);
```

上記の割り当てた内容を`setup`関数内で初期化します。

1. はじめに、`Init`を呼び出して WioLTE 全体の初期化を行います。

    ```Cpp
    g_wio.Init();
    ```

2. 次に LTE の機能を使用するための、`PowerSupplyLTE`で電源供給を ON し、LTE モジュールの起動操作`TurnOnOrReset`を呼び出します。
   `PowerSupplyLTE` → `TurnOnOrReset`順番で呼び出さないと正しく初期化できないので注意してください。

    ```Cpp
    g_wio.PowerSupplyLTE(true);

    SerialUSB.println("start: Module Initialize.");
    if(!g_wio.TurnOnOrReset()){
      SerialUSB.println("error:  Module init failed.");
      return;
    }
    ```

3. データ通信を有効にするため`Activate`でアクティベーションします。
   SIM カードが刺さっていなかったり、圏外などで接続できない場合が 3 秒(3000 ミリ秒)で処理を中断するようにしています。
   (何も指定しない場合、120 秒間(2 分)のタイムアウト時間となります)

    ```Cpp
    SerialUSB.println("start: APN activate.");
      if(!g_wio.Activate("soracom.io", "sora", "sora", 3000)){
      SerialUSB.println("error: APN activate failed.");
    }
    ```

4. Grove センサーへの電源供給を ON(`g_wio.PowerSupplyGrove`) して、`g_temp_hum.begin()`で温度センサーの初期化処理をしています。

    ```Cpp
      g_wio.PowerSupplyGrove(true);
      delay(500);
      g_temp_hum.begin();
    ```

#### 定期処理(loop)

定期処理では、読み出しと送信処理を呼び出しています。

1. `readTemperature`で温度センサーから値を読み出し、`data.temp`に値を格納します。

    ```Cpp
    data.temp = g_temp_hum.readTemperature();
    ```

2. `sendData`でスケッチ(コード)で準備した送信用の関数呼び出しをします。

    ```Cpp
    sendData(data);
    ```

3. 最後に 5000 ミリ秒(5 秒)待機して終了します。
   なお、時間を短くすることは可能ですが、
   温度センサーの DHT11 のサンプリング間隔が 2 秒以上の仕様のため、正しい値が取得できなくなってしまいます。

    ```Cpp
    delay(5000);
    ```

#### 送信処理(sendData)

送信処理では、接続 → データ変換 → 送信 → 切断の順番で処理をします。

1. 接続のために`SocketOpen`を呼び出しをしています。
   接続先のホスト名、ポート番号はそれぞれ`TARGET_HOST`、`TARGET_PORT`で定義しており、
   Beam/Funnel/Harvest を以下の定義を変更することで、切り替えができるようにしています。

    ```Cpp
    // SORACOM Beamに対して接続する場合
    #define USE_TARGET              SORACOM_PROTOCOL_BEAM
    ```

    ```Cpp
    // SORACOM Funnelに対してへ接続する場合
    #define USE_TARGET              TARGET_SORACOM_FUNNEL
    ```

    ```Cpp
    // SORACOM Harvestに対してへ接続する場合
    #define USE_TARGET              TARGET_SORACOM_HARVEST
    ```

2. データ変換では、`JsonStringify`を呼び出して、文字列へ変換しています。

3. 送信データでは、送信先で SocketOpen で取得した"connectId"指定と、
   `c_str()`で String 型から char \*型のデータを取得して送信します。

    ```Cpp
    if (!g_wio.SocketSend(connectId, data.c_str())) {
      SerialUSB.println("error: cannot send data.");
    }
    ```

4. 最後に送信が終わったら`SocketClose`で切断します。

<!-- prettier-ignore-end -->
#### 文字列へ変換(JsonStringify)

取得するデータは単精度浮動小数点(float 型)ですが、DHT11 のセンサー精度は分解能 1℃ で小数点以下を取れないため、
`sprintf`整数(int 型)に変換した上で、文字列に変換しています。

また、最終的に JSON 形式に変換する必要があるため、`"{<key>:<value>}"`の形式にしていきます。
以下に 25℃ が取れた場合の変換過程を右側にコメントをつけて説明します。

```Cpp
    sprintf(buf, "%d", static_cast<int>(this->temp));   // 25.0 → "25"    浮動小数点から整数へ変換しbufへ格納
    result += "{\"tmpr_c\":";                           // {"tmpr_c":     resultに「{」括弧開きとキー「tmpr_c」を追加
    result += buf;                                      // {"tmpr_c":25   resultに「25」の文字列を追加
    result += "}";                                      // {"tmpr_c":25}  resultに「}」括弧閉じを追加
```

## クラウド環境の準備

WioLTEへスケッチを書き込んだら、クラウド環境の設定を行なっていきます。
クラウドの環境は[IoT ハッカソン クラウド環境 設定手順](https://qiita.com/uhuru-hackathon/items/dde4df4b0f4be21fc4da)を参照してください。

## トラブルシュート

### D38 ポート以外がうまく機能しない

起動直後は、D38 以外は OFF になるため、有効にする必要があります。
[PowerSupplyGrove](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Reference-ja.html#powersupplygrove)を setup()関数内で呼び出すことで解決します。

```Cpp
WioLTE wio;

void setup()
{
  wio.Init();                 // Wio LTEを初期化
  wio.PowerSupplyGrove(true); // Groveコネクタの電源供給ON
}
```

## 参考

### 公式 Wiki

デバイスのハード仕様、"Wio LTE for Arduino"ライブラリに関する情報を参照する場合は、以下の Seeed の Wiki ページを参照してください。

[https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html](https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html)

## 各種センサの値取得方法

各センサの値の取得方法はスケッチ(IDE)のスケッチ例のそれぞれ下記を参照してください。

`ファイル` → `スケッチ例` → `スケッチ例` → `Wio LTE for Arduino`

| センサ/アクチュエータ| スケッチ名                            |
| ---------------- | ------------------------------------- |
| プッシュボタン   | grove-button                          |
| 磁気センサ       | grove-magnetic-switch                 |
| ブザー           | grove-buzzer                          |
| 温度・湿度センサ | grove-temperature-and-humidity-sencor |
| 超音波センサ     | grove-ultrasonic-ranger               |
| 3 軸加速度センサ | grove-accelerometer                   |
| GPS センサ       | grove-gps                             |

## 追加センサーの使い方

| センサー/アクチュエータ | リンク                                                        |
| ----------------------- | ------------------------------------------------------------- |
| 水センサー              | http://wiki.seeedstudio.com/Grove-Water_Sensor/               |
| 水分センサー            | http://wiki.seeedstudio.com/Grove-Moisture_Sensor/            |
| 衝突センサー            | http://wiki.seeedstudio.com/Grove-Collision_Sensor/           |
| 傾きセンサー            | http://wiki.seeedstudio.com/Grove-Tilt_Switch/                |
| ラインセンサー          | http://wiki.seeedstudio.com/Grove-Line_Finder/                |
| 心拍センサー            | http://wiki.seeedstudio.com/Grove-Ear-clip_Heart_Rate_Sensor/ |
| PIRモーションセンサー   | http://wiki.seeedstudio.com/Grove-PIR_Motion_Sensor/          |
| RGB LED                 | http://wiki.seeedstudio.com/Grove-Chainable_RGB_LED/          |
| 磁気スイッチ            | http://wiki.seeedstudio.com/Grove-Magnetic_Switch/            |