【Greengrass V2】カスタムコンポーネントでRaspberry Pi から Modbus プロトコルで温湿度データを取得してみた

Modbus RTU 対応の温湿度トランスミッタから Greengrass のカスタムコンポーネント経由でセンサーデータを取得してみました。
2021.08.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

前回の Modbus 温湿度センサー を使って、Greengrass V2 でセンサーデータを取得してみたいと思います。今回は Greengrass Core デバイスとして Raspbessy Pi 4 を使います。

前回の記事はこちらです。

前回と同じように Modbus センサーと Raspberry Pi を写真のように接続します。

01-rpi-modbus-sensor-min

Python環境の整備

今回も Python を使いますが、Raspbessy Pi の Python 環境は下記のとおりでした。特に問題ないので、このまま利用します。

$ python -V
Python 2.7.16

$ python3 -V
Python 3.7.3

コード自体は前回のものとほぼ同じものを使います。そのため事前にpymodbusだけインストールしておきましょう

$ pip3 install pymodbus

Greengrass を使わずに温湿度データを取得する

Greengrass を試す前に前回のコードをベースに、Greengrass を使わずに単純に Raspberry Pi 上で温湿度が取得できることを確認してみます。

コードは下記のとおりです。適当な場所に下記ファイルを作成してください。
なお、今回は温湿度データを10秒間隔で取得するように前回のコードを少し修正しました。また、ModbusClient()port指定も Raspberry Pi に合わせて /dev/ttyUSB0に変更しました。

$ vi ~/test-modbus-sensor.py
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import datetime
import time

def run_sync_client():
    client = ModbusClient(baudrate=9600, port="/dev/ttyUSB0", method="rtu")
    client.connect()
    rr = client.read_input_registers(address=1, count=2, unit=0x1) 
    temperature = rr.registers[0]/10
    humidity  = rr.registers[1]/10

    # Append the message to the log file.
    with open('/tmp/Greengrass_modbus_sensor2.log', 'a') as f:
        print(f"{str(datetime.datetime.now())}\tTemperatur:\t{temperature} ℃", file=f)
        print(f"{str(datetime.datetime.now())}\tHumidity:\t{humidity} %", file=f)

    client.close()

while True:
    if __name__ == "__main__":
        run_sync_client()
        time.sleep(10)

実行してみましょう

$ python3 ~/test-modbus-sensor.py

スクリプトを実行しているターミナルとは別のものを開いて確認します。下記のように温湿度が記録されていればOKです。

$ tail -f /tmp/Greengrass_modbus_sensor.log

2021-08-12 06:49:03.881930	Temperatur:	28.3 ℃
2021-08-12 06:49:03.881979	Humidity:	56.2 %
2021-08-12 06:50:03.576678	Temperatur:	28.3 ℃
2021-08-12 06:50:03.576738	Humidity:	56.4 %
2021-08-12 06:50:13.623309	Temperatur:	28.4 ℃
2021-08-12 06:50:13.623406	Humidity:	56.4 %

お気づきの方もいらっしゃると思いますが、Raspberry Pi なので GPIO 経由でデータ取得することも可能です。しかし、GPIO で取得するには追加でデバイスが必要になるため、今回は前回と同様にシリアルポート経由でデータ取得することにしました。

Greengrass V2のセットアップ

スクリプトの動作確認ができたので次は Greengrass のコンポーネントとして動かしてみたいと思います。

今回は Raspberry Pi 4 (4GB) を Greengrass Core デバイスとしてセットアップを行います。セットアップの手順は下記のブログが参考になると思いますので、本記事では手順を割愛させていただきます。(気になる点もあるので、改めて記事にしたいと思います)

Greengrass Core ソフトウェアのインストール時に、Greengrass CLI も同時にインストールしているものします。

なお、作業は基本的に全てpi ユーザーで行います。

レシピの作成

まず最初にコンポーネント作成用のディレクトリを用意しましょう。(適当なディレクトリを作ってください)

$ mkdir ~/MyComponent

このディレクトリを起点に下記のような構成としました。

.
├── artifacts
│   └── com.example.ModbusSensor
│       └── 1.0.0
│           └── modbus_sensor.py
└── recipes
    └── com.example.ModbusSensor-1.0.0.yaml

次にレシピ用ディレクトリを作成します。

$ cd ~/MyComponent/
$ mkdir recipes

レシピ用のディレクトリができたらレシピを作成します。

$ vi recipes/com.example.ModbusSensor-1.0.0.yaml

レシピの内容は次のとおりです。

RecipeFormatVersion: '2020-01-25'
ComponentName: com.example.ModbusSensor
ComponentVersion: 1.0.0
ComponentDescription: Modbus sensor component.
ComponentPublisher: Amazon
Manifests:
  - Platform:
      os: linux
    Lifecycle:
      Run: |
        python3 -u {artifacts:path}/modbus_sensor.py

