メインコンテンツに移動
日常で楽しむクラウドテクノロジー

スマートガーデニングの実現へ ! マイコンを活用したリーズナブルなお花の自動水やり機

2024-03-04 | Author : 宮本 篤

はじめに

こんにちは。IoT コンサルタントの宮本です。

先日、とあるイベントでお花の種を頂きました。私は、お花は鑑賞するのは好きなのですが、育てた経験はほとんどありません。ネットで軽く、お花の育て方を調べてから、種を蒔き、育ててみました。無事に芽は出てきたのですが、「水やりって、どのようなタイミングでやれば良いの ?」と疑問を持ちました。また、水やりを忘れてしまう日もあって、これは良くない、何とかしようと思い、「自動水やりシステム」を開発することにしました !

まずは、どんな機能を実現するのか、を明確にします。理想的には、下記のようなシステムを作りたい、と考えていました。

  • 土壌の水分量に基づいて、水を供給 / 停止する

  • 水分量に加えて、気温・湿度もインプット情報に加える

  • 花の育成状況をカメラで撮影し、日々の状態を記録する

  • 更に、機械学習を使って、撮像より、植物の成長度合いを解析し、最適な水やり周期にフィードバックする

全てを一度に作るのはハードルが高そうなので、今回は開発のハードルを下げて、1 番目の「土壌の水分量に基づいて、水を供給/停止する」のみを実現することにしました。

X ポスト » | Facebook シェア » | はてブ »

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

*ハンズオン記事およびソースコードにおける免責事項 »

builders.flash メールメンバー登録

builders.flash メールメンバー登録で、毎月の最新アップデート情報とともに、AWS を無料でお試しいただけるクレジットコードを受け取ることができます。

今すぐ登録 »

1. 開発の方針

前述のようなシンプルな仕様であれば、システムの作り方には様々な方法が考えられるかと思います。今回は、下記の方針を立て、開発することにしました。

1. できるだけデバイスを安価にする

デバイス側の選択肢には PC、ラズパイ、マイコンなどありますが、単純にデバイス単体の価格を比較すると、やはりマイコンが一番安価になります。ですので、デバイスにはマイコンを採用しました。

2. 柔軟性を向上さる

今回のように、水の供給/停止を判断するための水分量の閾値などは、試しながら調整することが想定されます。マイコンを使う場合には、デバイス側のソースコードの更新は、クラウド側に比べると比較的に作業コストがかかる場合があります。そのため、マイコン側のソースコードを更新するよりも、クラウド側を更新する方がシステムとしての柔軟性を高められる、と考え、システムの変動部は可能な限りクラウド側で実現する方針にしました。

3. 開発工数を削減する

機能を実現するためには、基本的にはソースコードでロジックを組む必要があります。一方で、AWS にはノーコードで機能を実現可能なサービスも存在しています。よって、クラウド側は、できるだけノーコードで開発し、開発工数を削減しました。

開発の方針をまとめます。

  • 「できるだけデバイスを安価にする」: デバイスには安価なマイコンを使用する
  • 「柔軟性を向上さる」: 変動部やロジックは、できるだけクラウド側で実現する
  • 「開発工数を削減する」: クラウド側はできるだけノーコードで開発する

2. 設計

2-1. システムの全体アーキテクチャ

今回開発するシステムの機能は、「土壌の水分量に基づいて、水を供給/停止する」だけであり、とてもシンプルです。この機能を実現するためのシステム構成を前述の開発方針を考慮して設計していきます。

  • デバイス側にはマイコンを使用するため、デバイスとクラウドとの通信には、比較的に処理負荷の小さい軽量プロトコルである MQTT を使ってクラウド側と通信する

  • AWS で MQTT を使う場合には、MQTT ブローカーの機能を持つ AWS IoT Core を使用する

  • クラウド側のロジックの実現には、ノーコードで開発可能な AWS IoT Events を活用する

アーキテクチャ図

2-2. デバイス側のハードウェア設計

