IoT ハッカソン デバイスプログラム手順 〜 Wio LTE M1/NB1(BG96) SoftBank Edition 〜

# はじめに

本コンテンツは、Wio LTE M1/NB1(BG96) (以下Wio LTE)を用いた、IoT ハッカソンで使用する技術ハンズオンの資料です。
ネットワーク環境は参加者が自由選択できますので、参考としてご活用ください。

## 概要

本ページでは温湿度センサーから取得した温度値をHTTPを介して転送する例を示します。

## お断り

本ハッカソンで提供する Wio LTE M1/NB1(BG96) は非売品のサンプルとなりますので、取扱いにご注意ください。

## 準備
[Seeed JPの開発環境のセットアップページ](https://github.com/SeeedJP/Wiki/blob/master/Wio_cell_lib_for_Arduino/setup-ja.md)より、環境の準備を進めてください。

## 動作確認用フロー作成

[enebular](https://enebular.com/)から、動作確認用のフローを作成していきます。

### プロジェクト作成

`Projects`を選択し、`Create Project`を選択します。
<img width="895" alt="Projects___enebular.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/e0b2b2b7-7c87-74ae-8c90-590a2725fa1e.png" style="border: 1px solid #000000;">

続いて、ダイアログにプロジェクト名を入力し、`Submit`ボタンを選択します。
プロジェクト名には、何を入力しても構いません。

<img width="562" alt="Projects___enebular-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/258deb3e-73a9-6a11-89d3-8640a72f4e54.png">

### フロー作成

作成したプロジェクトのASSETSから、`Flows`を選択します。

<img width="443" alt="niigata-hackathon___enebular.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/09e52098-2dcd-8f8b-aaac-6102cac02a84.png">

Flowのリストから`+`ボタンを選択して、フローを作成していきます。

<img width="584" alt="Flows___enebular.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/43ad862c-53ab-98d2-0d2e-6f3f43ed3297.png">

ダイアログが表示されたら、最上段のname欄にフロー名を入力します。
ここでは、"wio-debug"としています。
入力が終わったら、"Continue"ボタンを選択してください。
<img width="645" alt="Flows___enebular-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/37c3e169-b103-5363-2619-59e57fb9c51a.png">

続いて、`Edit`ボタンを選択して、フローを編集していきます。
<img width="774" alt="wio-debug___enebular.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/fda7fe60-40e1-aa64-a056-abcb78981934.png">

### フロー編集

以下の図のようなフローを作成していきます。
<img width="679" alt="enebular_Flow_Editor-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/43504243-15a1-7dbd-e8d7-dedb7bce785a.png">

ブラウザ左側にあるノード一覧から以下のノードを選択して、フローを編集するエリアにドラッグ & ドロップしてください。
配置したら、`http`ノードから`debug`と`https response`ノードへ接続します。
- `入力` - `http`
- `出力` - `debug`
- `出力` - `http response`

次に、エントリーポイントURLを設定していきます。
`http`ノードをダブルクリックして、以下設定値を入力します。
入力が完了したら`完了`ボタンを選択します。

| 項目     | 設定値 |
|---------|-------|
| メソッド | POST |
| URL     | /test |
| 名前    | <任意 > |

<img width="497" alt="enebular_Flow_Editor-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/24a7b573-e2c6-7fe4-6566-033b5fed6c76.png">

ここではURLを"/test"としていますが、開発時に各自設定を変更してください。
また、<font color="red">`URL`の設定項目はデバイスのプログラムに設定が必要になるため、控えておいてください。</font>

次に、応答コードを設定します。
`http response`ノードをダブルクリックして、以下設定値を入力します。
入力が完了したら`完了`ボタンを選択します。

| 項目      | 設定値 |
|----------|-------|
| 名前      | <任意> |
| 状態コード | 200   |

<img width="504" alt="Banners_and_Alerts_と_enebular_Flow_Editor.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/31d14cbc-4a6d-a7a8-bcb3-bd6e71f1fb26.png">

編集が完了したら、`デプロイ`を選択します。
<img width="919" alt="enebular_Flow_Editor-3.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/84b02a6a-1b1d-474e-08a1-94b3e27c5794.png">

続いて、`i`アイコンにマウスカーソルを載せて<font color="red">転送先URLを控えてください。</font>
<img width="534" alt="転送先URL" src="https://qiita-image-store.s3.amazonaws.com/0/281420/47045820-385e-5d7e-f239-6bb2b8e1c6b3.png">

以上で、デバッグ用のフロー作成が完了です。

## センサー接続

以下のように、温湿度センサーをD38ポートに接続します。
また、アンテナが接続されていることを確認してください。

![温湿度センサー接続](https://qiita-image-store.s3.amazonaws.com/0/281420/33f73be5-c031-e394-32eb-1035827afc9f.jpeg)

続いて、接続が完了したらUSBケーブルでPCとWio LTEを接続してください。

## サンプルプログラム

Arduino IDEを開き、以下のサンプルコード`wiolte_bg96_temperature.ino`を貼り付けてください。

```Cpp:wiolte_bg96_temperature.ino
#include <WioCellLibforArduino.h>
#include <stdio.h>

// LTEプロバイダ 接続情報
#define APN      "<APN>"
#define USERNAME "<ユーザー名>"
#define PASSWORD "<パスワード>"

// データ送信先選択
#define TARGET_HOST       "<転送先URL>"
#define TARGET_URL        "<エントリーポイントURL>"
constexpr const char *ENDPOINT = TARGET_HOST TARGET_URL;

constexpr uint8_t SENSOR_PIN = WIO_D38; // センサー接続ピン

// ===================================================
// グローバル変数
// ===================================================
WioCellular g_wio;

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

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

    sprintf(buf, "%.1f", this->temp);
    result += "{\"tmpr_c\":";
    result += buf;
    result += "}";

    return result;
  }
};

// ===================================================
// 初期化処理
// ===================================================
void setup()
{
  SerialUSB.begin(115200);
  delay(200);

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

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

  // LTE NB1を明示的に指定
  SerialUSB.println("### ACCESS_TECHNOLOGY_LTE_NB   ");
  g_wio.SetAccessTechnology(WioCellular::ACCESS_TECHNOLOGY_LTE_NB1);

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

  // プロバイダ経由してMobile Network接続
  // OSI参照モデルのL2(データリンク層)における認証
  SerialUSB.println("### SELECT_NETWORK_MODE_MANUAL ");
  g_wio.SetSelectNetwork(WioCellular::SELECT_NETWORK_MODE_MANUAL, "44020");

  SerialUSB.println("start: APN activate.");
  if(!g_wio.Activate(APN, USERNAME, PASSWORD, 30000)){
    SerialUSB.println("error: APN activate failed.");
  }

  g_wio.PowerSupplyGrove(true);
  delay(500);
  TemperatureAndHumidityBegin(SENSOR_PIN);

  SerialUSB.println("setup OK.");

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

// ===================================================
// 定期処理
// ===================================================
void loop()
{
  float temp;
  float humi;

  const bool sensorValueOk = TemperatureAndHumidityRead(&temp, &humi);
  if (sensorValueOk) {
    SendPacket data;
    data.temp = temp;
    SerialUSB.print("temp:"); SerialUSB.println(data.temp);
    sendData(data);
  }
  else {
    SerialUSB.println("TEMP SENSOR ERROR!");    
  }
  
  delay(5000);
}

// ===================================================
// データ送信処理
// ===================================================
void sendData(const SendPacket &packet)
{
  String data = packet.JsonStringify();
  SerialUSB.print("Send:");
  SerialUSB.print(data);
  SerialUSB.println("");

  WioCellularHttpHeader header;
  header["Content-Type"] = "application/json";
  
  int status;
  if (g_wio.HttpPost(ENDPOINT, data.c_str(), &status, header)){
    SerialUSB.print("Response Code:");
    SerialUSB.println(status);
  }
  else {
    SerialUSB.println("Failed POST.");    
  }
}

////////////////////////////////////////////////////////////////////////////////////////
//
// 以下、温湿度センサーのスケッチ例を引用
// [ファイル] -> [Wio cell lib for Arduino] -> [grove] -> [grove-temperature-and-humidity-sensor]
// 
////////////////////////////////////////////////////////////////////////////////////////

int TemperatureAndHumidityPin;

void TemperatureAndHumidityBegin(int pin) {
  TemperatureAndHumidityPin = pin;
  DHT11Init(TemperatureAndHumidityPin);
}

bool TemperatureAndHumidityRead(float* temperature, float* humidity) {
  byte data[5];
  
  DHT11Start(TemperatureAndHumidityPin);
  for (int i = 0; i < 5; i++) data[i] = DHT11ReadByte(TemperatureAndHumidityPin);
  DHT11Finish(TemperatureAndHumidityPin);
  
  if(!DHT11Check(data, sizeof (data))) return false;
  if (data[1] >= 10) return false;
  if (data[3] >= 10) return false;

  *humidity = (float)data[0] + (float)data[1] / 10.0f;
  *temperature = (float)data[2] + (float)data[3] / 10.0f;

  return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//

void DHT11Init(int pin) {
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

void DHT11Start(int pin) {
  // Host the start of signal
  digitalWrite(pin, LOW);
  delay(18);
  
  // Pulled up to wait for
  pinMode(pin, INPUT);
  while (!digitalRead(pin)) ;
  
  // Response signal
  while (digitalRead(pin)) ;
  
  // Pulled ready to output
  while (!digitalRead(pin)) ;
}

byte DHT11ReadByte(int pin) {
  byte data = 0;
  
  for (int i = 0; i < 8; i++) {
    while (digitalRead(pin)) ;

    while (!digitalRead(pin)) ;
    unsigned long start = micros();

    while (digitalRead(pin)) ;
    unsigned long finish = micros();

    if ((unsigned long)(finish - start) > 50) data |= 1 << (7 - i);
  }
  
  return data;
}

void DHT11Finish(int pin) {
  // Releases the bus
  while (!digitalRead(pin)) ;
  digitalWrite(pin, HIGH);
  pinMode(pin, OUTPUT);
}

bool DHT11Check(const byte* data, int dataSize) {
  if (dataSize != 5) return false;

  byte sum = 0;
  for (int i = 0; i < dataSize - 1; i++) {
    sum += data[i];
  }

  return data[dataSize - 1] == sum;
}

////////////////////////////////////////////////////////////////////////////////////////
```
### URL設定

貼り付けたコードを以下の`転送先URL`と`エントリーポイントURL`をenebularのフロー作成で控えた値に置き換えてください。

```Cpp
#define TARGET_HOST       "<転送先URL>"
↓
#define TARGET_HOST       "https://ev2-prod-node-red-＊＊＊＊＊.herokuapp.com"
```

```Cpp
#define TARGET_URL        "<エントリーポイントURL>"
↓
#define TARGET_URL        "/test"
```

### APN設定

貼り付けたコードの以下のAPN設定情報は、運営技術メンターから入手してください。

```Cpp
// LTEプロバイダ 接続情報
#define APN               "<APN>"
#define USERNAME          "<ユーザー名>"
#define PASSWORD          "<パスワード>"
```

### プログラム書き込み

書き換えが終わったら、Wio LTEにプログラムの書き込みを行います。
<img width="282" alt="wiolte_bg96_temperature___Arduino_1_8_7.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/01cdd70f-08f6-a399-8e1a-a22216d7fe04.png">


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

1. **[初期化(setup)](#初期化setup)**  
  LTE 回線への接続、Grove センサの初期化

2. **[定期処理(loop)](#定期処理loop)**  
  温度線センサーから温度値を読み出し、送信処理(sendData)を呼び出す

3. **[送信処理(sendData)](#送信処理senddata)**  
  温度データを文字列に変換(JsonStringify)して、enebularにHTTP POSTメソッドでデータを転送する

4. **[文字列へ変換(JsonStringify)](#文字列へ変換jsonstringify)**  
  float 型からクラウドでデータ処理しやすい JSON 形式の文字列へ変換

5. **温湿度センサー値(TemperatureAndHumidityRead)**  
  温湿度センサのデジタル値を読み取り、アナログ値へ変換
  (※Wio LTEのライブラリに付属するスケッチ例そのもののため、詳細は省略)

### 初期化(setup)

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

```Cpp:wiolte_bg96_temperature.ino抜粋
WioCellular g_wio;
```

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

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

    ```Cpp:wiolte_bg96_temperature.ino抜粋
    g_wio.Init();
    ```

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

    ここでは、通信方式として**NB-IoT**を指定します。

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      g_wio.PowerSupplyCellular(true);

      // LTE NB1を明示的に指定
      SerialUSB.println("### ACCESS_TECHNOLOGY_LTE_NB   ");
      g_wio.SetAccessTechnology(WioCellular::ACCESS_TECHNOLOGY_LTE_NB1);

      SerialUSB.println("start: Module Initialize.");
      if(!g_wio.TurnOnOrReset()){
        SerialUSB.println("error:  Module init failed.");
        return;
      }
    ```
    また、**Cat.M1**を指定する場合は、通信方式を呼び出している箇所を以下に書き換えてください。

    ```Cpp:wiolte_bg96_temperature.ino抜粋
    g_wio.SetAccessTechnology(WioCellular::ACCESS_TECHNOLOGY_LTE_M1);
    ```

3. 今回使用する通信業者のMobile Network Codeを明示的に指定します

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      g_wio.SetSelectNetwork(WioCellular::SELECT_NETWORK_MODE_MANUAL, "44020");
    ```

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

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      if(!g_wio.Activate(APN, USERNAME, PASSWORD, 30000)){
        SerialUSB.println("error: APN activate failed.");
      }
    ```

5. Groveポートへの電源供給
    追加でセンサーをつけることを考慮して、GroveコネクターD38以外のポート(D20/A4/A6/I2C/UART)の電源供給をONします

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      g_wio.PowerSupplyGrove(true);
      delay(500);
    ```

6. 温湿度センサーを初期化
   スケッチ例(ライブラリ付属のサンプルコード)を参考に、温湿度センサー初期化用の関数を呼び出します

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      TemperatureAndHumidityBegin(SENSOR_PIN);
    ```


### 定期処理(loop)

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

1. TemperatureAndHumidityReadで温度センサーから値を読み出し、data.tempに値を格納して、`sendData`(スケッチ(コード))で準備した送信用の関数呼び出しをします。

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      float temp;
      float humi;

      const bool sensorValueOk = TemperatureAndHumidityRead(&temp, &humi);
      if (sensorValueOk) {
        SendPacket data;
        data.temp = temp;
        SerialUSB.print("temp:"); SerialUSB.println(data.temp);
        sendData(data);
      }
      else {
        SerialUSB.println("TEMP SENSOR ERROR!");    
      }
    ```

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


### 送信処理(sendData)

送信処理では、データ変換 → 転送に必要な情報準備 → 送信の順番で処理をします。

1. バイナリの生値をHTTPで転送するため、JSON形式の文字列に変換します

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      String data = packet.JsonStringify();
    ```

2. HTTPのコンテンツタイプヘッダにMIMEタイプとしてJSONを指定します

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      WioCellularHttpHeader header;
      header["Content-Type"] = "application/json";
    ```

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

    ```Cpp:wiolte_bg96_temperature.ino抜粋
      int status;
      if (g_wio.HttpPost(ENDPOINT, data.c_str(), &status, header)){
        SerialUSB.print("Response Code:");
        SerialUSB.println(status);
      }
    ````

### 文字列へ変換(JsonStringify)

単精度浮動小数点(float 型)を、sprintf関数を用いて小数点第1位表示(%.1f)になるよう文字の列(char[32])に変換します。
(温度センサーの精度が小数点第1位で取得できるため)

また、最終的に JSON 形式に変換する必要があるため、"{&lt;key&gt;:&lt;value&gt;}"の形式にしていきます。
ここでは仮に 25.51℃ が取れた場合の変換過程を右側にコメントをつけて説明します。

```Cpp:wiolte_bg96_temperature.ino抜粋
    sprintf(buf, "%.1f", this->temp);   // 25.50 → "25.5"    浮動小数点から整数へ変換しbufへ格納
    result += "{\"tmpr_c\":";           // {"tmpr_c":        resultに「{」括弧開きとキー「tmpr_c」を追加
    result += buf;                      // {"tmpr_c":25.5    resultに「25.5」の文字列を追加
    result += "}";                      // {"tmpr_c":25.5}   resultに「}」括弧閉じを追加
```

プログラムの解説は以上です。

## 動作確認

### デバイス側

Arduino IDEのメニューバー`ツール` - `シリアルモニタ`でシリアルモニタを開きます。
または、ツールバー右側の虫眼鏡アイコンを選択します。

<img width="821" alt="シリアルモニタ表示" src="https://qiita-image-store.s3.amazonaws.com/0/281420/53631c19-3eaa-1f29-183d-e3bf98e8194a.png">

続いて、Wio LTE <-> PC間のUART通信のボーレートを設定します。
ここでは、プログラムに合わせて`115200 bps`にしています。
<img width="880" alt="シリアルモニタ-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/a008b6b7-772a-81a0-f05e-03e9a5cff746.png">

シリアルモニタに以下のように、温度とResponse Codeが200が出力されていれば成功です。

```
Send:{"tmpr_c":27.5}
Response Code:200
```

### enebular側

データが正しくenebularに転送されていることを確認します。
enebularのフローエディタ右側のタブから`デバッグ`を選択します。

以下の図のように、温度(デバイスが送信した文字列)が表示されていれば成功です。

<img width="399" alt="enebular_Flow_Editor-4.png" src="https://qiita-image-store.s3.amazonaws.com/0/281420/b6e09a3d-da41-beb6-ac9b-4e0c8e074147.png">

## その他情報

### APIリファレンス  
https://seeedjp.github.io/Wiki/Wio_cell_lib_for_Arduino/reference-ja.html

### トラブルシュート

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

起動直後は、D38 以外は OFF になるため、有効にする必要があります。
`PowerSupplyGrove`を setup()関数内で呼び出すことで解決します。

```Cpp:Groveコネクタ電源供給のサンプルコード
WioCellular wio;

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

#### データがenebularに転送されない

Arduino IDEのシリアルコンソール出力でResponse Codeが`404`になっている場合、enebularでホストしているURLとデバイスに設定したURLに相違があります。

```
Send:{"tmpr_c":26.6}
Response Code:404
```

#### デバイス側でデータ転送後、次の処理へ進むのが遅い

enebular側のフローで応答コードを返すフローがない場合、
デバイスはタイムアウトまで待機ます。
一定時間経過後、`503`と表示されているため、その場合は`http response`ノードを追加してください。

```
Send:{"tmpr_c":27.4}
Response Code:503
```


### サンプルコード

  運営が全チームに用意するセンサーの使い方は、IDEスケッチ例のそれぞれ下記を参照してください。
  Arduino IDE `ファイル` → `Wio cell lib for Arduino` → `grove`

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

<img src="https://qiita-image-store.s3.amazonaws.com/0/281420/cd879840-ffea-4b60-4b2f-c6039b33b4f4.jpeg" alt="starter-kit" width="80%">

---

### 追加センサーの使い方
 運営が用意する追加センサーの使い方以下を参照してください。

- [【ウフル貸出分】Groveセンサー/アクチュエーター使い方集 〜Wio LTE Cat M1/NB1〜](https://qiita.com/uhuru-hackathon/private/8559e94843203f04e086)
- [【SB貸出分】Groveセンサー／アクチュエーターの使い方集〜Wio LTE Cat M1/NB1〜](https://qiita.com/bywaysideway/private/704174bbcbb47ef4ad36)