Arduino M0 Pro でマルチタスクプログラミング : Milkcocoaへの接続

# はじめに

この記事では，[TOPPERS/R2CA](http://dev.toppers.jp/trac_user/contrib/wiki/rtos_arduino)によるMilkcocoaへの接続方法について説明します．

TOPPERS/R2CAの説明とサンプルの動作方法は次の記事を見て下さい．

 - [Arduino M0 Pro でマルチタスクプログラミング : インストールとサンプルの実行](http://qiita.com/exshonda/items/093870ecdcda6c79678c)
 - [Arduino M0 Pro でマルチタスクプログラミング : マルチタスク](http://qiita.com/exshonda/items/7afb12b56696cf44895e)
 - [Arduino M0 Pro でマルチタスクプログラミング : Wifi通信](http://qiita.com/exshonda/items/914b7df0efd36866fd98)

# 変更履歴
 - 2016/5/05
  -  アクセスポイントの設定を別ファイルとした．

# Milkcocoaとは

Milkcocoaは，IoT向けのリアルタイムなデータのやりとりやデータのストア及び可視化をサポートするクラウドサービスです．アカウントを作れば無料で使うことができます．
ArduionoやESP8266向けのライブラリが提供されていので，簡単にデバイスとクラウド間の通信が可能です．

Qiitaでも[多くの記事](http://qiita.com/tags/milkcocoa/items)が投稿されています．

# ハードウエア

必要なハードウエアは， [Arduino M0 Pro でマルチタスクプログラミング : Wifi通信](http://qiita.com/exshonda/items/914b7df0efd36866fd98)の記事と同じものです．


# サーバー側の設定

ネットに色々説明があるので簡単に説明すると手順は次の通りです．

- Milkcocoaのアカウントを作成する．
- Milkcocoaにログインして新しいアプリ(名前は何でもよい)を作る．
- アプリが作成されると app_id が表示されるので覚えておく．
- [Milkcocoa Tester](http://milkcocoa-tester.netlify.com/)を使い，プッ
  シュしたデータがストアされるか確認する．
  - [Milkcocoa Tester](http://milkcocoa-tester.netlify.com/)にアクセス
  して，app_id に上記のIDをdatastore name に "R2CA_DATA"と入力する．
  - KEY に 'LED', VALUEに"ON"を入れてPUSHする．
  - データストアに"R2CA_DATA"を指定して内容をチェックしてpushしたデータが格納されているか確認する
  
  
# データをPushしてみる

まずMilkcocoaにデータをPushしてみます．

R2CAのパッケージの\examples\Milkcocoa_basicにあるサンプルを実行します．

まずexamples_gdef.hを開き，アクセスポイントの情報をSSIDとPASSWORDに設定します．

```\examples\examples_gdef.h
#define SSID       ""
#define PASSWORD   ""
```

次にapp_idをMILKCOCOA_APP_IDにdatastore name をMILKCOCOA_DATASTOREに設定します．

```\examples\examples_gdef.h
#define MILKCOCOA_APP_ID      ""
#define MILKCOCOA_DATASTORE   ""
```

マクロはMILKCOCOA_PUSHのみを有効にします．MILKCOCOA_ONは無効としておきます．

```\examples\Milkcocoa_basic\r2ca_app.cpp
#define MILKCOCOA_PUSH
//#define MILKCOCOA_ON
```

設定後，同じフォルダにあるdo_run.bat をダブルクリックするとビルドが実施され，ターゲットに書き込まれ実行が開始されます．

コンソールには次のように表示され，LEDのKeyで"ON"と"OFF"を繰り返し実行しています．

![WS000023.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/da30eb21-6011-629a-a2c7-37783398483d.jpeg)


Milkcocoaにログインしてデータストアを見るとデータがPUSHされていることが確認できます．

![WS000024.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/b225a8fd-f4e9-b452-8920-9794f0299103.jpeg)




# データをOnしてみる

次にMilkcocoaからデータ送られた場合に特定の処理を実行するプログラムを実行します．Pushと同じファイルに次の変更を行います．

マクロはMILKCOCOA_ONを有効とします．MILKCOCOA_PUSHはOFFとします．


```\examples\Milkcocoa_basic\r2ca_app.cpp
//#define MILKCOCOA_PUSH
#define MILKCOCOA_ON
```

設定後，同じフォルダにあるdo_run.bat をダブルクリックするとビルドが実施され，ターゲットに書き込まれ実行が開始されます．

コンソールには次のように表示され，Onを待つ状態となります．

![WS000025.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/925b1236-3f1b-81ee-18a0-799137af6995.jpeg)


[Milkcocoa Tester](http://milkcocoa-tester.netlify.com/)にアクセスして，app_id に上記のIDをdatastore name に "R2CA_DATA"と入力する．KEY に 'LED', VALUEに"ON"を入れてPUSHします．

PUSHされたデータがコンソールに表示され，ボードの上のLEDが変更されます．


![WS000026.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/25b21c23-8f84-b7f1-96e2-258497510b62.jpeg)


# プログラム


初期化処理は基本的には， [Wifi通信](http://qiita.com/exshonda/items/914b7df0efd36866fd98)と同様です．

Milkcocoa用にネット接続用のclientを指定してMilkcocoaクラスのmilkcocoaを宣言しています．初期化は，wifiの初期化後にONする場合は，"push"された際に実行する関数を登録します．この例ではonpush()関数を登録しています．

```\examples\Milkcocoa_basic\r2ca_app.cpp

#define MILKCOCOA_SERVERPORT  1883

ESP8266Client wifi_client;

const char MQTT_SERVER[] PROGMEM    = MILKCOCOA_APP_ID ".mlkcca.com";
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ MILKCOCOA_APP_ID;

Milkcocoa milkcocoa = Milkcocoa(&wifi_client, MQTT_SERVER, MILKCOCOA_SERVERPORT, MILKCOCOA_APP_ID, MQTT_CLIENTID);

void setup()
{
    int ret;
    
    Serial.begin(115200);
    Serial.print("Milkcocoa SDK demo");

    // Connect to WiFi access point.
    Serial.println(); Serial.println();
    Serial.print("Connecting to ");
    Serial.println(SSID);

    ret = WiFi.begin(Serial5, 115200);

    if(ret == 1) {
        Serial.print("Cannot communicate with ESP8266.");
        while(1);        
    } else if(ret == 2) {
        Serial.println("FW Version mismatch.");
        Serial.print("FW Version:");
        Serial.println(WiFi.getVersion().c_str());
        Serial.print("Supported FW Version:");
        Serial.println(ESP8266_SUPPORT_VERSION);
        while(1);
    } else {
        Serial.print("begin ok\r\n");
    }

    Serial.print("FW Version:");
    Serial.println(WiFi.getVersion().c_str());
    
    if (WiFi.setOprToStation()) {
        Serial.print("to station ok\r\n");
    } else {
        Serial.print("to station err\r\n");
    }
    
    if (WiFi.joinAP(SSID, PASSWORD)) {
        Serial.print("Join AP success\r\n");
        Serial.print("IP: ");
        Serial.println(WiFi.getLocalIP().c_str());    
    } else {
        Serial.print("Join AP failure\r\n");
    }

    if (WiFi.stopServer()) {
        Serial.print("Stop server ok\r\n");
    } else {
        Serial.print("Stop server err\r\n");
    }        
    
    if (WiFi.disableMUX()) {
        Serial.print("single ok\r\n");
    } else {
        Serial.print("single err\r\n");
    }
    
#ifdef MILKCOCOA_ON
    if (milkcocoa.on(MILKCOCOA_DATASTORE, "push", onpush)){
        Serial.println("milkcocoa on sucesss");   
    }
    else {
        Serial.println("milkcocoa on failure");   
    }

    pinMode(13, OUTPUT);
    digitalWrite(13, LOW);   
#endif /* MILKCOCOA_ON  */

    Serial.println("setup end\r\n");
}
```

メイン処理を見ていきます．MILKCOCOA_PUSHのifdefで囲まれている箇所がpushのための箇所です．

pushの流れとしては，Dataelementを作成してkeyとvalueを設定します．このサンプルでは，周期毎にONとOFFを繰り返しています．次にmilkcocoa.push()によりサーバーにpushします．

ONは，毎周期milkcocoa.loop()を実行します．新たなデータがpush()があれば，ここから，後述のonpush()関数が呼び出されます．

```\examples\Milkcocoa_basic\r2ca_app.cpp
#ifdef MILKCOCOA_PUSH
bool req_led_on = false;
#endif /* MILKCOCOA_PUSH */

void loop() {
    int8_t ret;
    int8_t push_ret;
    
#ifdef MILKCOCOA_ON
    while((ret = milkcocoa.loop(1)) != 0) {
        Serial.println("Milkcocoa.loop() connection error.");
        Serial.println(milkcocoa.connectErrorString(ret));
        Serial.println(ret);
        Serial.println("Retrying MQTT connection in 5 seconds...");
        delay(5000);
    }
    Serial.print(".");
#endif /* MILKCOCOA_ON */
    
#ifdef MILKCOCOA_PUSH
    DataElement elem = DataElement();
    
    if(req_led_on) {
        Serial.println("Push data to Milkcocoa : Key:LED, Value:ON");
        elem.setValue("LED", "ON");
    }else {
        Serial.println("Push data to Milkcocoa : Key:LED, Value:OFF");        
        elem.setValue("LED", "OFF");
    }
    
    do {
        push_ret = milkcocoa.push(MILKCOCOA_DATASTORE, &elem);
        if (push_ret != 0) {
            Serial.println("Milkcocoa.push() error.");
            Serial.println(milkcocoa.pushErrorString(push_ret));
            Serial.println(push_ret);
            Serial.println("Retrying MQTT push in 5 seconds...");
            delay(5000);
        }        
    }while(push_ret != 0);
    
    Serial.println("Push success.");
    req_led_on = (req_led_on)? false : true;
    delay(2000);    
#endif /* MILKCOCOA_PUSH */
    
    if (Serial.available() > 0) {
        Serial.read();
        Serial.print("Pause  : Input any char to continue.\n\r");
        while(!(Serial.available() > 0));
        Serial.print("Resume.\n\r");
        Serial.read();
    }    
}
```

初期化時に登録したonpush()関数はいわゆるコールバック関数で，サーバーからデータがpushされると呼び出されます．データは引数で与えられ，keyを指定するとそのデータを取得することができます．この例ではLEDのキーを取得して"ON"ならLEDを点灯，"OFF"なら，LEDを消灯ています．

```\examples\Milkcocoa_basic\r2ca_app.cpp
#ifdef MILKCOCOA_ON
void onpush(DataElement *pelem) {
    char *data;
    if(!pelem->getString("LED", &data)) {
        Serial.print("onpush : key LED is not found.");
        return;
    };
    Serial.print("onpush : {LED, ");
    Serial.write(data);
    Serial.println("}.");
    if (strcmp(data, "ON") == 0) {
        Serial.println("LED : ON!");
        digitalWrite(13, HIGH);
    }
    else if(strcmp(data, "OFF") == 0) {
        Serial.println("LED : OFF!");
        digitalWrite(13, LOW);
    }
}
#endif /* MILKCOCOA_ON */
```

# おわりに

Milkcocoaを使うとリアルタイムにデータがやりとり可能です．ブラウザ等でpush/onをする方法もあるのでこれらと組み合わせると様々なアプリケーションが実現できるかと思います．

# おまけ
IoT向けのシールドを作っています．センサー等を付けるには[GROVE](http://www.seeedstudio.com/depot/Grove-Starter-Kit-V3-p-1855.html)がよいのですが，M0のIOは3.3Vなので，動作しないモジュールが多いという問題があります．あと，WifiやSDがないのも気になりましたので，ボードを起こしました(正確には起こしてもらいました)．

量産(?)前なのでご意見がある方はお知らせ下さい．

表面はGROVEコネクタがついています．5Vに変換しているので，殆どのモジュールが使えます．

<img width="400" alt="aFullSizeRender.jpg.jpg" src=https://qiita-image-store.s3.amazonaws.com/0/106870/6ab1d498-a67a-1a31-874d-60d47cf499ad.jpeg>

裏面にはWifi(ESP8266)とSDソケットがあります．

<img width="400" alt="aFullSizeRender.jpg.jpg" src=https://qiita-image-store.s3.amazonaws.com/0/106870/fdcb03ad-fcc4-ffaa-b3f8-c5837381b185.jpeg>

表面はフラットにしているので．ブレッドボードものります．

<img width="400" alt="aFullSizeRender.jpg.jpg" src=https://qiita-image-store.s3.amazonaws.com/0/106870/1944d1eb-940b-2f5e-db93-f8c853f1a889.jpeg>