Wio(Arduino+GPSセンサ)からAlibabaCloud IoT Platformへ通信する

#はじめに
こんにちは。ひつじです。
すっかり寒い季節になってきましたね。

前回、AlibabaCloudPlatformの記事を書いたのですが今回は実際にIoTデバイスからセンシングデータを受信してみたいと思います。

本記事の目的としては「ArduinoとSoracomAir」を通じてAlibabaCloudへの接続が成功したことのシェア」となります。

#<前回記事>AlibabaCloud IoT Platform設定方法
https://qiita.com/sheep29/items/e99dc9e50d7a44321520

#マイコンについて

マイコンって何だ！？とお思いの方もいらっしゃると思いますが、今回使うのは以下のマイコンモジュールです。

![products_wio_lte.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/ce7b59e6-00a5-a6d0-ac21-c21dc698571f.png)


Wioと書いて「ワイオ」と読みます。
Seeed社が開発しているマイコンモジュールです。Grove コネクターと STM32F4 マイコン、LTE モジュールが搭載されています。

#Wioについて

今、手元には２つのWioモジュールがあります。

![wio2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/3e21d4a5-249c-1674-5173-6033fe68b6ec.png)


**Cat.1（カテゴリー１）**
　NB-IoTと比較し帯域が多く利用でき、通話モードも備えている。
　SIM：Soracom Air
**NB-IoT（NBは、Narrow Bandの略。)**
　Cat.1より使用電流が少なく、モデムも低価格。帯域幅が狭いので低容量通信に長けている。
　SIM：SoftBank

このWioにはArduinoという開発環境が搭載されており、C#に似たArduino言語でプログラムを記述、実行することができます。
基板にはgroveコネクタが6個ほど付いているのがわかると思います。
ここに、センサーをアタッチするイメージです。
Groveコネクタを使えばセンサーをカチッとはめるだけで簡単に使えて便利です！

![wio3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/447096f9-169f-7621-e0d7-64989ca7e8d9.png)

こんな感じ。

![iOS の画像 (12).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/93b6886a-dffd-6e97-031d-bc2a3566a2d1.jpeg)

上記の画像はGPSセンサーをアタッチします。
なんかプラモデルみたいで楽しいですよね。

今回の最終構成ですが、このモジュールにてセンサーの情報を取得し、MQTTでAlibabaCloudのIoTプラットフォームへ転送を行います。その後、TableStoreと呼ばれるNoSQLにストアして、可視化ツール（DATAV）で可視化していきたいと思います。

**[前置き]**
**記事が長くなっちゃったので、まずは、ArduinoとIoTPlatFormへの接続を投稿します**

<img width="1041" alt="スクリーンショット 2019-12-13 16.09.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/eea557d4-60cf-6c8d-8827-c536753c29a7.png">


#Arduinoについて
Arduinoは使うマイコンモジュールによってボード、必要なライブラリが異なるので注意が必要です。
以下のリンクを参考すれば、ちゃんとWioの環境にあったArduinoが準備できるで安心してください。

###Arduinoのインストール
Arduinoのインストールは以下のサイトから実施できます。

https://www.arduino.cc/en/main/software

###ボードの設定
今回はWioのボードをインストールする必要がありますが、手順は全て以下のリンクに記載されております。
https://seeedjp.github.io/Wiki/Wio_LTE_for_Arduino/Home-ja.html

###今回使うマイコン
今回はSoracomスターターキットについてきているCat.1を利用していきたいとおもいます。こちらのスターターキットにはSoracomAirというSIMが付いています。今回はSoracomAirを用いた回線でAlibabaCloudに接続したいと思います。

以下、後々使う接続情報です。

|項目|パラメータ|
|---|---|
|APN|               soracom.io|
|USERNAME|          sora|
|PASSWORD|          sora|

#IoTPlatformの設定

