UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

日頃あまり使っていないAWSのサービスを使う!〜AWS IoT編〜

はじめに

こちらは UUUM Advent Calendar 2018 18日目の記事です。

こんにちは、エンジニアのいぐちです。 WEBアプリケーションを開発していくうえで、AWSと触れ合うことが多い今日この頃ですが、ものすごい数のサービスを提供しているAWSの一部のサービスしか利用できておらず、少し寂しい・・・ そこで、日頃仕事で使ったことのないAWSのサービスを使ってみようということで AWS IoT を使ってみることにしました。

今回はRaspberry Pi2台とAWS IoTのセキュアなメッセージブローカーの機能を利用して簡単なおうちハック的なことをしたのでそちらをつらつらと書いていきます。

AWS IoT

AWS IoTというのはその名の通りちゃんとしたIoTでの開発をおこなっていくうえで必要になってくる様々な機能をまとめたサービス群になります。

詳しくはAWSのドキュメントをご覧ください。

AWS IoT とは - AWS IoT

今回利用したメッセージブローカー機能というのは、 MQTT等のプロトコルで各IoTデバイス(今回はRaspberry Pi)同士のメッセージのやり取りをpub/subモデルで行うものです。 MQTTなどがよく分からない方は以下のリンクをご覧ください。

MQTT の基本知識

やったこと

AWS IoTを使って、 目覚まし時計 を作りました。

え?目覚まし時計にAWS IoTって何言ってるの?ん? と思われるかもしれないですがちょっと説明させてください。

今回AWS IoTを使って作った目覚まし時計は、 スピーカーとスイッチが分離された目覚まし時計 です。

スピーカー側を寝室に置いてスイッチを遠く離れた別の部屋に置くことで、うるさいアラームを止めるのに歩いてスイッチを押しにいかなければならず嫌でも起床することになるという朝が弱い我が家の 最終兵器 です。

構成

  • Raspberry Pi A
    • スピーカーのみ接続
    • 特定の時間になるとアラームが鳴る
    • AWS IoTでTopicをsubscribeしており、別のデバイスからメッセージがpublishされたらアラームが止まる
  • Raspberry Pi B
    • タクトスイッチのみ接続
    • タクトスイッチを押すとメッセージをpublishする。
  • ソースコード
    • それぞれのRaspberry PiにはPython3系で書かれた簡単なコードが配置されています。

f:id:rinjin5th:20181217121317j:plain

AWS IoTの設定

まず以下のとおりモノを作成していきます。 (2台のデバイスを使用していますがとりあえず今回は1つのモノを使いまわします。)

f:id:rinjin5th:20181217010750p:plain

f:id:rinjin5th:20181217010841p:plain

今回は必須項目のモノの名前だけ適当に設定します。 f:id:rinjin5th:20181217010927p:plain

誰でも簡単にメッセージを送受信できては困るので、証明書による認証でセキュリティを担保します。 証明書もコンソール上からすぐに作成できます。

f:id:rinjin5th:20181217011138p:plain

作成できたら、モノの証明書、プライベートキー、ルートCAの証明書をダウンロードしておきます。 また証明書を有効化しとりあえず完了をクリックします。

f:id:rinjin5th:20181217011155p:plain

次にポリシーを作成し、先ほど作成した証明書にアタッチします。 今回はAWSIoT以外操作しないのでiot:*を指定することでAWSIoTの全操作が可能なように設定しておきます。

f:id:rinjin5th:20181217011214p:plain

f:id:rinjin5th:20181217011234p:plain

f:id:rinjin5th:20181217011250p:plain

これだけで設定は完了です。

ソースコード

一部抜粋してソースコードを掲載し説明します。 フルバージョンはこちらで公開しておりますので、気になる方はご確認ください。

https://github.com/rinjin5th/remote-alarm/

ライブラリとして以下を使用しています。

  • AWSIoTPythonSDK
  • PyAudio

AWSIoTMQTTClientの初期設定

スピーカー側、スイッチ側両方共にAWSIoTMQTTClientの設定を行う必要があります。 重要な部分は以下の通りです。

  • ClientIDの設定
  • エンドポイントの設定
  • AWSのルートCA証明書、モノの証明書、プライベートキーの設定