デバイス側のハードウェアは、以下のものを使用します。

  • 制御用マイコンデバイス : 今回、比較的に広く利用されている ESP32 マイコンを活用することにしました。手元に M5StickC Plus があったので、それを使用します。

  • 水分量計 : 水分量系には、土壌の水分量の計測には、5 個入りで 1,000 円程度 ( 2024 年 2 月現在) と比較的に安価な DiyStudio 土壌湿度計 を使用します。

A close-up photo of a soil moisture sensor inserted into soil in a planter, illustrating smart gardening and IoT-based environmental monitoring.

水を供給するポンプ

  • 水を供給するポンプ : CQ出版社 Interface 2023年3月号 に掲載されている「土壌水分センサのアナログ電圧出力を A-D コンバータで読み取って表示する測定値をアナログ電圧や電流で出力するセンサ」の記事を参考に、WayinTop ミニ 小型ポンプ を選定しました。

A smart gardening prototype setup showing a transparent container with a submersible water pump, simple breadboard circuit, and battery pack. The Japanese label ポンプ highlights the pump used in the system.

2-3. システムの機能仕様

システムの機能仕様は、以下の通りです。

  • デバイスは、水分量センサー情報を周期的に AWS IoT Core に Publish する

  • クラウド側では、AWS IoT Core がデバイスから受信した水分量センサー情報を AWS IoT Events に送信する

  • AWS IoT Events では、受信した水分量センサー情報に基づいて、土壌の乾燥状態を判断しポンプの On/Off をデバイスに指示する

  • デバイスへのポンプの On/Off の指示は、Device Shadow を介して送信する

  • デバイスは、Device Shadow の値を読み取り、その指示に基づいて、ポンプを On/Off する

3. AWS IoT Core の設定

3-1. AWS IoT のモノの作成

デバイスを AWS IoT Core に接続するためにモノ (Thing) を作成します。AWS IoT Core のマネジメントコンソールから IoT のモノを作って、MQTT で Publish や Subscribe するためのポリシーを追加した後、接続に必要な証明書ファイルや秘密鍵ファイルを取得します。

  • AWS IoT Core のマネジメントコンソールから「モノ」を開きます。

    • モノを作成」ボタンを選択します。

    • 1 つのモノを作成」を選択します。

    • モノのプロパティを指定 画面では、 モノの名前m5stickc-plus とし、残りはデフォルトのままで 画面下部の「次へ」ボタンをクリックします。

  • デバイス証明書を設定 - オプション 画面が表示されます。

    • デバイス証明書 セクション にて「新しい証明書を自動生成 (推奨)」を選択します。

    • 次へ」ボタンをクリックします。

  • 証明書にポリシーをアタッチ - オプションの画面が表示されます。

    • ポリシー で 「ポリシーを作成」を選択します。

  • ブラウザの新規タブが開き、ポリシーを作成 の画面が表示されます

    • ポリシー名に m5stickc-plus-policy と入力します。

    • ポリシーステートメント」タブを選択し、「新しいステートメント」をクリックして、下記を設定していきます。本例では、東京リージョン (ap-northeast-1) を使用していますが、他のリージョンを使用する場合には、変更してください。また、アカウントIDは、ご自身の環境に合わせて変更してください。

    • 作成」ボタンを選択します。

ポリシー効果

ポリシー
アクション

ポリシーリソース

許可

iot:Connect

arn:aws:iot:ap-northeast-1:{アカウントID}:client/m5stickc-plus

許可

iot:Publish

arn:aws:iot:ap-northeast-1:{アカウントID}:topic/watering-machine/state,
arn:aws:iot:ap-northeast-1:{アカウントID}:topic/$aws/things/m5stickc-plus/shadow/update

許可

iot:Subscribe

arn:aws:iot:ap-northeast-1:{アカウントID}:topicfilter/$aws/things/m5stickc-plus/shadow/update/delta

許可

iot:Receive

arn:aws:iot:ap-northeast-1:{アカウントID}:topic/$aws/things/m5stickc-plus/shadow/update/delta

  • 証明書にポリシーをアタッチ - オプションの画面があるタブに戻ります。

    • さきほど作成した m5stickc-plus-policy を選択します。

    • モノを作成」ボタンを選択します。

  • 証明書とキーをダウンロード のモーダル画面が表示されます。

    • 以下のファイルをダウンロードします。

      • デバイス証明書 - <long-string>-certificate.pem.crt

      • プライベートキーファイル - <long-string>-private.pem.key

  • ダウンロードしたファイルは、後続の手順で使用します。

