[AWS IoT Greemgrass V2] MQTTのメッセージで再起動やシャットダウンを行うコンポーネントを作成してみました。

2021.10.03

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

1 はじめに

IoT事業部の平内(SIN)です。

エッジデバイスとして、RaspberryPIを使用している場合、遠隔からシャットダウンやリブートしたくなることがあると思います。

今回は、AWS IoT Greengrass V2のコンポーネントで、これを作成してみました。 操作コマンドは、MQTTメッセージとなっています。

2 操作コマンド

操作コマンドの仕様です。

(1) Topic

maintenance/コアデバイス名

例)

maintenance/gg-device-001

(2) Payload

キー 備考
cmd reboot 再起動
cmd shutdown シャットダウン

例)

{
  "cmd": "reboot"
}

次のような形でメッセージを送ることで、デバイス(gg-device-001)が、再起動されます。

3 アーティクル

コードです。

環境変数から、コアデバイス名(ホスト名ではありません)を取得し、自身に当てたメッセージをSubscribeしています。

また、cmdの種類に応じて、sudoコマンドを発行しています。

Greenglassのセットアップや、コンポーネントの作成、MQTTのSubscribeに関しては、下記の記事をご参照ください。


[AWS IoT Greengrass V2] RaspberryPIにインストールしてみました
[AWS IoT Greengrass V2] RaspberryPIでコンポーネントを作成してみました
[AWS IoT Greengrass V2] コンポーネントからIoT CoreのメッセージブローカーにPublish/Subscribeしてみました

import time
import os
import json
import subprocess
import awsiot.greengrasscoreipc
import awsiot.greengrasscoreipc.client as client
from awsiot.greengrasscoreipc.model import (
  IoTCoreMessage,
  QOS,
  SubscribeToIoTCoreRequest
)

# MQTT送受信クラス
class Mqtt():
  def __init__(self) -> None:
    self.ipc_client = awsiot.greengrasscoreipc.connect()
    self.timeout = 10
  
  class StreamHandler(client.SubscribeToIoTCoreStreamHandler):
    def __init__(self, on_recv):
      super().__init__()
      self.__on_recv = on_recv

    def on_stream_event(self, event: IoTCoreMessage) -> None:
      message = str(event.message.payload, "utf-8")
      print("Stream.Recv payload:{}".format(message))
      self.__on_recv(json.loads(message))

    def on_stream_error(self, error: Exception) -> bool:
      print("Stream.Error: {}".format(error))
      return True

    def on_stream_closed(self) -> None:
      print("Stream.Close")
      pass

  def subscribe(self, topic, on_recv):
    print("Subscribe: {}".format(topic))
    qos = QOS.AT_LEAST_ONCE
    request = SubscribeToIoTCoreRequest()
    request.topic_name = topic
    request.qos = qos
    handler = self.StreamHandler(on_recv)
    operation = self.ipc_client.new_subscribe_to_iot_core(handler)
    future = operation.activate(request)
    future.result(self.timeout)

def on_recv(message):
  if("cmd" in message):
    cmd = ''
    if(message["cmd"] == "shutdown"):
      cmd = 'sudo shutdown -h now'
    elif(message["cmd"] == "reboot"):
      cmd = 'sudo reboot'
    if(cmd != ''):
      print(cmd)
      subprocess.Popen(cmd, shell=True)

# 自分のコアデバイス名だけサブスクライブする
thing_name = os.environ['AWS_IOT_THING_NAME']
topic = "maintenance/{}".format(thing_name)
mqtt = Mqtt()
mqtt.subscribe(topic, on_recv)

while True:
  time.sleep(1)

4 レシピ

レシピは、以下の通りです。デフォルトのコンフィグで、IoT CoreへのSubscribeを許可しています。

---
RecipeFormatVersion: '2020-01-25'
ComponentName: com.example.Shutdown
ComponentVersion: '1.0.0'
ComponentConfiguration:
  DefaultConfiguration:
    accessControl:
      aws.greengrass.ipc.mqttproxy:
        com.example.Shutdown:mqtt:1:
          operations:
          - "aws.greengrass#SubscribeToIoTCore"          
          resources:
          - "maintenance/#"
Manifests:
  - Platform:
      os: linux
    Lifecycle:
      Install: pip3 install awsiotsdk
      Run: python3 -u {artifacts:path}/shutdown.py
    Artifacts:
      - URI: s3://gg-artifacts-2021-08-11/artifacts/com.example.Shutdown/1.0.0/shutdown.py

5 sudo

コンポーネントの中では、sudo をつけたコマンドを実行していますが、何も設定しないと、下記のエラーが発生します。

コンポーネントのログ

stderr. sudo: no tty present and no askpass program specified.

また、この時、syslogには、以下のように出力されています。

pam_unix(sudo:auth): auth could not identify password for [ggc_user]

こちらは、パスワード無しでの実行を許可する必要がります。

具体的には、sudo visudoを実行して、以下の行を追加します。

$ sudo visudo
ggc_user ALL = NOPASSWD: /usr/sbin/shutdown, /usr/sbin/reboot

※ ユーザー(ggc_user)が、reboot/shutdownをパスワードなしで実行することを許可しています。

6 最後に

今回は、MQTTメッセージで、rebootやshutdownを行うコンポーネントを作成してみました。 visudoで許可が必要な点に、ちょっ戸惑いました。

Greengrass V2 強力です。引き続き、色々試して行きたいです。

7 参考リンク


[AWS IoT Greengrass V2] RaspberryPIにインストールしてみました
[AWS IoT Greengrass V2] RaspberryPIでコンポーネントを作成してみました
[AWS IoT Greengrass V2] クラウド側から複数のコアデバイスにコンポーネントをデプロイしてみました
[AWS IoT Greengrass V2] クラウド側からコンポーネントを削除してみました
[AWS IoT Greengrass V2] ローカルデバッグコンソール(aws.greengrass.LocalDebugConsole)を使用してみました
[AWS IoT Greengrass V2] Lambda関数(コンポーネント)をデプロイしてみました
[AWS IoT Greengrass V2] コンポーネントからIoT CoreのメッセージブローカーにPublish/Subscribeしてみました
[AWS IoT Greengrass V2] コンポーネントからシークレットマネージャにアクセスしてみました
[AWS IoT Greengrass V2] コンポーネントでコアデバイス間のPublish/Subscribeを試してみました
[AWS IoT Greengrass V2] ログマネージャでコンポーネントのログをCloudWatch Logsに送ってみました
[AWS IoT Greengrass V2] トークン交換サービスでコンポーネントからDynamoDBにアクセスしてみました
[AWS IoT Greengrass V2] ストリームマネージャーを使用してコンポーネントからKinesis Data Streamsへデータを送ってみました
[AWS IoT Greengrass V2] プロセス間通信 (IPC) を使用してコンポーネントの設定値を使用してみました
[AWS IoT Greengrass V2] ストリームマネージャーを使用してWebカメラの画像を毎秒2フレームでS3に送信してみました
[AWS IoT Greenglass V2] 100円ショップの Bluetooth リモコン シャッターでパトランプ回してみました