[AWS IoT Greenglass V2] 100円ショップの Bluetooth リモコン シャッターでパトランプ回してみました

2021.10.02

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

1 はじめに

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

唐突ですが、パトランプ回してみました。ON/OFFに使用したのは、100円ショップ(330円)で販売されているBluetooth リモコン シャッターです。

下の動画は、動作 を確認している様子です。

2つあるボタンで、それぞれON/OFFするようになっています。なお、Bluetoothで取得したボタン押下の情報は、MQTTにPublishされ、Sabscribeしているデバイスが、そのPayloadに基づいてパトランプを操作しています。

操作指示がMQTTのメッセージなので、IoT Coreのテスト画面からメッセージを送っても、動作できていることが分かります。これで、地球の裏側のパトランプも操作できるはずです。

2 構成

構成は、以下の通りです。

デバイス側で動作するプログラムは、Greengrass V2のコンポーネントとして作成されています。 図中の左右のGreengrassで動作するコンポーネントは、同一のものです。 末端デバイスとしてリモコンを繋ぐか、パトライトを繋ぐかで役割が変わるイメージです。

また、MQTTメッセージがトリガですので、1つのリモコンで、複数のパトライトを操作することも可能です。

3 Bluetooth リモコン シャッター

使用したリモコンです。現在は、ダイソーで品切れになっているようですが、Amazonnでも購入できます。まったく同じものでは無いですが、Can★Doでは、最近でも店頭に並んでいるのを見かけました。


https://www.amazon.co.jp/Bluetooth-Shutter-ABS3-BLK/dp/B00JX70WK4/

4 Raspberry PI

使用したのは、RaspberryPi 3Bで、OSは、今年4月の最新版(Raspberry Pi OS with desktop)です。

3Bでは、最初からBluetoothが利用可能なので、特にドングルを用意する必要なありません。

$ cat /proc/cpuinfo  | grep Revision
Revision	: a32082

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Raspbian
Description:	Raspbian GNU/Linux 10 (buster)
Release:	10
Codename:	buster

$ uname -a
Linux raspberrypi 5.10.17-v7+ #1414 SMP Fri Apr 30 13:18:35 BST 2021 armv7l GNU/Linux

5 ペアリング

Bluetoothのペアリングは、RaspberryPiで利用可能な、bluetoothctlを使用しました。

(1) 起動

$ bluetoothctl
Agent registered
[bluetooth]#

(2) スキャン

scan onで、電源ONとなっている周辺のBluetoothデバイスが列挙できるので、対象(AB Shutter3)のアドレスを控えます。

[bluetooth]# scan on
Discovery started
[NEW] Device 2A:1B:3C:11:22:38 AB Shutter3

・・・略・・・

[bluetooth]# devices
Device 2A:1B:3C:11:22:38 AB Shutter3

・・・略・・・

[bluetooth]# scan off
Discovery stopped

(3) ペアリング

pair/removeでペアリング及び、解除ができます。 下記では、デバイスをペアリングして、ペアリング中のデバイス一覧しています。

[AB Shutter3]#pair 2A:1B:3C:11:22:38
Attempting to pair with 2A:1B:3C:11:22:38

[AB Shutter3]# paired-devices
Device 2A:1B:3C:11:22:38 AB Shutter3

(4) 信頼

trustで、デバイスを信頼しておくことで、再起動後もペアリング有効になります。

[AB Shutter3]# trust 2A:1B:3C:11:22:38
[CHG] Device 2A:1B:3C:11:22:38 Trusted: yes
Changing 2A:1B:3C:11:22:38 trust succeeded
[AB Shutter3]# exit
$

(5) /dev/input/evdev0

認識されているリモコンは、入力デバイスとして/dev/input/の配下に見つけることができます。

$ ls -la /dev/input
total 0
drwxr-xr-x  2 root root     100 Sep 26 14:59 .
drwxr-xr-x 16 root root    3660 Sep 26 14:59 ..
crw-rw----  1 root input 13, 64 Sep 26 14:59 event0
crw-rw----  1 root input 13, 63 Sep 26 14:59 mice