3-2. AWS IoT のルールの作成ンプルコードを GitHub から取得

AWS IoT Core が受け取ったメッセージをそのまま AWS IoT Events に渡すようにルールを設定します。AWS IoT Core デベロッパーガイドの「AWS IoT ルールの作成」を参考にルールを作成します。今回は、デバイスが Publish するメッセージを全て AWS IoT Events に渡すように設定します。

  • SQL ステートメント : SELECT * FROM ‘watering-machine/state’
  • Actions : Send a message to an IoT Events Input

4. AWS IoT Events の設定

水分量センサーの値に応じてポンプを On/Off するロジックは、 AWS IoT Events を使って実装します。

IoT Events では、ブラウザベースの GUI である AWS IoT Events コンソールを使って、ノーコードでロジックを実装することが可能です。IoT Events を使うと、下図のような探知器モデルを作成できます。

探知器モデルは、イベントの検知と対応アクションを定義する仕組みです。デバイスやシステムの異常をリアルタイムで監視し、特定の条件が満たされた際にアラートや自動処理をトリガーする、といった使い方ができます。

探知器モデルには、状態、移行イベント、アクションの 3 つの主要要素が含まれます。状態はデバイスの状態を表し、移行イベントは状態間の遷移を定義し、アクションは特定の状態で実行される処理を指定します。探知器モデルの設定方法を記載します。

State diagram for a smart gardening system showing the transitions between PumpOff, PumpOn, and Wait states, indicating event-driven automation for water pump control.

4-1. AWS IoT Events の⼊⼒の定義

デバイスが取得した水分量計のデータは、下記のようなフォーマットで AWS IoT Core に送信します。

json
{
  "moisture": 2522
}

AWS IoT Events の入力を作成

この JSON を input.json という名称でファイルに保存します。下記の手順で、AWS IoT Events の入力を作成します。

  • Create input」 ボタンを選択します。

  • 入力名moisture_input と入力します。

  • JSON ファイルのアップロード にて JSON ファイルを選択します。ここで、先ほど作成した input.json ファイルを選択します。

  • 作成」ボタンを押します。

4-2. AWS IoT Events が使用する IAM Role の作成

続いて、探知器モデル が使用する IAM Role を作成します。

この探知器モデルでは、デバイスから受信した水分量計の値に応じて、Device Shadow を介して、ポンプの On/Off を指示します。そのために、探知器モデルで Device Shadow のトピックに Publish できるように、以下の IAM Policy を持つ IAM Role を作成します。

IAM Role を作成

ここではロール名を IoTEvents_watering_machine_role とします。本例では、東京リージョン (ap-northeast-1) を使用していますが、他のリージョンを使用する場合には、変更してください。また、アカウント ID は、ご自身の AWS アカウントの ID に置き換えます。

json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:UpdateThingShadow"
            ],
            "Resource": "arn:aws:iot:your-region:{アカウントID}:thing/m5stickc-plus"
        }
    ]
}

信頼ポリシーを設定

IoT Events がこのロールを引き受けられるように、信頼ポリシー には下記を設定します。

json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "iotevents.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

4-3. AWS IoT Events 探知器の定義

探知器モデルアクション > 探知器モデルのインポートを選択し、以下のモデル watering_machine をインポートします。アカウント ID は、ご自身の AWS アカウントの ID に置き換えます。

watering_machine をインポート

アカウント ID は、ご自身の AWS アカウントの ID に置き換えます。

