Pahoを用いたAWS IoT Core通信プログラム

## 今回の目標
Eclipse Pahoを用いてAWS IoT CoreとMQTT通信するプログラムをC言語で実装することを目指す。
まずは同期でAWS IoT Coreにメッセージをpublishするプログラムの作成を行う(Payloadも単純な文字列)。

非同期通信、メッセージのsubscribe、json形式のメッセージ作成はまた別の機会に。

## 実施環境
- Virtualbox上にUbuntuを構築して実施
    - VirtualBox 6.1.30
    - Ubuntu 20.04.3 LTS
- AWS IoT Coreは2021/11時点。

(最終的にはRaspberry Piなどのデバイスを用いる予定)


## 事前準備
### pahoのインストール
最初にmqttクライアントライブラリであるpahoをインストールする。

```sh
sudo apt install build-essential gcc make cmake cmake-gui cmake-curses-gui
sudo apt install fakeroot fakeroot devscripts dh-make lsb-release
sudo apt install libssl-dev

git clone https://github.com/eclipse/paho.mqtt.c.git
cd paho.mqtt.c/
make
sudo make install
```

インストールが成功していれば、/usr/local/lib以下にlibmqtt*.soが、/usr/local/include以下にMQTT*.hが存在する。

### AWS IoT Coreへの「モノ」の登録
AWS IoT Core内部の「管理」→「モノ」を選択する。
「モノ」のページ内部から「モノの作成」を選択し、通信先となるモノを作成する。今後も見据え、名前付きシャドウも作成した。
モノと同時に作成したポリシーは以下の通り。

```json
{
  "Version": "YYYY-MM-XX",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:XXX:client/myDevice"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": "arn:aws:iot:XXX:topic/myIoT/sensor"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": "arn:aws:iot:XXX:topicfilter/myIoT/cmd"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": "arn:aws:iot:XXX:topic/myIoT/cmd"
    },
    {
      "Effect": "Allow",
      "Action": "iot:UpdateThingShadow",
      "Resource": "arn:aws:iot:XXX:thing/myDeviceShadow"
    },
    {
      "Effect": "Allow",
      "Action": "iot:GetThingShadow",
      "Resource": "arn:aws:iot:XXX:thing/myDeviceShadow"
    }
  ]
}
```

モノの作成後、ルート証明書など一式をダウンロードしておく。
あとで作成する通信プログラム内でpahoからこれらのファイルを読み取って通信する。

### 通信プログラムの作成

