ESP8266 からAWSへMQTT を投げる

[こちらの記事](https://qiita.com/YumeYakata/items/3fc190feb5164afa9c26)の続きです。ESP8266 からAWSへMQTT を投げるプログラムです。[このプログラム](https://github.com/odelot/aws-mqtt-websockets/tree/master/examples/aws-mqtt-websocket-example-paho)を参考にさせていただきました。ちゃんと解説するにはよくわかっていないところ多々なのですが、このままで動作することを確認しています。

### ヘッダー
``` 
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>

//Wifi connection
ESP8266WiFiMulti WiFiMulti;
#define MAXTRY    100

//AWS
#include "sha256.h"
#include "Utils.h"

//WEBSockets
#include <Hash.h>
#include <WebSocketsClient.h>

//MQTT PAHO
#include <SPI.h>
#include <IPStack.h>
#include <Countdown.h>
#include <MQTTClient.h>

//AWS MQTT Websocket
#include "Client.h"
#include "AWSWebSocketClient.h"
#include "CircularByteBuffer.h"

//MQTT config
const int maxMQTTpackageSize = 512;
const int maxMQTTMessageHandlers = 1;

AWSWebSocketClient awsWSclient(1000);
IPStack ipstack(awsWSclient);
MQTT::Client<IPStack, Countdown, maxMQTTpackageSize, maxMQTTMessageHandlers> *client = NULL;
```
### AWS IoT Configuration
AWS IoTのConfiguration を記載します。

```
//AWS IOT config, change these:
char aws_endpoint[]    = "abcdefghijklmg.iot.us-west-2.amazonaws.com";    //your-endpoint.iot.eu-west-1.amazonaws.com";
char aws_key[]         = "AXXXXXXXXXXXXXXXXXXQ";                          //"your-iam-key";
char aws_secret[]      = "9dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTQ";      //"your-iam-secret-key";
char aws_region[]      = "us-west-2";                                     //eu-west-1";
const char* aws_topic  = "TOPIC/VALUE";                                  //"$aws/things/your-device/shadow/update";
```

### setup()関数
setup()関数では、
1. Wifi を接続する。
2. websocket と mqtt レイヤーを接続する connect() 関数を呼ぶ
3. 接続がOKならば、subscribe() 関数でメッセージのサブスクライバー（購読者）であることを宣言します。

```
void setup() {
    Serial.begin(9600);
    Serial.println ("connecting to wifi");
    WiFiMulti.addAP("SSID", "PASSWORD");    // put your setup code here, to run once:
    int iwait=0;
    while(WiFiMulti.run() != WL_CONNECTED) {    //wait for connection
        delay(100);
        Serial.print (".");
        iwait++;
        if(iwait>=MAXTRY){ 
            Serial.println ("\wifi nconnect error");    //connect NG
            break;
        }
    }
    if(WiFiMulti.run() == WL_CONNECTED){            //connect OK
        Serial.println ("\nwifi connected");
        //Fill AWS parameters    
        awsWSclient.setAWSRegion(aws_region);
        awsWSclient.setAWSDomain(aws_endpoint);
        awsWSclient.setAWSKeyID(aws_key);
        awsWSclient.setAWSSecretKey(aws_secret);
        awsWSclient.setUseSSL(true);
        if (connect ()){
            subscribe ();
        }
    }
}
```
### connect()関数
connect()関数では
1. MQTT Client をCreate する。
2. websocket layerを接続する。
3. mqtt layerを接続する。

```
//----------------------------------------------------------------
//# of connections
long connection = 0;
int port = 443;

//----------------------------------------------------------------
//connects to websocket layer and mqtt layer
bool connect () {
    // create MQTT client
    if (client == NULL) {
        client = new MQTT::Client<IPStack, Countdown, maxMQTTpackageSize, maxMQTTMessageHandlers>(ipstack);
    } else {
        if (client->isConnected ()) {    
            client->disconnect ();
        }  
        delete client;
        client = new MQTT::Client<IPStack, Countdown, maxMQTTpackageSize, maxMQTTMessageHandlers>(ipstack);
    }

    // delay is not necessary... it just help us to get a "trustful" heap space value
    delay (1000);
    // debug print
    Serial.print (millis ());
    Serial.print (" - conn: ");
    Serial.print (++connection);
    Serial.print (" - (");
    Serial.print (ESP.getFreeHeap ());
    Serial.println (")");

    // connects to websocket layer
    int rc = ipstack.connect(aws_endpoint, port);
    if (rc != 1)
    {
        Serial.println("error connection to the websocket server");
        return false;
    } else {
        Serial.println("websocket layer connected");
    }

    //connects to mqtt layer
    Serial.println("MQTT connecting");
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    data.MQTTVersion = 3;
    char* clientID = generateClientID ();
    data.clientID.cstring = clientID;
    rc = client->connect(data);
    delete[] clientID;

    if (rc != 0)
    {
        Serial.print("error connection to MQTT server");
        Serial.println(rc);
        return false;
    }
    // sucsess connection to mqtt
    Serial.println("MQTT connected");
    return true;
}
```
### subscribe ()関数
ひとつのTOPIC の購読者であることを宣言します。

```
//----------------------------------------------------------------
// subscribe to a mqtt topic
void subscribe () {
   // subscript to a topic
   // messageArrived is a callback function when MQTT message received
    int rc = client->subscribe(aws_topic, MQTT::QOS0, messageArrived);
    if (rc != 0) {
        Serial.print("rc from MQTT subscribe is ");
        Serial.println(rc);
        return;
    }
    Serial.println("MQTT subscribed");
}
```
### generateClientID()関数
Client ID をランダム関数から作成します。

```
//----------------------------------------------------------------
// generate random mqtt clientID
char* generateClientID () {
    char* cID = new char[23]();
    for (int i=0; i<22; i+=1)
        cID[i]=(char)random(1, 256);
    return cID;
}
```

### メッセージ受信
メッセージを受け取るコールバック関数です。受け取ったメッセージをプリントしています。

```
//----------------------------------------------------------------
// count messages arrived
int arrivedcount = 0;

//----------------------------------------------------------------
//callback to handle mqtt messages
void messageArrived(MQTT::MessageData& md)
{
    MQTT::Message &message = md.message;

    Serial.print("Message ");
    Serial.print(++arrivedcount);
    Serial.print(" arrived: qos ");
    Serial.print(message.qos);
    Serial.print(", retained ");
    Serial.print(message.retained);
    Serial.print(", dup ");
    Serial.print(message.dup);
    Serial.print(", packetid ");
    Serial.println(message.id);
    Serial.print("Payload ");
    char* msg = new char[message.payloadlen+1]();
    memcpy (msg,message.payload,message.payloadlen);
    Serial.println(msg);
    delete msg;
}
```
### メッセージ送信
メッセージの送信を行います。この送信方法ではJSON形式ではありません。

```
//----------------------------------------------------------------
//send a message to a mqtt topic
void sendmessageNumber (int num) {
    //send a message
    MQTT::Message message;
    String strbun;
    String snum = String(num);
    const char *buf;
  
    strbun="{\"Temporather\":\"";
    strbun+=snum;
    strbun+="\"}";
    buf = strbun.c_str();   
    Serial.println (buf);   // kakitest

    message.qos = MQTT::QOS0;
    message.retained = false;
    message.dup = false;
    message.payload = (void*)buf;
    message.payloadlen = strlen(buf)+1;
    int rc = client->publish(aws_topic, message); 
}
```

### ループ関数

awsWSclient.connected ()をチェックしてclient->yield()を呼ぶ、この行が無いとメッセージを受け取れません。sendmessageNumber(int num)で値を送信します。

```
//----------------------------------------------------------------
int msend_count=16;
void loop()
{
  //keep the mqtt up and running
    if (awsWSclient.connected ()) {    
        client->yield();
    } else {
    //handle reconnection
        if (connect ()){
            subscribe ();      
        }
    }
    if(!(msend_count--)){
        sendmessageNumber(1234);
        msend_count=16;
    }
}
```