json
{
    "detectorModelDefinition": {
        "states": [
            {
                "stateName": "PumpOff",
                "onInput": {
                    "events": [
                        {
                            "eventName": "checkMoisture",
                            "condition": "$input.moisture_input.moisture  > 2000",
                            "actions": [
                                {
                                    "setVariable": {
                                        "variableName": "dryCounter",
                                        "value": "$variable.dryCounter + 1"
                                    }
                                }
                            ]
                        }
                    ],
                    "transitionEvents": [
                        {
                            "eventName": "to_pump_on",
                            "condition": "$variable.dryCounter > 10",
                            "actions": [],
                            "nextState": "PumpOn"
                        }
                    ]
                },
                "onEnter": {
                    "events": [
                        {
                            "eventName": "pump_off",
                            "condition": "true",
                            "actions": [
                                {
                                    "iotTopicPublish": {
                                        "mqttTopic": "$aws/things/m5stickc-plus/shadow/update",
                                        "payload": {
                                            "contentExpression": "'{\"state\": {\"desired\": \n{\"pumpOn\": 0 }}}'",
                                            "type": "JSON"
                                        }
                                    }
                                },
                                {
                                    "setVariable": {
                                        "variableName": "dryCounter",
                                        "value": "0"
                                    }
                                }
                            ]
                        }
                    ]
                },
                "onExit": {
                    "events": []
                }
            },
            {
                "stateName": "PumpOn",
                "onInput": {
                    "events": [
                        {
                            "eventName": "count_up",
                            "condition": "$input.moisture_input.moisture  < 1800",
                            "actions": [
                                {
                                    "setVariable": {
                                        "variableName": "wetCounter",
                                        "value": "$variable.wetCounter + 1"
                                    }
                                }
                            ]
                        }
                    ],
                    "transitionEvents": [
                        {
                            "eventName": "to_pump_off",
                            "condition": "(timeout(\"pumpOffTimer\")) || ($variable.wetCounter > 0)",
                            "actions": [
                                {
                                    "clearTimer": {
                                        "timerName": "pumpOffTimer"
                                    }
                                }
                            ],
                            "nextState": "Wait"
                        }
                    ]
                },
                "onEnter": {
                    "events": [
                        {
                            "eventName": "pump_on",
                            "condition": "true",
                            "actions": [
                                {
                                    "iotTopicPublish": {
                                        "mqttTopic": "$aws/things/m5stickc-plus/shadow/update",
                                        "payload": {
                                            "contentExpression": "'{\"state\": {\"desired\": {\"pumpOn\": 1 }}}'",
                                            "type": "JSON"
                                        }
                                    }
                                },
                                {
                                    "setTimer": {
                                        "timerName": "pumpOffTimer",
                                        "seconds": 60,
                                        "durationExpression": null
                                    }
                                },
                                {
                                    "setVariable": {
                                        "variableName": "wetCounter",
                                        "value": "0"
                                    }
                                }
                            ]
                        }
                    ]
                },
                "onExit": {
                    "events": [
                        {
                            "eventName": "pump_off",
                            "condition": "true",
                            "actions": [
                                {
                                    "iotTopicPublish": {
                                        "mqttTopic": "$aws/things/m5stickc-plus/shadow/update",
                                        "payload": {
                                            "contentExpression": "'{\"state\": {\"desired\": {\"pumpOn\": 0 }}}'\n",
                                            "type": "JSON"
                                        }
                                    }
                                }
                            ]
                        }
                    ]
                }
            },
            {
                "stateName": "Wait",
                "onInput": {
                    "events": [],
                    "transitionEvents": [
                        {
                            "eventName": "pumpOn_wait_timeout",
                            "condition": "timeout(\"pumpOnWaitTimer\")",
                            "actions": [
                                {
                                    "clearTimer": {
                                        "timerName": "pumpOnWaitTimer"
                                    }
                                }
                            ],
                            "nextState": "PumpOff"
                        }
                    ]
                },
                "onEnter": {
                    "events": [
                        {
                            "eventName": "StartWaitTimer",
                            "condition": "true",
                            "actions": [
                                {
                                    "setTimer": {
                                        "timerName": "pumpOnWaitTimer",
                                        "seconds": 600,
                                        "durationExpression": null
                                    }
                                },
                                {
                                    "setVariable": {
                                        "variableName": "dummy",
                                        "value": "0"
                                    }
                                }
                            ]
                        }
                    ]
                },
                "onExit": {
                    "events": []
                }
            }
        ],
        "initialStateName": "PumpOff"
    },
    "detectorModelDescription": "水分量計の値に応じてポンプを On/Off する探知機モデル",
    "detectorModelName": "watering_machine",
    "evaluationMethod": "SERIAL",
    "key": null,
    "roleArn": "arn:aws:iam::{アカウントID}:role/service-role/IoTEvents_watering_machine_role"
}

