M5StackでAmazon FreeRTOSを使用する５（shadow編）

# はじめに

Amazon FreeRTOSでデバイスを管理しましょう。シャドウ（AWS IoT Device Shadows）を使用してデバイスの状態を参照できるようにしてみます。Amazon FreeRTOSをインストールしたM5Stackの、ファームウェアのバージョンと最後にネットワークに接続した日時が参照できるようにしてみましょう。

今回は、起動時にAmazon FreeRTOSで提供されているHTTPSライブラリーを使用して、HTTPでntpサーバーから時刻を取得してみます。ntpは [国立研究開発法人 情報通信研究機構のntpサーバー](http://www.nict.go.jp/JST/http.html) を利用します。

最初に接続します。ほぼFreeRTOSのサンプルそのままですが、HTTPSではなくHTTPでアクセスしているのでシンプルです。


```cpp:
const char HTTPS_SERVER_ADDRESS[18] = "ntp-a1.nict.go.jp";
const int HTTP_TIMEOUT_MS = 10000;

IotHttpsConnectionInfo_t connInfo = IOT_HTTPS_CONNECTION_INFO_INITIALIZER;
IotHttpsConnectionHandle_t connHandle = IOT_HTTPS_CONNECTION_HANDLE_INITIALIZER;

/*-----------------------------------------------------------*/
/**
 * @brief HTTP connect.
 *
 * @param[in] pNetworkInterface Network Interface.
 *
 * @return void.
 */
static void prvHTTPSConnect( IotNetworkInterface_t* pNetworkInterface )
{
    IotHttpsClient_Init();

// An initialized network interface.
//    IotNetworkInterface_t* pNetworkInterface;


// Parameters to HTTPS Client connect.
    uint8_t* pConnUserBuffer = (uint8_t*)malloc(connectionUserBufferMinimumSize + 512);
    // Set the connection configuration information.
    connInfo.pAddress = HTTPS_SERVER_ADDRESS;
    connInfo.addressLen = strlen(HTTPS_SERVER_ADDRESS);
    connInfo.port = 80;
    connInfo.flags = IOT_HTTPS_IS_NON_TLS_FLAG;
    connInfo.pAlpnProtocols = NULL;
    connInfo.alpnProtocolsLen = 0;
    connInfo.pCaCert = NULL;
    connInfo.caCertLen = 0;
    connInfo.userBuffer.pBuffer = pConnUserBuffer;
    connInfo.userBuffer.bufferLen = connectionUserBufferMinimumSize + 512;
    connInfo.pClientCert = NULL;
    connInfo.clientCertLen = 0;
    connInfo.pPrivateKey = NULL;
    connInfo.privateKeyLen = 0;
    connInfo.pNetworkInterface = pNetworkInterface;
    IotHttpsReturnCode_t returnCode = IotHttpsClient_Connect(&connHandle, &connInfo);
    if( returnCode == IOT_HTTPS_OK )
    {
        configPRINTF( ( "Connected.\r\n" ) );
    }
    else
    {
        configPRINTF( ( "Connection error.\r\n" ) );
        configPRINTF( ( "Return code: %d\r\n", returnCode ) );
    }
}
```

接続に成功したら、時刻をサーバーから取得します。

```cpp:
/*-----------------------------------------------------------*/
/**
 * @brief HTTP request.
 *
 * @param[in] xMethod Request method.
 * @param[in] pcPath Request path.
 * @param[in] ulPathLength Request path length.
 *
 * @return void.
 */
static void prvHTTPRequest( IotHttpsMethod_t xMethod, 
                                   const char *pcPath, 
                                   uint32_t ulPathLength )
{
    IotHttpsReturnCode_t xHTTPClientResult;
    IotHttpsRequestInfo_t xHTTPRequestInfo = IOT_HTTPS_REQUEST_INFO_INITIALIZER;
    IotHttpsResponseInfo_t xHTTPResponseInfo = IOT_HTTPS_RESPONSE_INFO_INITIALIZER;
    IotHttpsRequestHandle_t xHTTPRequest = IOT_HTTPS_REQUEST_HANDLE_INITIALIZER;
    IotHttpsResponseHandle_t xHTTPResponse = IOT_HTTPS_RESPONSE_HANDLE_INITIALIZER;
    IotHttpsSyncInfo_t xHTTPSyncRequestInfo = IOT_HTTPS_SYNC_INFO_INITIALIZER;
    IotHttpsSyncInfo_t xHTTPSyncResponseInfo = IOT_HTTPS_SYNC_INFO_INITIALIZER;

    uint8_t ucHTTPRequestUserBuffer[ requestUserBufferMinimumSize + 512 ];
    uint8_t cHTTPRequestBodyBuffer[ 512 ];
    uint8_t ucHTTPResponseUserBuffer[ responseUserBufferMinimumSize + 512 ];
    uint8_t ucHTTPResponseBodyBuffer[ 256 ];

	/************************** HTTP request setup. ***************************/

    if( ( xMethod == IOT_HTTPS_METHOD_PUT ) || ( xMethod == IOT_HTTPS_METHOD_POST ) )
    {
        xHTTPSyncRequestInfo.pBody = ( uint8_t* )cHTTPRequestBodyBuffer;
        xHTTPSyncRequestInfo.bodyLen = sizeof( cHTTPRequestBodyBuffer );
    }
    else
    {
        xHTTPSyncRequestInfo.pBody = NULL;
        xHTTPSyncRequestInfo.bodyLen = 0;
    }

    xHTTPRequestInfo.pHost = HTTPS_SERVER_ADDRESS;
    xHTTPRequestInfo.hostLen = sizeof( HTTPS_SERVER_ADDRESS ) - 1;
    xHTTPRequestInfo.pPath = pcPath;
    xHTTPRequestInfo.pathLen = ulPathLength;
    xHTTPRequestInfo.method = xMethod;
    xHTTPRequestInfo.isNonPersistent = false;
    xHTTPRequestInfo.userBuffer.pBuffer = ucHTTPRequestUserBuffer;
    xHTTPRequestInfo.userBuffer.bufferLen = sizeof( ucHTTPRequestUserBuffer ) - 1;
    xHTTPRequestInfo.isAsync = false;
    xHTTPRequestInfo.u.pSyncInfo = &xHTTPSyncRequestInfo;

    xHTTPClientResult = IotHttpsClient_InitializeRequest( &xHTTPRequest, &xHTTPRequestInfo );

    if( xHTTPClientResult == IOT_HTTPS_OK )
    {
        configPRINTF( ( "Initialize ok.\r\n" ) );
    }
    else
    {
        configPRINTF( ( "Initialize error.\r\n" ) );
        configPRINTF( ( "Result: %d\r\n", xHTTPClientResult ) );
    }

    /************************** HTTP response setup. **************************/

    memset( ucHTTPResponseBodyBuffer, 0, sizeof( ucHTTPResponseBodyBuffer ) );

    if( xMethod == IOT_HTTPS_METHOD_HEAD )
    {
        xHTTPSyncResponseInfo.pBody = NULL;
        xHTTPSyncResponseInfo.bodyLen = 0;
    }
    else
    {
        xHTTPSyncResponseInfo.pBody = ucHTTPResponseBodyBuffer;
        xHTTPSyncResponseInfo.bodyLen = sizeof( ucHTTPResponseBodyBuffer );
    }

    xHTTPResponseInfo.userBuffer.pBuffer = ucHTTPResponseUserBuffer;
    xHTTPResponseInfo.userBuffer.bufferLen = sizeof( ucHTTPResponseUserBuffer );
    xHTTPResponseInfo.pSyncInfo = &xHTTPSyncResponseInfo;

    /*************************** Send HTTP request. ***************************/

    /* This synchronous send function blocks until the full response is received
     * from the network. */
    xHTTPClientResult = IotHttpsClient_SendSync( connHandle, 
                                       xHTTPRequest, 
                                       &xHTTPResponse, 
                                       &xHTTPResponseInfo, 
                                       HTTP_TIMEOUT_MS );

    char    *lines[ MAXITEM ];
    int cnt = split( (char*)&ucHTTPResponseBodyBuffer, "\n" , lines );
    for( int i = 0; i < cnt; i++ ) {
        if( lines[i][0] != '<' )
        {
        	set_clock( lines[i] );
        }
    }

}
```

取得した時刻や、ファームウェアのバージョンでシャドウを更新します。

```cpp:
/**
 * @brief Send the Shadow updates that will trigger the Shadow callbacks.
 *
 * @param[in] pDeltaSemaphore Used to synchronize Shadow updates with the delta
 * callback.
 * @param[in] mqttConnection The MQTT connection used for Shadows.
 * @param[in] pThingName The Thing Name for Shadows in this demo.
 * @param[in] thingNameLength The length of `pThingName`.
 *
 * @return `EXIT_SUCCESS` if all Shadow updates were sent; `EXIT_FAILURE`
 * otherwise.
 */
static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore,
                               IotMqttConnection_t mqttConnection,
                               const char * const pThingName,
                               size_t thingNameLength )
{
    int status = EXIT_SUCCESS;
    int32_t i = 0, desiredState = 0;
    AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING;
    AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER;

    /* A buffer containing the update document. It has static duration to prevent
     * it from being placed on the call stack. */
    static char pUpdateDocument[ 100 ] = { 0 };

    /* Set the common members of the Shadow update document info. */
    updateDocument.pThingName = pThingName;
    updateDocument.thingNameLength = thingNameLength;
    updateDocument.u.update.pUpdateDocument = pUpdateDocument;
    updateDocument.u.update.updateDocumentLength = EXPECTED_REPORTED_JSON_SIZE;

    /* Publish Shadow updates at a set period. */
    for( i = 1; i <= AWS_IOT_DEMO_SHADOW_UPDATE_COUNT; i++ )
    {
        /* Toggle the desired state. */
        desiredState = !( desiredState );

        /* Generate a Shadow desired state document, using a timestamp for the client
         * token. To keep the client token within 6 characters, it is modded by 1000000. */
        status = sprintf( pUpdateDocument,
                          SHADOW_REPORTED_JSON,
                          APP_VERSION_MAJOR,
                          APP_VERSION_MINOR,
                          APP_VERSION_BUILD,
                          ctime(&tv_now.tv_sec),
                          ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) );
        updateDocument.u.update.updateDocumentLength = status;

        IotLogInfo( "Sending Shadow update %d of %d: %s",
                    i,
                    AWS_IOT_DEMO_SHADOW_UPDATE_COUNT,
                    pUpdateDocument );

        /* Send the Shadow update. Because the Shadow is constantly updated in
         * this demo, the "Keep Subscriptions" flag is passed to this function.
         * Note that this flag only needs to be passed on the first call, but
         * passing it for subsequent calls is fine.
         */
        updateStatus = AwsIotShadow_TimedUpdate( mqttConnection,
                                                 &updateDocument,
                                                 AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS,
                                                 TIMEOUT_MS );

        /* Check the status of the Shadow update. */
        if( updateStatus != AWS_IOT_SHADOW_SUCCESS )
        {
            IotLogError( "Failed to send Shadow update %d of %d, error %s.",
                         i,
                         AWS_IOT_DEMO_SHADOW_UPDATE_COUNT,
                         AwsIotShadow_strerror( updateStatus ) );

            status = EXIT_FAILURE;
            break;
        }
        else
        {
            IotLogInfo( "Successfully sent Shadow update %d of %d.",
                        i,
                        AWS_IOT_DEMO_SHADOW_UPDATE_COUNT );

            /* Wait for the delta callback to change its state before continuing. */
            if( IotSemaphore_TimedWait( pDeltaSemaphore, TIMEOUT_MS ) == false )
            {
                IotLogError( "Timed out waiting on delta callback to change state." );

                status = EXIT_FAILURE;
                break;
            }
        }

        IotClock_SleepMs( AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS );
    }

    return status;
}
```

AWS IoTの管理画面で確認します。

![aws_shadow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/e28f5222-b8ed-ff4c-aa49-b9ad35afaf93.png)

# サンプルソースとビルド

サンプルソースは [こちら](https://github.com/fukuen/m5stack-freertos-shadow) を参照してください。

ビルドは、

```bash:
git clone https://github.com/fukuen/m5stack-freertos-shadow --recursive
```

ダウンロードしたディレクトリーに移動して、アプリケーションをビルドします。

```bash:
cd m5stack-freertos-tft
cmake -DCMAKE_TOOLCHAIN_FILE=amazon-freertos/tools/cmake/toolchains/xtensa-esp32.cmake -GNinja -S . -B build
cd build
ninja
```

ビルドが完了したら、フラッシュします。

```bash:
cd ..
idf.py flash
```

シリアルコンソールでモニターしてみます。

```bash:
idf.py monitor
```


# メモ

[第１回 Amazon FreeRTOSインストール](https://qiita.com/fukuebiz/items/54f765d12c084ceea392)
[第２回 Lチカ](https://qiita.com/fukuebiz/items/3b803912bf51b1f46d79)
[第３回 TFT](https://qiita.com/fukuebiz/items/732296b84e68a1a08d41)
[第４回 SDカード](https://qiita.com/fukuebiz/items/e7010b1d84131e93a0a3)