class MQTTClient:
    def __init__(self, client_id, host, root_ca, cert, key):
        # client_idはスピーカー側とスイッチ側で別の値にする必要がある
        self._client = AWSIoTMQTTClient(client_id)
        # AWSコンソール上で確認できるエンドポイント
        # ex)xxxxx..iot.ap-northeast-1.amazonaws.com
        self._client.configureEndpoint(host, 8883)
        # ルートCA証明書、モノの証明書、プライベートキーファイルのパスを設定
        self._client.configureCredentials(root_ca, key, cert)

parser = argparse.ArgumentParser()
parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your AWS IoT custom endpoint")
parser.add_argument("-r", "--rootCA", action="store", required=True, dest="rootCAPath", help="Root CA file path")
parser.add_argument("-c", "--cert", action="store", required=True, dest="certificatePath", help="Certificate file path")
parser.add_argument("-k", "--key", action="store", required=True, dest="privateKeyPath", help="Private key file path")
parser.add_argument("-m", "--mode", action="store", dest="mode", default="alarm" ,help="Operation modes: %s"%str(ALLOWED_MODES))

args = parser.parse_args()
host = args.host
rootCAPath = args.rootCAPath
certificatePath = args.certificatePath
privateKeyPath = args.privateKeyPath

clientId = "client_" + args.mode
mqtt_client = iotclient.MQTTClient(clientId, host, rootCAPath, certificatePath, privateKeyPath)

スピーカー側

大まかな流れは以下の通りです。

  1. トピックをsubscribeする。メッセージ受信時に実行されるcallback処理(アラーム停止)を設定しておく。
  2. アラームをループして再生しつづける。
class Alarm:
    playing = False
    
    def __init__(self):
        self._audio = pyaudio.PyAudio()

    def play(self, filename):
        self.playing = True
        while True:
            if not self.playing :
                break
            with wave.open(filename, 'rb') as wf:
                stream = self._audio.open(format=self._audio.get_format_from_width(wf.getsampwidth()),
                                 channels=wf.getnchannels(),
                                 rate=wf.getframerate(),
                                 output=True)
                 
                data = wf.readframes(1024)

                while data != b'':
                    stream.write(data)
                    data = wf.readframes(1024)

                stream.stop_stream()
                stream.close()

    def stop(self, client, userdata, message):
        self.playing = False

class MQTTClient:
    def subscribe(self, topic, callback):
        self._client.connect()
        self._client.subscribe(topic, 1, callback)

alarm_obj = alarm.Alarm()
# アラームの停止処理をメッセージ受信時のcallbackメソッドとして設定する
mqtt_client.subscribe("remote/alarm", alarm_obj.stop)
alarm_obj.play(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'alarm.wav'))

今回はあまり複雑な作りにはしたくなかったため、こちらのプログラムを起床時間にcronで実行しております。

スイッチ側

大まかな流れは以下の通りです。

  1. スイッチの状態を監視する。
  2. スイッチが押されたらメッセージをpublishする。
class MQTTClient:
    def publish(self, topic, message):
        self._client.connect()
        message_d = {}
        message_d['message'] = message
        self._client.publish(topic, json.dumps(message_d), 1)
        self._client.disconnect()

def watch():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(24, GPIO.IN) 
    
    try:
        while True:
            # タクトスイッチが押されたらループを抜けて後続の処理を実行
            if GPIO.input(24) == GPIO.HIGH:
                break 
            time.sleep(0.1)
    
    except KeyboardInterrupt:
        pass
    
    GPIO.cleanup() 

watch()
mqtt_client.publish("remote/alarm", "stop")

こちらもまた複雑な作りにはしたくなかったため、こちらのプログラムを起床時間にcronで実行しております。

まとめ

今回は目覚まし時計という軽いモノだったため、AWS IoTのほんの一部の機能しか利用しておりません。 今後はもっと色々なものを作ってAWS IoTを極めていきたいなと思います。

さいごに

UUUMではAWSのサービス(IoTは多分使わないけど・・)を使ったWEBアプリケーション開発ができる方を募集しております! とりあえずお気軽にお話だけでもどうぞ!

www.wantedly.com

www.wantedly.com