モデルの状態と遷移条件

このモデルの状態と遷移条件は以下の表のようになっています。各状態を表す丸いノードと矢印をクリックするとそれぞれのアクションと遷移の条件式が表示されるので、確認してみてください。

状態

説明

PumpOff

ポンプ Off の状態。本状態に遷移する時に、ポンプを Off するために Device Shadow を更新する。

PumpOn

ポンプ On の状態。本状態に遷移する時に、ポンプを On するために Device Shadow を更新する。また、ポンプ On 状態が継続しないように、タイマを使用して、本状態が 1 分間継続すると、ポンプ Off を指示して、Wait 状態に遷移する。

Wait

ポンプを On した後に、一定のポンプ OFF の期間を設けるための Wait 状態

5. デバイス側の実装

マイコンからクラウドに接続したり、MQTT のメッセージを送受信するなどのコードをフルスクラッチで開発するのは、非常に難易度が高い実装となります。ですので、今回は、ESP32 マイコンの開発元の半導体メーカーである Espressif Systems 社が GitHub に公開している ESP-AWS-IoT という SDK を活用します。

ESP-AWS-IoT は AWS IoT Core を ESP32 デバイスで利用するためのサンプルコードやライブラリを含むオープンソースプロジェクトです。ESP-IDF フレームワークを使用し、AWS IoT の機能と統合されたセキュアな IoT ソリューションを構築するためのリソースが提供されています。

今回は ESP-AWS-IoT のサンプルとして収録されている thing_shadow を活用します。thing_shadow サンプルは、ESP32 デバイスで AWS IoT Thing Shadow を操作する実用的なコードです。Thing Shadow を介してデバイスの状態を同期し、セキュアで効果的なリモートデバイス管理を実現しています。今回開発するコードと内容が近いですね。

デバイス側の処理は、大きく分けると ①水分量計のセンサーデータをクラウドに Publish する処理と、②クラウドからの指示に従ってポンプを制御する処理の 2 つに大別されます。今回は、これらを FreeRTOS のタスクを使って実装します。タスクとは、RTOS(Real-Time Operating System)内で実行されるスレッドに相当します。独自のスタックとコンテキストを持ち、優先度や実行周期を設定でき、マルチタスク環境で並行して動作し、リアルタイムな処理やタイミングに敏感なアプリケーションを構築するために利用されます。

前述の 2 つ (①と②) の処理をそれぞれ xTaskSensor タスクと xTaskPump タスクという名称にします。

5-1. 水分量センサー用のタスク : xTaskSensor の実装

処理の概要を図に示します。このタスクの処理は非常にシンプルです。水分量計のデータを GPIO 経由で取得し、そのデータを AWS IoT Core に Publish するのみです。

参考までにサンプルのソースコードも併せて掲載します。

A flowchart diagram in Japanese showing the workflow of a smart gardening sensor task. It includes steps for acquiring soil moisture meter data, publishing the moisture data, and waiting for 5 seconds before repeating the process.

xtask_sensor.h

サンプルソースコード

c
#ifndef MAIN_XTASKSENSOR_H_
#define MAIN_XTASKSENSOR_H_
void xTaskSensor(void *pvParameters);
#endif /* MAIN_XTASKSENSOR_H_ */

xtask_sensor.c

サンプルソースコード

c
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <math.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include <driver/spi_master.h>
#include <driver/gpio.h>
#include "esp_log.h"
#include "xtask_sensor.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "shadow_demo_helpers.h"
#include "core_mqtt.h"

#include "xtask_pump.h"
#define TAG "xTaskSensor"

static MQTTPublishInfo_t publishInfo = {0};

