お金をかけずにサーバーの勉強をしよう

光センサーで IoT(後編)

2023年3月27日

メニューへ戻る

どうしても IoTしたいので光センサを作りました

光センサーで IoT(前編)」にてちゃんと光センサーを作ってラズパイに繋げてセンサーからの値を取れるようにしました。
光センサーで IoT(中編)」にて Node-REDで値を取ってみました。

ここでは、AWSのエミュレーターである Localstackに取得したデータを溜めてみたいと思います。

なお、LocalStackのインストールについては「LocalStackを使う (AWSエミュレーター)」に書いています。


AWSのどのサービスをターゲットにするかを考えると、IoT Coreが適当と思います。

思います…が、LocalStackにおいて IoT Coreは Pro Edition(有償)の機能でして、お金をかけないと使えませんので断念しました。orz...

次に、AWS MQが良いのでは?と思いました。

思いました…が、以下略。

じゃ、何だったらできんのよ?!

ということで、DynamoDBにすることにします。
だんだん IoTから離れていってしまっていますが…。
貧乏は辛いですねぇ。


DynamoDBは AWSの KVS(Key Value Store)・NoSQLデータベースです。

AWSとしては結構前からあるサービスで、それ故に利用する例も多くあって、ある意味枯れていて、ある意味安定している、そんなサービスかと思います。


ここでは、Node-REDから DynamoDBにデータを定期的に書きにいきたい訳ですので、Node-REDのパレットから DynamoDBに対応したものがないか探してみました。
虫眼鏡のところで「dynamodb」として検索した結果です。
Node-REDのパレットで DynamoDBに対応したもの一覧
う〜ん、ちょっと古いですねぇ。
[node-red-contrib-aws]が対象ですので、対象行のノードを追加を押します。

ダウンロードした結果追加されたノードがこれらです。
Node-REDの AWSノード
この中から[AWS DynamoDB]ノードを使います。

これらノードのドキュメントはこちら。
node-red-contrib-aws 0.7.0


先に LocalStackの DynamoDBにテーブルを作っておきましょう。
LocalStackをインストールした UbuntuServer 22.04.2に LocalStack実行用に作った[localstack]ユーザーにて作業しています。

LocalStackをインストールすると一緒に入る awslocalコマンドでテーブルを作りますが、このコマンドは awsコマンドのラッパーですので、ドキュメントは awsコマンドのものを見ましょう。
ステップ 1: テーブルを作成します

テーブル名は「Hikari」とし、マシン名をパーティションキーに、日付時刻をソートキーにして、光センサーで取った明るさの値を格納するようにします。

localstack@UbuntuServer2204-1:~$ awslocal dynamodb create-table --table-name Hikari --attribute-definitions AttributeName=MachineName,AttributeType=S AttributeName=DateTime,AttributeType=S --key-schema AttributeName=MachineName,KeyType=HASH AttributeName=DateTime,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
{
    "TableDescription": {
        "AttributeDefinitions": [
            {
                "AttributeName": "MachineName",
                "AttributeType": "S"
            },
            {
                "AttributeName": "DateTime",
                "AttributeType": "S"
            }
        ],
        "TableName": "Hikari",
        "KeySchema": [
            {
                "AttributeName": "MachineName",
                "KeyType": "HASH"
            },
            {
                "AttributeName": "DateTime",
                "KeyType": "RANGE"
            }
        ],
        "TableStatus": "ACTIVE",
        "CreationDateTime": 1679882473.199,
        "ProvisionedThroughput": {
            "ReadCapacityUnits": 5,
            "WriteCapacityUnits": 5
        },
        "TableSizeBytes": 0,
        "ItemCount": 0,
        "TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/Hikari",
        "TableId": "0bb0032b-ce56-4d0b-85d6-5de94db342bd"
    }
}

テーブルができました。


では、光センサーのあるラズパイの Node-REDでフローを作っていきましょう。
このアイコンのノードが DynamoDBを操作するものになります。
Node-REDの DynamoDBノード

これをフローエディタの格子のところにドラッグ&ドロップしますとこんなノードのアイコンになるので、ダブルクリックします。
Node-REDの DynamoDBノード

DynamoDBの接続先を設定するため、えんぴつアイコンを押します。
Node-REDの DynamoDBノード プロパティ 1

[Name]と[Region]はそのまま、[Access Id]と[Secret Key]は LocalStackの場合は嘘のもので構いませんので、適当に入れておきます。
追加を押します。
Node-REDの DynamoDBノード プロパティ 2