6 権限追加

Greengrassで、デフォルトのユーザー(ggc_user)を使用する場合、GPIO及び、入力デバイスでパーミッションエラーが発生してしまいます。

stderr. GPIO.setup(PATLAMP, GPIO.OUT).
stdout. Exception [Errno 13] Permission denied: '/dev/input/event0'.

どちらも、グループに追加することで、解消可能です。

$ sudo usermod -G input,gpio ggc_user
$ cat /etc/group | grep ggc_user
input:x:105:pi,ggc_user
gpio:x:997:pi,ggc_user
ggc_user:x:995:

7 アーティクル

下記が、本体コードとなります。

それぞれ役割ごとにクラスになっています。

  • Patramp パトライト制御クラス(GPIO処理)
  • Bluetooth Bluetooth受信クラス
  • Mqtt MQTT送受信クラス

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


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

import time
import glob
import RPi.GPIO as GPIO 
import evdev
import json
import awsiot.greengrasscoreipc
import awsiot.greengrasscoreipc.client as client
from awsiot.greengrasscoreipc.model import (
    IoTCoreMessage,
    QOS,
    PublishToIoTCoreRequest,
    SubscribeToIoTCoreRequest
)

# パトライト制御クラス
class Patramp():
  def __init__(self, gpio) -> None:
    self.PATLAMP = gpio 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(self.PATLAMP, GPIO.OUT)

  def on(self):
    GPIO.output(self.PATLAMP, 1)  

  def off(self):
    GPIO.output(self.PATLAMP, 0)  

# Bluetooth受信クラス
class Bluetooth():
  def __init__(self) -> None:
    inputs = glob.glob('/dev/input/event*')
    if(len(inputs) <= 0):
      print('device not found.')
      exit(0)
    self.input = inputs[0]
    print(self.input)
  
  def recv(self):
    self.device = evdev.InputDevice(self.input)
    
    for event in self.device.read_loop():
      if(event.type == 1):
        if(event.code == 28):
          return 1
        elif(event.code == 115):
          return 0
    return -1

# 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("onRecv payload:{}".format(message))
          self.__on_recv(json.loads(message))

      def on_stream_error(self, error: Exception) -> bool:
          return True

      def on_stream_closed(self) -> None:
          pass

  def subscribe(self, topic, on_recv):
    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 publish(self, topic, payload):
    qos = QOS.AT_LEAST_ONCE
    request = PublishToIoTCoreRequest()
    request.topic_name = topic
    request.payload = json.dumps(payload).encode('utf-8')
    request.qos = qos
    operation = self.ipc_client.new_publish_to_iot_core()
    operation.activate(request)
    future = operation.get_response()
    future.result(self.timeout)    
    print("publish :{}".format(payload))
    time.sleep(1)


gpio = 17 # GPIO 17
patraml = Patramp(gpio)
bluetooth = Bluetooth()

def on_recv(message):
  if(message["sw"] == "ON"):
    patraml.on()
  else:
    patraml.off()

topic = "patramp/100"
mqtt = Mqtt()
mqtt.subscribe(topic, on_recv)

while True:
  try:
    result = bluetooth.recv()
    if(result != -1):
      if(result == 0):
        sw = "ON"
      else:
        sw = "OFF"
      mqtt.publish(topic, { "sw": sw })
  except Exception as e:
    print('Exception {}'.format(e))
    time.sleep(1)

8 レシピ

レシピです。 デフォルトCoonfigrationのaccessControlでIoTCoreに対するPublishSubscribeが許可されています。

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

9 最後に

今回は、Bluetoothのリモコンでパトランプを操作するコンポーネントを作成してみました。慣れてくると、普通のツールを作成するのと、コンポーネントを作成するのに、それほど差異を感じなくなってきます。

要件が許す場合、ありがたくGreengrassの恩恵を受けれるように、色々試しておきたいと思っています。

10 参考リンク


[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に送信してみました