pahoを用いたプログラムのソースコード。
[Paho MQTT C Client Library](https://eclipse.github.io/paho.mqtt.c/MQTTClient/html/index.html)の"Synchronous publication example"をベースに作成した。

```c:pub_sync.c
/* pub_sync.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#include "mqtt_var.h"

int main(int argc, char* argv[]){
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = conn_opts_initial;
    MQTTClient_SSLOptions ssl_opts = ssl_opts_initial;
    MQTTClient_message pubmsg = {
         /* strcture detail is described on "MQTTClient.h" */
        {'M', 'Q', 'T', 'M'}, // the eyecather(must be MQTM)
        1,      // version number of the structure
        0,      // payload lenghth in bytes
        NULL,   // payload of MQTT message (void*)
    };
    MQTTClient_deliveryToken token;
    int rc;

    /* MQTTCLIENT_PERSISTENCE_NONE -> memory-based persistence mechanism */
    if((rc = MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS){
        printf("Failed to create client, return code %d\n", rc);
        exit(EXIT_FAILURE);
    }

    conn_opts.ssl = &ssl_opts;

    if((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS){
        printf("Failed to connect, return code %d\n", rc);
        exit(EXIT_FAILURE);
    }

    char* payload = "This is MQTT message";
    pubmsg.payload = (void*)payload;
    pubmsg.payloadlen = (int)strlen(payload);
    pubmsg.qos = QOS;
    pubmsg.retained = 0;
    if((rc = MQTTClient_publishMessage(client, PUB_TOPIC, &pubmsg, &token)) != MQTTCLIENT_SUCCESS){
        printf("Failed to publsh message, return code %d\n", rc);
        exit(EXIT_FAILURE);
    }

    printf("Wait for up to %d seconds for publication of %s\n"
            "on topic %s for client with ClientID: %s\n",
            (int)(TIMEOUT / 1000), payload, PUB_TOPIC, CLIENTID);
    rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
    printf("Message with delivery token %d delivered\n", token);

    if((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS){
        printf("Failed to disconnect, return code %d\n", rc);
    }
    MQTTClient_destroy(&client);
    return rc;
}
```

```c:aws_cert.h
/* aws_cert.h */
#ifndef __AWS_CERT_H__
#define __AWS_CERT_H__

#define ADDRESS "ssl://XXX.amazonaws.com:8883"


#define CERFILE_PATH "<AWSからダウンロードした証明書ファイルディレクトリへのパス>"

#define root_ca CERFILE_PATH "AmazonRootCA1.pem"
#define certificate  CERFILE_PATH "XXX-certificate.pem.crt"
#define private_key  CERFILE_PATH "XXX-private.pem.key"
#define ca_path CERFILE_PATH "ca/"

#endif 
```

```c:mqtt_var.h
/* mqtt_var.h */
#ifndef __MQTT_VAR__
#define __MQTT_VAR__

#include "MQTTClient.h"
#include "aws_cert.h"

const char* CLIENTID = "myDevice";
const char* SUB_TOPIC = "myIoT/cmd";
const char* PUB_TOPIC = "myIoT/sensor";

const int QOS = 1;
const long TIMEOUT = 10000L;

MQTTClient_connectOptions conn_opts_initial = {
    /* strcture detail is described on "MQTTClient.h" */
    {'M', 'Q', 'T', 'C'}, // the eyecather(must be MQTC)
    8,      // version of structure
    60,     // keep alive interval
    1,      // clean session
    1,      // reliable
    NULL,   // MQTTClient_willOptions
    NULL,   // username
    NULL,   // password
    30,     // connection timeout
    0,      // retry Interval
    NULL,   // MQTTClient_SSLOptions
    0,      // the number of serverURI 
    NULL,   // serverURIs
    MQTTVERSION_DEFAULT, // MQTT version
    {NULL, 0, 0}, // Returned from the connect when the MQTT version used to connect is 3.1.1
    {0, NULL}, // binary password
    -1, // maximum number of messages in flight
    0,      // MQTT c5 clean start flag
    NULL,   // HTTP headers for websockets
    NULL,   // HTTP proxy for websockets
    NULL,   // HTTPs proxy for websockets
};

MQTTClient_SSLOptions ssl_opts_initial = {
    {'M', 'Q', 'T', 'S'}, // the eyecatcher(must be MQTS)
    3,      // struct version
    root_ca,   // trustStore (PEM format containig public digital certificates)
    certificate,   // keyStore (PEM format containing public certificate chain)
    private_key,   // private key
    NULL,   // password of private key
    NULL,   // the list of ciper suites
    1,      // true/false option to enbale verifaication of server certificate
    MQTT_SSL_VERSION_TLS_1_2, // SSL/TLS version to use
    1,      // whether to carry out post-connect checks
    ca_path,   // CA path
    NULL,   // callback function for OpenSSL error handler
    NULL,   // application-specific contex for OpenSSL error handler
    NULL,   // callback function for setting TLS-PSK options
    NULL,   //Application-specific contex for ssl_psk_cb
    0,      // disable default trust store
    NULL,   // protocol-list
    0,      // protocal length of protocol-list vector
};

#endif
```

ビルドコマンドは以下。libpaho-mqtt*.soのリンク設定を忘れずに行う。

```sh
gcc pub_sync.c -lpaho-mqtt3cs -o pub_sync
```

## 動作確認
AWS IoT Coreでは「MQTT テストクライアント」を用いることでメッセージ通信を確認することができる。
- 「トピックをサブスクライブする」からトピックフィルターを設定
    - フィルターとして「myIoT/#」を設定

正常に送信できれば、AWS IoT Core上で”This is MQTT message”がPayloadのメッセージ受信を確認することができる。

このときのクライアント側の出力は次のようになる。

```sh
$ ./pub_sync
Wait for up to 10 seconds for publication of This is MQTT message
on topic myIoT/sensor for client with ClientID: myDevice
Message with delivery token 1 delivered
```

## 次回にやること
今回はPayloadとして文字列をそのまま送信したため、AWS IoT Core上でエラーメッセージが発生する。
次回はこれを解決するために送信するデータをJSON形式に変換してAWS IoT Coreにデータ送信する。

## 参考
- [MQTT C Client for Posix and Windows](https://www.eclipse.org/paho/index.php?page=clients/c/index.php)
- [Paho MQTT C Client Library](https://eclipse.github.io/paho.mqtt.c/MQTTClient/html/index.html)