static uint16_t GetMoisture()
{
       uint16_t moisture = adc1_get_raw(ADC1_CHANNEL_4); // GROVE端子を使用 GP32
    return moisture;
}

static void PublishMoisuture(uint16_t moisture)
{
    MQTTContext_t *pMqttContext;
    MQTTStatus_t status;
    uint16_t packetId;
    static char payload[50];
    memset(payload, 0x00, sizeof(payload));
    
    // This context is assumed to be initialized and connected.
    pMqttContext = GetMqttContext();
    LogInfo( ( "publishPumpState pContext(4): %p", pMqttContext) );    
    LogInfo( ( "publishPumpState pContext->getTime(4): %p", pMqttContext->getTime ) );

    // QoS of publish.
    publishInfo.qos = MQTTQoS0;
    publishInfo.pTopicName = "watering-machine/state";
    publishInfo.topicNameLength = strlen(publishInfo.pTopicName);
    sprintf(payload, "{\"moisture\":%d}", moisture);
    publishInfo.pPayload = payload;
    publishInfo.payloadLength = strlen(payload);

    // Packet ID is needed for QoS > 0.
    packetId = MQTT_GetPacketId(pMqttContext);
    LogInfo(("PublishMoisuture Packet ID: %u !!", packetId));

    status = MQTT_Publish(pMqttContext, &publishInfo, packetId);
    LogInfo(("Publish Result: %u.", status));
}