データを入力するには、[Operation]を[PutItem]にします。
[TableName]は上で作った通り[Hikari]にします。
[Item]はこのノードに対して、[msg.Item]で与えたもので上書きされますので空にしておいて下さい。
完了を押します。
Node-REDの DynamoDBノード プロパティ 3

以前作った CdSセルのノードと合わせてこんなフローを作りました。
injectionノードの実行間隔は 1分毎に変えています。
右にある緑色のは debugノードと言って、出力内容を確認するためのものです。
Node-REDの DynamoDB フロー

では、[データ編集]というノードの説明。
これは functionノードと言って、JavaScriptをそのまま記述するノードです。

ノーコードとは相反するノードですけど、ちょっと込み入った事をする際にはこれを使うことになります。
余り使い過ぎると「素の Node.jsで書いた方が話が早くね?」と本末転倒な事になりますのでご注意下さい。
Node-REDの functionノード
実際に書いたソースはこうなっています。

let now = new Date();

let year = now.getFullYear();
let month = ("0" + (now.getMonth() + 1)).slice(-2);
let date = ("0" + now.getDate()).slice(-2);
let hour = ("0" + now.getHours()).slice(-2);
let minute = ("0" + now.getMinutes()).slice(-2);

let lx = String(msg.payload);
let datetime = year + month + date + hour + minute;

msg.Item = {"MachineName":{"S":"raspberrypi"},"DateTime":{"S": datetime },"lx":{"N": lx}};
msg.AWSConfig = {"endpoint":"http://192.168.1.102:4566"};

return msg;

ソートキーとなる日時を作り、光センサー(CdSセル)から取った数値と合わせて、書き込むデータ(msg.Item)を作っています。
このデータの作り方は、以下を参考にしました。
ステップ 2: コンソールまたは AWS CLI を使用して、テーブルにデータを書き込みます
[lx]が「ルクス」のことで、明るさの値です。

また、本物の AWSではなく LocalStackを使う場合は、[msg.AWSConfig]という変数に[endpoint]という名前で LocalStackの URLを入れてやります。
([192.168.1.120]は UbuntuServer 22.04.2の IPです)

この 2つを DynamoDBのノードに渡してやることで、LocalStackの DynamoDBに動的に生成したデータを渡すことができます。

※LocalStackに外からアクセスする場合は「EDGE_BIND_HOST=0.0.0.0 localstack start]と実行します。
こうしないと[127.0.0.1]にバインドされてしまい、外部からアクセスできません。


上手く行きますと 1分おきに 1行のデータが DynamoDBに書かれるはずです。

暫く放っておいてから、awslocalコマンドで DynamoDBからデータを取得してみました。
[MachineName]が[raspberrypi]のものを全件取得しています。

localstack@UbuntuServer2204-1:~$ awslocal dynamodb query --table-name Hikari --key-condition-expression 'MachineName = :machinename' --expression-attribute-values '{ ":machinename": { "S": "raspberrypi" }}'
{
    "Items": [
        {
            "MachineName": {
                "S": "raspberrypi"
            },
            "lx": {
                "N": "500"
            },
            "DateTime": {
                "S": "202203271541"
            }
        },
        {
            "MachineName": {
                "S": "raspberrypi"
            },
            "lx": {
                "N": "534"
            },
            "DateTime": {
                "S": "202303272102"
            }
        },
        {
            "MachineName": {
                "S": "raspberrypi"
            },
            "lx": {
                "N": "513"
            },
            "DateTime": {
                "S": "202303272103"
            }
        },

〜〜〜 中略 〜〜〜

        {
            "MachineName": {
                "S": "raspberrypi"
            },
            "lx": {
                "N": "892"
            },
            "DateTime": {
                "S": "202303272118"
            }
        }
    ],
    "Count": 18,
    "ScannedCount": 18,
    "ConsumedCapacity": null
}

上手く取得できていますね。\(^o^)/
明るさの値もちゃんと変化しています。


こうして一旦データベースに入ってしまえば、利用は如何様にもできますね。
ただ、できれば IoT Coreを使いたかったです(残念)。


この「後編」ですが、実はかなりハマりました。

DynamoDBのノードにある[Item]という入力欄は、[msg.payload]と書き込めば良いと思っていたのですが、全然うまく行かずエラーになるばかり。

結局このノードのソースを見て、[msg.Item]を渡されると、それでオーバーライドするって事が分かって解決しました。

数日を費やし、随分と遠回りしてやっと上手く生きましたけど、おかげさまで随分と勉強になりました。