アーティファクトのソースコード

次に処理の実体となるアーティファクトのコードを用意します。はじめにアーティファクトのフォルダを作成します。

$ cd ~/MyComponent/
$ mkdir -p artifacts/com.example.ModbusSensor/1.0.0

次にアーティファクトのコードを用意します。

$ vi artifacts/com.example.ModbusSensor/1.0.0/modbus_sensor.py

コードは下記のとおりですが、先程単体で確認したものと全く同じです。

from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import datetime
import time

def run_sync_client():
    client = ModbusClient(baudrate=9600, port="/dev/ttyUSB0", method="rtu")
    client.connect()
    rr = client.read_input_registers(address=1, count=2, unit=0x1)  
    temperature = rr.registers[0]/10
    humidity  = rr.registers[1]/10

    # Append the message to the log file.
    with open('/tmp/Greengrass_modbus_sensor.log', 'a') as f:
        print(f"{str(datetime.datetime.now())}\tTemperatur:\t{temperature} ℃", file=f)
        print(f"{str(datetime.datetime.now())}\tHumidity:\t{humidity} %", file=f)

    client.close()

while True:
    if __name__ == "__main__":
        run_sync_client()
        time.sleep(10)

直面した問題

「後はコンポーネントをデプロイすればOK」と思っていましたが、デプロイしても「FAILED」となりうまく行きませんでした。
実際に/greengrass/v2/logs以下のログ(com.example.ModbusSensor.log)を見ると次のようなエラーが出ていました。

pymodbus.exceptions.ConnectionException: Modbus Error: [Connection] Failed to connect[ModbusSerialClient(rtu baud[9600])]

先程はpiユーザーで直接スクリプトを実行して正常動作を確認しました。試しにuseraddで別のユーザーを作成して同じスクリプトを実行してみたところ、同様のエラーとなりました。

Greengrass のコンポーネントはデフォルトでggc_userで実行されるので、このユーザでシリアルポートにアクセスできていないことが原因のようです。色々調べているうちに、Linux 系OS の場合、シリアルポートにアクセスできるのは dialoutグループのユーザーだけということが分かりました。(初めて知りました。)

確かに/etc/groupを見るとpiユーザーは dialoutグループに所属しています。

dialout:x:20:pi

原因が分かったのでggc_userユーザーをdialoutグループに追加します。

$ sudo gpasswd -a ggc_user dialout

追加できました。

$ grep dialout /etc/group
dialout:x:20:pi,ggc_user

参考URL

コンポーネントのデプロイ

準備ができたのでデプロイしてみます。

sudo /greengrass/v2/bin/greengrass-cli deployment create \
  --recipeDir ~/MyComponent/recipes \
  --artifactDir ~/MyComponent/artifacts \
  --merge "com.example.ModbusSensor=1.0.0"

確認します。Deployment Idには上記のデプロイコマンドを実行して表示されたものを指定します。

sudo /greengrass/v2/bin/greengrass-cli deployment status -i  <Deployment Id>

次のようにデプロイ結果がSUCCEEDEDになればOKです。IN_PROGRESSと出るときは少し待ってから再度確認しましょう。(10秒程度)

<Deployment Id>: SUCCEEDED

デプロイできたらログを確認してみます。

$ tail -f /tmp/Greengrass_modbus_sensor.log

先程と同様の出力ですが、今回は Greengrass のコンポーネントとしてデータ取得・出力できていることが分かりました。

2021-08-12 07:34:34.202113	Temperatur:	28.4 ℃
2021-08-12 07:34:34.202208	Humidity:	52.8 %
2021-08-12 07:34:44.248452	Temperatur:	28.4 ℃
2021-08-12 07:34:44.248550	Humidity:	53.1 %
2021-08-12 07:34:54.428230	Temperatur:	28.4 ℃
2021-08-12 07:34:54.428330	Humidity:	53.3 %

最後に

はじめは「コードも前回と同じだからすぐにできるだろう」と思っていました。しかしコンポーネントとして動かすとエラーになる原因が分かりませんでした。

最初はレシピで制御できるのかと思いましたが、レシピではRequiresPrivilegeroot権限で動かすかどうかくらいしか選択肢がなかったので、強引にレシピで下記のような指定を試していました。(これでもデプロイは成功してセンサーデータを取得できます。)

sudo -u pi python3 {artifacts:path}/modbus_sensor.py

この場合、visudopiユーザーのパスワード確認をスルーさせる必要があり、セキュアではないため悩みましたが、最終的に適切な解決策が見つかってよかったです。

次は、Lambda コンポーネント として試してみて、最後に AWS マネージドなコンポーネントである「Modbus-RTU プロトコルアダプタ」まで試していきたいと思います。

以上です。