前回の[ブログ](https://qiita.com/sheep29/items/e99dc9e50d7a44321520)に記述した内容を参考にIotPlatformの設定を行います。
設定後のパラメータは以下となります。

|項目|パラメータ|
|----|----|
|ホスト名|a63V16tah0l.iot-as-mqtt.ap-northeast-1.aliyuncs.com|
|ポート|1883|
|クライアントID|sheep29&#124;securemode=3,signmethod=hmacsha1|
|トピック|/a63V16tah0l/devsheep/user/topicsheep|
|ユーザ|devsheep&a63V16tah0l|
|パスワード|217629783CFE62B50ECCB2B56CAAFC4C465E7D66|

###PubSubclient library注意点

`PubSubClient.hはKeepAliveは15秒がデフォルトですが
AlibabaCloudではKeepAliveを30秒に設定する必要があります。
`

ご自身の端末にインストールしたライブラリを編集してください。

```:libraries\PubSubClient\src\PubSubClient.h
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 30
#endif
```

###Arduinoを操作してみる
では、実際にArduinoでのスケッチをしていきたいと思います。
WioデバイスのuptimeをAlibabaCloudに送ってみたいと思います。

尚、Arduinoのスケッチはスケッチ例から作成すると便利です。
`ファイル -> スケッチ例 -> Wio LTE for Arduino -> mqtt -> mqttclient`

```C#
#include <WioLTEforArduino.h>
#include <WioLTEClient.h>
#include <PubSubClient.h>
#include <stdio.h>

#define APN               "soracom.io"
#define USERNAME          "sora"
#define PASSWORD          "sora"
#define MQTT_SERVER_HOST  "a63V16tah0l.iot-as-mqtt.ap-northeast-1.aliyuncs.com"
#define MQTT_SERVER_PORT  (1883)
#define ID                "sheep29|securemode=3,signmethod=hmacsha1|"
#define OUT_TOPIC         "/a63V16tah0l/devsheep/user/topicsheep"
#define MQUSER            "devsheep&a63V16tah0l"
#define MQPASS            "217629783CFE62B50ECCB2B56CAAFC4C465E7D66"
#define INTERVAL          (60000)

WioLTE Wio;
WioLTEClient WioClient(&Wio);
PubSubClient MqttClient;

void callback(char* topic, byte* payload, unsigned int length) {
 SerialUSB.print("Subscribe:");
 for (int i = 0; i < length; i++) SerialUSB.print((char)payload[i]);
 SerialUSB.println("");
}

void setup() {
 delay(200);
 SerialUSB.println("");
 SerialUSB.println("--- START ---------------------------------------------------");
 SerialUSB.println("### I/O Initialize.");
 Wio.Init();
 SerialUSB.println("### Power supply ON.");
 Wio.PowerSupplyLTE(true);
 delay(500);
 SerialUSB.println("### Turn on or reset.");
 if (!Wio.TurnOnOrReset()) {
   SerialUSB.println("### ERROR! ###");
   return;
 }
 SerialUSB.println("### Connecting to \""APN"\".");
 if (!Wio.Activate(APN, USERNAME, PASSWORD)) {
   SerialUSB.println("### ERROR! ###");
   return;
 }
 SerialUSB.println("### Connecting to MQTT server \""MQTT_SERVER_HOST"\"");
 MqttClient.setServer(MQTT_SERVER_HOST, MQTT_SERVER_PORT);
 MqttClient.setCallback(callback);
 MqttClient.setClient(WioClient);
 if (!MqttClient.connect(ID,MQUSER,MQPASS)) {
   SerialUSB.println("### ERROR! ###");
   return;
 }
 MqttClient.subscribe(IN_TOPIC);
 SerialUSB.println("### Setup completed.");
}

//INTERVALでUPTIME送信
void loop() {
 char data[100];
 sprintf(data, "{\"uptime\":%lu}", millis() / 1000);
 SerialUSB.print("Publish:");
 SerialUSB.print(data);
 SerialUSB.println("");
 MqttClient.publish(OUT_TOPIC, data);
err:
 unsigned long next = millis();
 while (millis() < next + INTERVAL)
 {
   MqttClient.loop();
 }
}
```

##スケッチと確認

IoTPlatformの設定とスケッチの作成が完了したら
マイコンボード(Wio)への書き込みを実施しましょう。

<img width="495" alt="ardui.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/cd0def34-e8a7-e73d-6f59-f1b404041632.jpeg">


実行後はArduino上のコンソールから出力結果を確認できるので、出力結果を確認してみましょう。

`ツール -> シリアルモニタ`


<img width="646" alt="ardui2.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/1e778d53-7c80-268b-6f4b-bf56bfe1f02b.jpeg">

上記の例では「soracom.io」と「AlibabaCloud」への接続がうまくできたことを表しています。


#AlibabaCloudコンソールでPublishデータの確認

次にAlibabaCloudのIoT Platformの画面にて出力結果を確認してみましょう。
AlibabaCloudコンソールにログインして、IoT Platformのアイコンをクリックします。

`監視と運用 -> デバイスログ　-> デバイス動作分析`

ステータス200で返ってきていることが確認できますね。

<img width="867" alt="ar1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/3743d70f-63b2-1165-0aa6-142b45c1368d.png">

では、WioからPublishしたテキストデータも確認しましょう。
`監視と運用 -> デバイスログ　-> アップストリーム分析 -> MessageID`

<img width="739" alt="ardui4.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/497c8ee2-4a9b-f542-ca5d-3f0ef7d29c05.jpeg">

これでWioデバイスからSoracomAirを使ってAlibabaCloudのIotPlatformとの接続を確認できました。

#GPSセンサーからデータを送る

GPSセンサーで緯度経度の情報を送付してみたいと思います。

先ほどと同じ要領で下記のようなコードをスケッチします。
尚、GPSのサンプルコードもWioのボードにあるので参考にすると簡単にかけます。

```C#
#include <WioLTEforArduino.h>
#include <WioLTEClient.h>
#include <PubSubClient.h>    // https://github.com/SeeedJP/pubsubclient
#include <stdio.h>
#define APN               "soracom.io"
#define USERNAME          "sora"
#define PASSWORD          "sora"
#define MQTT_SERVER_HOST  "a63V16tah0l.iot-as-mqtt.ap-northeast-1.aliyuncs.com"
#define MQTT_SERVER_PORT  (1883)
#define ID                "sheep29|securemode=3,signmethod=hmacsha1|"
#define OUT_TOPIC         "/a63V16tah0l/devsheep/user/topicsheep"
#define MQUSER            "devsheep&a63V16tah0l"
#define MQPASS            "217629783CFE62B50ECCB2B56CAAFC4C465E7D66"
#define INTERVAL          (60000)
WioLTE Wio;
WioLTEClient WioClient(&Wio);
PubSubClient MqttClient;

String cmds[14] = {"\0"}; // for SplitFunction
String latlong;　// 分割された文字列を格納する配列
void callback(char* topic, byte* payload, unsigned int length) {
  SerialUSB.print("Subscribe:");
  for (int i = 0; i < length; i++) SerialUSB.print((char)payload[i]);
  SerialUSB.println("");
}
void setup(){
  delay(5000);
  SerialUSB.println("");
  SerialUSB.println("--- START ---------------------------------------------------");
  SerialUSB.println("### I/O Initialize.");
  GpsBegin(&Serial);
  Wio.Init();
  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyGrove(true);
  delay(500);
  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyLTE(true);
  delay(500);
  SerialUSB.println("### Turn on or reset.");
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### ERROR0! ###");
    return;
  }
  SerialUSB.println("### Connecting to \""APN"\".");
  if (!Wio.Activate(APN, USERNAME, PASSWORD)) {
    SerialUSB.println("### ERROR1! ###");
    return;
  }
  SerialUSB.println("### Connecting to MQTT server \""MQTT_SERVER_HOST"\"");
  MqttClient.setServer(MQTT_SERVER_HOST, MQTT_SERVER_PORT);
  MqttClient.setCallback(callback);
  MqttClient.setClient(WioClient);
  if (!MqttClient.connect(ID,MQUSER,MQPASS)) {
    SerialUSB.println("### ERROR2! ###");
    return;
  }
  SerialUSB.println("### Setup completed.");
}
int split(String data, char delimiter, String *dst){
    //SerialUSB.println("### Step-S1");
    int index = 0;
    int arraySize = 15; //GPGGAのフィールド数指定
    int datalength = data.length();
    dst[index] = "\0";
    for (int i = 0; i < datalength; i++) {
        char tmp = data.charAt(i);
        if ( tmp == delimiter ) {
            index++;
            dst[index] = "\0";
            if ( index > (arraySize - 1)) return -1;
        }
        else dst[index] += tmp;
    }
    return (index + 1);
}
void loop()
{
  delay(5000);
  //SerialUSB.println("### Start");
  char sLat[64]="";
  char sLong[64]="";
  char sLatLong[64]=""  ;
  const char* data = GpsRead();
  //SerialUSB.println("### Step2");
  if (data != NULL && strncmp(data, "$GPGGA,", 7) == 0) {
      //for debug
      //data="$GPGGA,010841.000,3539.9084,N,13945.6998,E,1,4,1.99,165.8,M,3";
      SerialUSB.println(data);
      int index = split(data, ',', cmds);
      for(int i = 0; i < index; i++){
          if(i == 2){
               //string型のままではsprintfできないのでchar型に変換
               char x[50]="\0";
               (cmds[i].substring(0,2)+'.'+cmds[i].substring(2,4)+cmds[i].substring(6,9)).toCharArray(x, 50);
               sprintf(sLat,"\"lat\": %s",x);
          }
          if(i == 4){
               char y[50]="\0";
               (cmds[i].substring(0,3)+'.'+cmds[i].substring(3,5)+cmds[i].substring(6,10)).toCharArray(y, 50);
               sprintf(sLong,"\"long\": %s",y);
          }
      }
    sprintf(sLatLong,"{%s,%s}",sLat,sLong);
    SerialUSB.println(sLatLong);
    MqttClient.publish(OUT_TOPIC, sLatLong);
  }
}

#define GPS_OVERFLOW_STRING "OVERFLOW"
HardwareSerial* GpsSerial;
char GpsData[300];
char GpsDataLength;
void GpsBegin(HardwareSerial* serial)
{
  GpsSerial = serial;
  GpsSerial->begin(9600);
  GpsDataLength = 0;
}
const char* GpsRead()
{
  while (GpsSerial->available()) {
    char data = GpsSerial->read();
    if (data == '\r') continue;
    if (data == '\n') {
      GpsData[GpsDataLength] = '\0';
      GpsDataLength = 0;
      return GpsData;
    }
    if (GpsDataLength > sizeof (GpsData) - 1) { // Overflow
      GpsDataLength = 0;
      return GPS_OVERFLOW_STRING;
    }
    GpsData[GpsDataLength++] = data;
  }
  return NULL;
}
```

では、実行してみます。

<img width="864" alt="i10.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/25712618-c08b-398e-1df2-a8848eafb6b5.jpeg">

IoTPlatformでPublishデータの確認をしてみましょう。

<img width="703" alt="スクリーンショット 2019-12-13 15.40.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319145/0dbc26ec-03e6-f6fa-3b4f-8ef737765219.png">


GPS情報が取得できましたね。
latは"latitude"の略で緯度を意味します。
longは"longitude"の略で経度を意味します。


#最後に
本来であれば、このあとルールエンジンを用いてTableStoreと連携し、
DataVで可視化までしたかったのですが別記事として投稿致します！

それではご査収ください〜