Arduino
hackathon
IoT
softbank
WioLTE_M1_NB1

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


はじめに

本コンテンツは、Wio LTE M1/NB1(BG96) (以下Wio LTE)を用いた、IoT ハッカソンで使用する技術ハンズオンの資料です。

ネットワーク環境は参加者が自由選択できますので、参考としてご活用ください。


概要

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


お断り

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


準備

Seeed JPの開発環境のセットアップページより、環境の準備を進めてください。


動作確認用フロー作成

enebularから、動作確認用のフローを作成していきます。


プロジェクト作成

Projectsを選択し、Create Projectを選択します。

Projects___enebular.png

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

プロジェクト名には、何を入力しても構いません。

Projects___enebular-3.png


フロー作成

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

niigata-hackathon___enebular.png

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

Flows___enebular.png

ダイアログが表示されたら、最上段のname欄にフロー名を入力します。

ここでは、"wio-debug"としています。

入力が終わったら、"Continue"ボタンを選択してください。

Flows___enebular-2.png

続いて、Editボタンを選択して、フローを編集していきます。

wio-debug___enebular.png


フロー編集

以下の図のようなフローを作成していきます。

enebular_Flow_Editor-2.png

ブラウザ左側にあるノード一覧から以下のノードを選択して、フローを編集するエリアにドラッグ & ドロップしてください。

配置したら、httpノードからdebughttps responseノードへ接続します。

- 入力 - http

- 出力 - debug

- 出力 - http response

次に、エントリーポイントURLを設定していきます。

httpノードをダブルクリックして、以下設定値を入力します。

入力が完了したら完了ボタンを選択します。

項目
設定値

メソッド
POST

URL
/test

名前
<任意 >

enebular_Flow_Editor-2.png

ここではURLを"/test"としていますが、開発時に各自設定を変更してください。

また、URLの設定項目はデバイスのプログラムに設定が必要になるため、控えておいてください。

次に、応答コードを設定します。

http responseノードをダブルクリックして、以下設定値を入力します。

入力が完了したら完了ボタンを選択します。

項目
設定値

名前
<任意>

状態コード
200

Banners_and_Alerts_と_enebular_Flow_Editor.png

編集が完了したら、デプロイを選択します。

enebular_Flow_Editor-3.png

続いて、iアイコンにマウスカーソルを載せて転送先URLを控えてください。

転送先URL

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


センサー接続

以下のように、温湿度センサーをD38ポートに接続します。

また、アンテナが接続されていることを確認してください。

温湿度センサー接続

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


サンプルプログラム

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


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のフロー作成で控えた値に置き換えてください。

#define TARGET_HOST       "<転送先URL>"


#define TARGET_HOST "https://ev2-prod-node-red-*****.herokuapp.com"

#define TARGET_URL        "<エントリーポイントURL>"


#define TARGET_URL "/test"


APN設定

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

// LTEプロバイダ 接続情報

#define APN "<APN>"
#define USERNAME "<ユーザー名>"
#define PASSWORD "<パスワード>"


プログラム書き込み

書き換えが終わったら、Wio LTEにプログラムの書き込みを行います。

wiolte_bg96_temperature___Arduino_1_8_7.png


解説

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


  1. 初期化(setup)


    LTE 回線への接続、Grove センサの初期化


  2. 定期処理(loop)


    温度線センサーから温度値を読み出し、送信処理(sendData)を呼び出す


  3. 送信処理(sendData)


    温度データを文字列に変換(JsonStringify)して、enebularにHTTP POSTメソッドでデータを転送する


  4. 文字列へ変換(JsonStringify)


    float 型からクラウドでデータ処理しやすい JSON 形式の文字列へ変換


  5. 温湿度センサー値(TemperatureAndHumidityRead)


    温湿度センサのデジタル値を読み取り、アナログ値へ変換

    (※Wio LTEのライブラリに付属するスケッチ例そのもののため、詳細は省略)



初期化(setup)

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


wiolte_bg96_temperature.ino抜粋

WioCellular g_wio;


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



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


    wiolte_bg96_temperature.ino抜粋

    g_wio.Init();
    




  2. 次に LTE の機能を使用するための、PowerSupplyLTEで電源供給を ON し、LTE モジュールの起動操作TurnOnOrResetを呼び出します

    ネットワーク関連の設定(PowerSupplyCellular, SetAccessTechnology) → TurnOnOrReset 順番で呼び出さないと正しく初期化できないので注意してください。

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


    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を指定する場合は、通信方式を呼び出している箇所を以下に書き換えてください。


    wiolte_bg96_temperature.ino抜粋

    g_wio.SetAccessTechnology(WioCellular::ACCESS_TECHNOLOGY_LTE_M1);
    




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


    wiolte_bg96_temperature.ino抜粋

      g_wio.SetSelectNetwork(WioCellular::SELECT_NETWORK_MODE_MANUAL, "44020");
    




  4. データ通信を有効にするためActivateでアクティベーションします

    SIM カードが刺さっていなかったり、圏外などで接続できない場合が 3 秒(3000 ミリ秒)で処理を中断するようにしています。

    (何も指定しない場合、120 秒間(2 分)のタイムアウト時間となります)


    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します


    wiolte_bg96_temperature.ino抜粋

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




  6. 温湿度センサーを初期化

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


    wiolte_bg96_temperature.ino抜粋

      TemperatureAndHumidityBegin(SENSOR_PIN);
    




定期処理(loop)

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



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


    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形式の文字列に変換します


    wiolte_bg96_temperature.ino抜粋

      String data = packet.JsonStringify();
    




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


    wiolte_bg96_temperature.ino抜粋

      WioCellularHttpHeader header;
    
    header["Content-Type"] = "application/json";




  3. ENDPOINT指定した転送先にデータを転送します。

    送信データには、c_str()で String 型から char *型のデータを取得して送信します。


    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 形式に変換する必要があるため、"{<key>:<value>}"の形式にしていきます。

ここでは仮に 25.51℃ が取れた場合の変換過程を右側にコメントをつけて説明します。


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のメニューバーツール - シリアルモニタでシリアルモニタを開きます。

または、ツールバー右側の虫眼鏡アイコンを選択します。

シリアルモニタ表示

続いて、Wio LTE <-> PC間のUART通信のボーレートを設定します。

ここでは、プログラムに合わせて115200 bpsにしています。

シリアルモニタ-2.png

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

Send:{"tmpr_c":27.5}

Response Code:200


enebular側

データが正しくenebularに転送されていることを確認します。

enebularのフローエディタ右側のタブからデバッグを選択します。

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

enebular_Flow_Editor-4.png


その他情報


APIリファレンス

https://seeedjp.github.io/Wiki/Wio_cell_lib_for_Arduino/reference-ja.html


トラブルシュート


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

起動直後は、D38 以外は OFF になるため、有効にする必要があります。

PowerSupplyGroveを setup()関数内で呼び出すことで解決します。


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 Arduinogrove

センサ/アクチュエータ
スケッチ名

プッシュボタン
grove-button

磁気センサ
grove-magnetic-switch

ブザー
grove-buzzer

温度・湿度センサ
grove-temperature-and-humidity-sensor

超音波センサ
grove-ultrasonic-ranger

3軸加速度センサ
grove-accelerometer

GPS センサ
grove-gps

starter-kit



追加センサーの使い方

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