はじめに
本コンテンツは、Wio LTE M1/NB1(BG96) (以下Wio LTE)を用いた、IoT ハッカソンで使用する技術ハンズオンの資料です。
ネットワーク環境は参加者が自由選択できますので、参考としてご活用ください。
概要
本ページでは温湿度センサーから取得した温度値をHTTPを介して転送する例を示します。
お断り
本ハッカソンで提供する Wio LTE M1/NB1(BG96) は非売品のサンプルとなりますので、取扱いにご注意ください。
準備
Seeed JPの開発環境のセットアップページより、環境の準備を進めてください。
動作確認用フロー作成
enebularから、動作確認用のフローを作成していきます。
プロジェクト作成
Projects
を選択し、Create Project
を選択します。
続いて、ダイアログにプロジェクト名を入力し、Submit
ボタンを選択します。
プロジェクト名には、何を入力しても構いません。
フロー作成
作成したプロジェクトのASSETSから、Flows
を選択します。
Flowのリストから+
ボタンを選択して、フローを作成していきます。
ダイアログが表示されたら、最上段のname欄にフロー名を入力します。
ここでは、"wio-debug"としています。
入力が終わったら、"Continue"ボタンを選択してください。
続いて、Edit
ボタンを選択して、フローを編集していきます。
フロー編集
ブラウザ左側にあるノード一覧から以下のノードを選択して、フローを編集するエリアにドラッグ & ドロップしてください。
配置したら、http
ノードからdebug
とhttps response
ノードへ接続します。
-
入力
-http
-
出力
-debug
-
出力
-http response
次に、エントリーポイントURLを設定していきます。
http
ノードをダブルクリックして、以下設定値を入力します。
入力が完了したら完了
ボタンを選択します。
項目 | 設定値 |
---|---|
メソッド | POST |
URL | /test |
名前 | <任意 > |
ここではURLを"/test"としていますが、開発時に各自設定を変更してください。
また、URL
の設定項目はデバイスのプログラムに設定が必要になるため、控えておいてください。
次に、応答コードを設定します。
http response
ノードをダブルクリックして、以下設定値を入力します。
入力が完了したら完了
ボタンを選択します。
項目 | 設定値 |
---|---|
名前 | <任意> |
状態コード | 200 |
続いて、i
アイコンにマウスカーソルを載せて転送先URLを控えてください。
以上で、デバッグ用のフロー作成が完了です。
センサー接続
以下のように、温湿度センサーをD38ポートに接続します。
また、アンテナが接続されていることを確認してください。
続いて、接続が完了したらUSBケーブルでPCとWio LTEを接続してください。
サンプルプログラム
Arduino IDEを開き、以下のサンプルコード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にプログラムの書き込みを行います。
解説
プログラムは大きく分けて 5 つに別れています。
-
初期化(setup)
LTE 回線への接続、Grove センサの初期化 -
定期処理(loop)
温度線センサーから温度値を読み出し、送信処理(sendData)を呼び出す -
送信処理(sendData)
温度データを文字列に変換(JsonStringify)して、enebularにHTTP POSTメソッドでデータを転送する -
文字列へ変換(JsonStringify)
float 型からクラウドでデータ処理しやすい JSON 形式の文字列へ変換 -
温湿度センサー値(TemperatureAndHumidityRead)
温湿度センサのデジタル値を読み取り、アナログ値へ変換
(※Wio LTEのライブラリに付属するスケッチ例そのもののため、詳細は省略)
初期化(setup)
Wio LTE 本体の処理を呼び出すための変数をグローバル変数で準備します。
WioCellular g_wio;
上記の割り当てた内容をsetup関数内で初期化します。
-
はじめに、Initを呼び出して WioLTE 全体の初期化を行います
wiolte_bg96_temperature.ino抜粋g_wio.Init();
-
次に 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);
-
今回使用する通信業者のMobile Network Codeを明示的に指定します
wiolte_bg96_temperature.ino抜粋g_wio.SetSelectNetwork(WioCellular::SELECT_NETWORK_MODE_MANUAL, "44020");
-
データ通信を有効にするためActivateでアクティベーションします
SIM カードが刺さっていなかったり、圏外などで接続できない場合が 3 秒(3000 ミリ秒)で処理を中断するようにしています。
(何も指定しない場合、120 秒間(2 分)のタイムアウト時間となります)wiolte_bg96_temperature.ino抜粋if(!g_wio.Activate(APN, USERNAME, PASSWORD, 30000)){ SerialUSB.println("error: APN activate failed."); }
-
Groveポートへの電源供給
追加でセンサーをつけることを考慮して、GroveコネクターD38以外のポート(D20/A4/A6/I2C/UART)の電源供給をONしますwiolte_bg96_temperature.ino抜粋g_wio.PowerSupplyGrove(true); delay(500);
-
温湿度センサーを初期化
スケッチ例(ライブラリ付属のサンプルコード)を参考に、温湿度センサー初期化用の関数を呼び出しますwiolte_bg96_temperature.ino抜粋TemperatureAndHumidityBegin(SENSOR_PIN);
定期処理(loop)
定期処理では、読み出しと送信処理を呼び出しています。
-
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!"); }
-
最後に 5000 ミリ秒(5 秒)待機して終了します
なお、時間を短くすることは可能ですが、温度センサーの DHT11 のサンプリング間隔が 2 秒以上の仕様のため、正しい値が取得できなくなってしまいます。
送信処理(sendData)
送信処理では、データ変換 → 転送に必要な情報準備 → 送信の順番で処理をします。
-
バイナリの生値をHTTPで転送するため、JSON形式の文字列に変換します
wiolte_bg96_temperature.ino抜粋String data = packet.JsonStringify();
-
HTTPのコンテンツタイプヘッダにMIMEタイプとしてJSONを指定します
wiolte_bg96_temperature.ino抜粋WioCellularHttpHeader header; header["Content-Type"] = "application/json";
-
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℃ が取れた場合の変換過程を右側にコメントをつけて説明します。
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
にしています。
シリアルモニタに以下のように、温度とResponse Codeが200が出力されていれば成功です。
Send:{"tmpr_c":27.5}
Response Code:200
enebular側
データが正しくenebularに転送されていることを確認します。
enebularのフローエディタ右側のタブからデバッグ
を選択します。
以下の図のように、温度(デバイスが送信した文字列)が表示されていれば成功です。
その他情報
APIリファレンス
トラブルシュート
D38 ポート以外がうまく機能しない
起動直後は、D38 以外は OFF になるため、有効にする必要があります。
PowerSupplyGrove
を setup()関数内で呼び出すことで解決します。
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 |
追加センサーの使い方
運営が用意する追加センサーの使い方以下を参照してください。