void xTaskSensor(void *pvParameters)
{
    uint16_t moisture = 0;

    while (1)
    {
        ESP_LOGI(TAG, "xTaskSensor is running...");
        moisture = GetMoisture();
        ESP_LOGI(TAG, "Moisture: %u", moisture);

        PublishMoisuture(moisture);
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
}

ポンプ制御用のタスク : xTaskPump の実装

処理の概要を下図に示します。xTaskPump では、Device Shadow の差分 (Delta) を取得するために使用されるトピック をSubscribe します。その後、周期的にメッセージを受信しているか確認します。この確認には Device SDK の MQTT_ReceiveLoop 関数を使用します。

もし、何らかのメッセージを受信している場合には、MQTT セッションの確立時に設定した コールバック関数 (eventCallback) が呼び出されます。eventCallback 関数では、受信したメッセージが、Device Shadow の Delta であることことを確認し、変更後のポンプの状態 (state.pumpOn) を読み出して、「理想的なポンプの状態」を表すフラグ (desiredPumpState フラグ) を更新します。

今回は、これらの eventCallback 関数や updateDeltaHandler 関数は、サンプルとして提供されていた main/shadow_demo_main.c のコードに実装されている同名の関数を参考にしました。参考までに、ソースコードを記載します。

デバイスシャドウとMQTT通信を用いたスマートガーデニングのポンプ制御のフロー図。ポンプの状態(On/Off)の変更検知、Device ShadowのSubscribe/Pubish処理、メッセージ受信確認、状態変更のロジックが示されている。

xtask_pump.h

サンプルソースコード

c
#ifndef MAIN_XTASKPUMP_H_
#define MAIN_XTASKPUMP_H_
#include "core_mqtt.h"
void xTaskPump(void *pvParameters);
void eventCallback( MQTTContext_t * pMqttContext,
                    MQTTPacketInfo_t * pPacketInfo,
                    MQTTDeserializedInfo_t * pDeserializedInfo );
#define PUMP_ON     1
#define PUMP_OFF    0
#endif /* MAIN_XTASKPUMP_H_ */

xtask_pump.c

サンプルソースコード

c
#ifndef MAIN_XTASKPUMP_H_
#define MAIN_XTASKPUMP_H_
#include "core_mqtt.h"
void xTaskPump(void *pvParameters);
void eventCallback( MQTTContext_t * pMqttContext,
                    MQTTPacketInfo_t * pPacketInfo,
                    MQTTDeserializedInfo_t * pDeserializedInfo );
#define PUMP_ON     1
#define PUMP_OFF    0
#endif /* MAIN_XTASKPUMP_H_ */

5-2. ビルド手順

ビルド方法ですが、私は VisualStudio Code 用に Espressif Systems 社が提供している vscode-esp-idf-extension (VS Code の拡張) を使用しました。vscode-esp-idf-extension は、ESP-IDF(Espressif IoT Development Framework)用のVS Code 拡張機能です。ESP32/ESP8266 の開発をサポートし、自動補完、ビルド、デバッグ、フラッシュの機能を提供します。セットアップ方法は vscode-esp-idf-extension の README を参照してください。では、ビルド方法を以下に記載します。


1. ESP-AWS-IOTリポジトリの README に記載されている内容を参考にして、リポジトリを開発環境にクローンします

2. examples/thing_shadow のサンプルコードに変更を加えていきます。

初期的なフォルダ構成

初期的なフォルダ構成は下記のようになっていると思います。まず、ここまでに説明した 2 つのタスク処理 (xTaskSensor, xTaskPump) のソースコードをファイルとして保存します。例えば、 xtask_pump.c/xtask_pump.h, xtask_sensor.c/xtask_sensor.h とします。これらのファイルを main フォルダの配下に保存します。併せて、main フォルダ配下の CMakeLists.txt ファイルを更新し、追加したソースコードをビルドの対象に含めます。ルートディレクトリの直下にも同名の CMakeLists.txt が存在しますので、間違えないように注意してください。

bash
.
├── CMakeLists.txt
├── README.md
├── main
│   ├── CMakeLists.txt
│   ├── Kconfig.projbuild
│   ├── app_main.c
│   ├── certs
│   │   ├── client.crt
│   │   ├── client.key
│   │   └── root_cert_auth.crt
│   ├── demo_config.h
│   ├── idf_component.yml
│   ├── shadow_demo_helpers.c
│   ├── shadow_demo_helpers.h
│   └── shadow_demo_main.c
├── partitions.csv
└── sdkconfig.defaults

コードを変更

3. つづいて、examples/thing_shadow/main/app_main.c にて、下記を実行できるようにコードを変更します。後続のサンプルコードを参照してください。

  • ハードウェアの接続状態に合わせて GPIO を設定する

  • examples/thing_shadow/main/shadow_demo_helpers.c に収録されている EstablishMqttSession を呼び出して、MQTT セッションを確立する

  • xTaskCreate 関数を使って開発した2つのタスクを作成する

コードを変更

サンプルコード

c
examples/thing_shadow/main/app_main.c

void app_main()
{
    
    /* 省略 */
    
    /* 下記の関数呼び出しをコメントアウトし、それ以降のコードを追記 */
    /* aws_iot_demo_main(0,NULL); */
    
    int returnStatus = EXIT_SUCCESS;

    /* GPIOの設定関数を呼び出し */
    SetupGpio();
    
    do 
    {
        /* MQTTセッションを確立 */
        returnStatus = EstablishMqttSession(eventCallback);

        if(returnStatus == EXIT_FAILURE)
        {
            LogError( ( "Failed to connect to MQTT broker." ) );
        }
        else
        {
            /* 2つのタスクを作成する */
            xTaskCreate(xTaskPump, "TaskPump", 1024*6, NULL, 2, NULL);
            xTaskCreate(xTaskSensor, "TaskSensor", 1024*6, NULL, 2, NULL);
        }
    } while(returnStatus != EXIT_SUCCESS);
}

証明書、秘密鍵を配置

前の手順で取得した証明書、秘密鍵をそれぞれ client.crt, client.key というファイル名で main/certs フォルダに配置します。なお、それぞれ、既にサンプルの client.crt, client.key ファイルが配置されているので、上書き保存などして配置してください。

ESP-IDF 拡張の設定

ESP-IDF 拡張の設定を更新していきます。画面下部の歯車マークをクリックすると、ESP-IDF の設定画面が表示されます。

Screenshot of the SDK Configuration editor showing build and bootloader configuration options. The interface is in Japanese and displays various configuration categories on the left, with detailed settings on the right. A red arrow and annotation in Japanese explain that clicking the gear icon will display the configuration settings panel on the right.

Example Configuration

6. 左メニューの「Example Configuration」を選択し、前の手順で調べた AWS IoT Core のエンドポイントと、登録した Thing 名を設定します。

Screenshot showing example configuration for connecting an ESP32 IoT device ('m5stickc-plus') to an MQTT broker using AWS IoT. The image displays fields for client identifier, endpoint, port (8883), and hardware platform.

Example Connection Configuration

7. 続いて、左メニューの「Example Connection Configuration」を選択し、Wifi SSID およびパスワードを設定し、その後、設定を保存 (「Save」)」します。

Screenshot of an example WiFi connection configuration interface, showing options to connect using WiFi, provide WiFi connect commands, and input fields for WiFi SSID and WiFi password, with sample values filled in. Highlighted areas show 'SSID-Example-xxxx' and a masked password. Used in the context of smart gardening device setup.

(オプション) M5StickC Plus に書き込む

8. (オプション) M5StickC Plus に書き込む際のボーレートですが、私は .vscode/settings.json ファイルに "idf.flashBaudRate": "115200", と追記しました。

json
{
    "idf.port": "/dev/cu.usbserial-xxxxxxxxxx",
    "idf.flashBaudRate": "115200",
    "idf.flashType": "UART"
}

設定完了

9. これで設定ができましたので、デバイスを PC と接続し、ソースコードをビルドしてデバイスに書き込むことができると思います。ESP-IDF 拡張の基本的な使用方法が こちら で説明されていますので、参照してください。

6. 試してみる

水やりの要否を判断するための水分量センサーの閾値などを調整し、システムを完成させました。種をまいたプランターの土に水分量計を指し、ポンプを設置して、動作確認してみました。仕様通りに正常に動作しているようです。

念の為、エラーケースもテストしてみました。水分量センサーを土から取り出し、「土が乾いている状態」を継続させてみました。しばらくすると、ポンプが作動し、水が供給され始め、1 分後には停止しました。

う〜ん、1 分間も水を供給するのは長すぎますね。ただし、IoT Events のタイマーの最小設定時間が 60 秒という制約があります。これでは、フェールセーフ機能としては、いまいちなので、マイコンのプログラムで時間制限を組み込んだ方が良さそうですね。

7. 注意事項

今回開発したシステムは、あくまで学習目的で開発したものです。エラー処理はログの出力のみ、など、最低限の実装に留めています。商用のシステム開発の場合には、エラー処理など、もっと洗練化する必要がある部分がありますので、ご注意ください。

また、もしデバイスがオフラインだった時にクラウド側で変更があった場合には、デバイス側は知ることができません。ですので、デバイス起動時に Shadow の値を取得するなど、何かしらの方法で現在の値を取得する必要があります。

8. 今後の展望

これで、最低限の水やり機能は実現できました。今後は、以下のことにもトライしていきたいと考えています。

  • 水分量だけでなく温度・湿度も測定し、最適な水やりの条件を探ってみる
  • 測定したセンサー情報をグラフ化し可視化する

こう言った機能の追加は、例えばサンサー情報を新たに取得できるようにして、既存のタスクまたは、既存のタスクを横展開して、新たにタスクを追加する、など簡単に拡張できるようになりました。

9. まとめ

いかがでしたでしょうか ? 今回のようにマイコンや、ノーコードで開発可能な AWS サービスを活用することにより、安価に、そしてコード量を減らして迅速にシステムを開発可能であることがお分かりいただけたかと思います。また、マイコンでの開発は、比較的に敷居が高い印象をお持ちの方もいらっしゃるかと思いますが、FreeRTOS や SDK を活用することにより、シンプルに実装できることもご理解いただけたかと思います。

今後も開発をマイペースに続けていき、もっとリッチなシステムを作っていきたい、と考えています。

筆者プロフィール

宮本 篤
アマゾン ウェブ サービス ジャパン合同会社
プロフェッショナルサービス本部 IoT コンサルタント

主に IoT 関連を中心に、クラウドの活用を目指すお客様に対して、ビジネス目標の達成をご支援しております。趣味のブラジリアン柔術で体を鍛え、抜けない筋肉痛を抱えながら日々の業務に従事しています。

Portrait of a man with short black hair in a dark top, black background