#1.全体構成
作成動機
我が家はなぜかTVリモコンがすぐなくなる。
なくなる、とは言っても、本当になくなるわけではなく、ソファーの下に潜り込んでいたり、椅子の下に落ちてたり、
なぜかTVの直下に置いてあったり。
家族が誰も所定の置き場、みたいなものを意識しないで使いっぱなしの為、こうなる。
その為、TVを付ける、消すの、ふとした時にすぐリモコンが見つからないと、イライラする。
朝急いでいる時なんか、イライラ倍増。
そこで、TVのON/OFF、もっと言うとCH操作をAmazonEcho経由で行えれば、万事解決なのでは?
と思い立ったのが直接の動機。
あとは、もともとAmazonEchoが家にあった、RaspberryPiZeroを買った、などの契機も重なったので。
構成メモ、ログを取ってたら、思いの外長くなりそうなので、3部作くらいになりそう。。
構成案
- 去年(2016年)のRe:InventにてもらったAmazon Echo x1
- RaspberryPi Zero W + 赤外線LED
- AWS IoT
- AWS Lambda
- AlexaSkill
ピタゴラスイッチなみw
ただ、要素毎の事例はたくさんあるので、1つ1つ組み合わせていくだけ、のはず。
参考URL
[AWS IOT]
(http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html)
Qiita記事: AWS-IoT ShadowからLEDのオン・オフを行う(AWS IoT Device SDK for Pythonを用いて)
CloudWatch Events + LambdaでAWS IoTデバイスシャドウを制御する
Raspberry pi 3 で部屋の赤外線受信できる機器をコントロール!
Raspbian Stretchで LIRC機能を使った学習リモコン、赤外線リモコンを動かす方法
AWS IoT Device Shadowを試すためのpython mock
#2. IoTデバイス(RaspberryPi)準備
兎にも角にも、まずは直接TVリモコンを操作するIoT端末(RaspberryPi Zero)を作成。
Raspberry で赤外線リモコン、は様々な事例がQiitaにもあるので、基本はそちらを参考に。
本編ではポイントのみで。
##2.1 物理配線
準備
- 5mm赤外線LED OSI5LA5113A OSI5LA5113A x1
- 抵抗(30Ω)くらい x1
- 赤外線リモコン受信モジュールOSRB38C9AA OSRB38C9AA x1
- RaspberryPi Zero W 1
配線図
LIRCの出力にGPIO17/入力GPIO18を使用した例。
あくまで参考まで、、
##2.2 リモコン操作電波受信
今回の目的の各の技術ではあるが、主題ではないので、要点のみの記載に留めてます。
Raspbian Stretchで LIRC機能を使った学習リモコン、赤外線リモコンを動かす方法
こちら参考に適宜環境で対応
###2.2.1 lircインストール
pi@raspberrypi:~ $ sudo apt-get update
pi@raspberrypi:~ $ sudo apt-get install lirc
###2.2.2 コンフィグ設定
一般的にここでハマることが多いようです。
lircのバージョン、raspbianのバージョンによって設定ファイルが違うためです。
環境にあった設定をしましょう。
(2017/11時点)
RaspberryPi Zero W
Kernel : Linux raspberrypi 4.9.59+ #104
lirc : 0.9.4c-9
この環境の場合は、以下の設定でOKでした。
# Uncomment this to enable the lirc-rpi module
dtoverlay=lirc-rpi:gpio_out_pin=17,gpio_in_pin=18,gpio_in_pull=up
pi@raspberrypi:~ $ diff /etc/lirc/lirc_options.conf.dist /etc/lirc/lirc_options.conf
11,12c11,14
< driver = devinput
< device = auto
---
> #driver = devinput
> #device = auto
> driver = default
> device = /dev/lirc0
ここで一度リブート。
###2.2.3 TVリモコン学習
起動後、一度lircdを停止。
手動で受信状態にし、その結果をteeでファイル保存させて、モジュールに向かって覚えさせるボタンをおす。
以下はtv電源ボタン例
pi@raspberrypi:~ $ sudo /etc/init.d/lircd stop
[....] Stopping lircd (via systemctl): lircd.serviceWarning: Stopping lircd.service, but it can still be activated by:
lircd.socket
. ok
pi@raspberrypi:~/mytv_lirc $ mode2 -d /dev/lirc0 | tee tv_on
Trying device: /dev/lirc0
Using device: /dev/lirc0
Using driver default on device /dev/lirc0
space 7519518
pulse 8931
space 4483
pulse 667
space 451
pulse 560
space 562
pulse 610
space 510
pulse 609
space 509
~略
正常に受信出来ていれば、上記の様にspace/pulseが出力される。
これを覚えさせたいボタンの分、繰り返し実施。
保存結果ファイルから、以下パースプログラム(parse_pulse.rb)を使って出力数字を取り出す。
lines = File.open(ARGV[0]).readlines
lines_without_first = lines.slice(1..-1)
puts lines_without_first.map{|line|line.split(" ")[1]}.join(" ")
pi@raspberrypi:~/mytv_lirc $ ruby parse_pulse.rb tv_on
7519518 8931 4483 667 451 560 562 610 510 609 509 608 517 560 565 603 1631 609 508 560 1684 610 1627 550 1684 600 1631 554 1685 549 1681 554 566 598 1640 552 576 587 1640 561 554 603 1641 593 1637 644 471 603 524 599 518 602 1640 597 523 596 1639 596 519 602 521
~略
ここで上記の結果をlircd.conf.d/ 以下に任意のconf名で保存する。
lircd.conf.d/配下の不要なファイルは消しておく。(2重入力=dupicateエラーを避けるため)
ちなみに、以下注意点
- コードの羅列は、80文字ほどで適宜改行を入れること(必須)。
- パース後の出力結果の一番初めの桁数の多い数字(7519518など)はコピペしない。(桁の多い数字は、lengthエラーがでる)
begin remote
name room
flags RAW_CODES
eps 30
aeps 100
gap 200000
toggle_bit_mask 0x0
begin raw_codes
name tv_on
8931 4483 667 451 560 562 610 510 609 509 608 517 560 565 603 1631 609 508 560
1684 610 1627 550 1684 600 1631 554 1685 549 1681 554 566 598 1640 552 576 587
1640 561 554 603 1641 593 1637 644 471 603 524 599 518 602 1640 597 523 596 1639
596 519 602 5211598 637 472 651 474 645 1601 639 528 591 474 645 489 634 1597
637 480 651 1583 639 1599 635 490 633 1595 638 1617 618 1594 641 39645 9004
2169 634
end raw_codes
end remote
上記の要領で、必要そうな操作を全て登録しておく。
正常に登録されていると、以下の通りに登録信号が並ぶ。
pi@raspberrypi:~ $ sudo /etc/init.d/lircd start
[ ok ] Starting lircd (via systemctl): lircd.service.
pi@raspberrypi:~/mytv_lirc $ irsend LIST "" "" room
room
0000000000000001 tv_on
0000000000000002 tv_off
0000000000000003 volup
0000000000000004 voldown
0000000000000005 ch1
0000000000000006 ch2
0000000000000007 ch4
0000000000000008 ch6
0000000000000009 ch7
000000000000000a ch8
000000000000000b ch9
000000000000000c ch10
lircを起動して赤外線の送信テスト。
pi@raspberrypi:~ $ irsend SEND_ONCE room tv_on
pi@raspberrypi:~ $ irsend SEND_ONCE room tv_off
pi@raspberrypi:~ $ irsend SEND_ONCE room ch1
pi@raspberrypi:~ $ irsend SEND_ONCE room ch2
pi@raspberrypi:~ $ irsend SEND_ONCE room ch4
pi@raspberrypi:~ $ irsend SEND_ONCE room ch6
pi@raspberrypi:~ $ irsend SEND_ONCE room ch10
pi@raspberrypi:~ $ irsend SEND_ONCE room volup
pi@raspberrypi:~ $ irsend SEND_ONCE room voldown
これでうまく動けば、デバイス準備は完了!
以下は、RasPiZeroをフリスクケースに納めて、赤外線送信部分だけを、TVの前に向けた状態。
最終的にはテレビの裏に貼り付ける予定だけど、今は暫定でこのまま。
#3. AWS IoT -> RaspberryPi
ピタゴラの1歩目。
作成した端末を「AWS IoT」のシャドウ機能を利用して、TV操作可能なようにする。
##3.1 AWS IoT登録
これはAWS IoTのデバイス登録ウィザードからほぼデフォルトのままサクサク進めてOK。
リージョンはもちろん東京を選択。
「AWS IoTに接続する」を選択
「デバイスの設定」「今すぐ始める」を選択
今回はRasbian + Pythonスクリプトを利用するので、
「Linux」 & 「Python」を選択
名前は「myHomePi」としておく。(ちょっと美味そうなひびきだな。。)
一応ポリシーなど含め、内容確認して問題なければ、接続キットをダウンロードする。
無事登録完了。
##3.2 登録デバイスのRestAPIエンドポイントを記録
後述のスクリプトに仕込むための、RestAPIエンドポイント情報を確認しておく。
デバイス管理画面から、「操作」を選択し、HTTPSの欄にあるホスト名をメモしておく。
##3.3 デバイス側の連携準備
AWS SDKをインストール & 作業ディレクトリの作成
pi@raspberrypi:~ $ sudo pip install AWSIoTPythonSDK
Collecting AWSIoTPythonSDK
Downloading AWSIoTPythonSDK-1.2.0.tar.gz (67kB)
100% |████████████████████████████████| 71kB 528kB/s
Building wheels for collected packages: AWSIoTPythonSDK
Running setup.py bdist_wheel for AWSIoTPythonSDK ... done
Stored in directory: /root/.cache/pip/wheels/c9/b1/9c/0ae77289f3e4049944a807981269309f83e906addfb0afe5c9
Successfully built AWSIoTPythonSDK
Installing collected packages: AWSIoTPythonSDK
Successfully installed AWSIoTPythonSDK-1.2.0
pi@raspberrypi:~ $ mkdir python_aws_iot
pi@raspberrypi:~ $ cd python_aws_iot/
3.1でダウンロードした接続キット(zipファイル)をRaspberryPiに送る。
toguma-MacBook:~ toguma$ scp ~/Downloads/connect_device_package.zip pi@192.168.1.42:~/python_aws_iot
pi@192.168.1.42's password:
connect_device_package.zip 100% 3591 310.5KB/s 00:00
解凍して、スクリプトを準備する。
この接続キット内にある、証明書(.cert.pem)とキー(private.key)はスクリプト登録に必要のため、certs ディレクトリに下記名前で保存しておく。
キー配置場所
./certs/
- certs.pem (もともとのmyHomePi.cert.pem)
- public.pem (もともとのmyHomePi.public.key)
- private.pem (もともとのmyHomePi.private.key)
pi@raspberrypi:~/python_aws_iot $ unzip connect_device_package.zip
Archive: connect_device_package.zip
inflating: myHomePi.private.key
inflating: myHomePi.public.key
inflating: myHomePi.cert.pem
inflating: start.sh
pi@raspberrypi:~/python_aws_iot $ ls
connect_device_package.zip myHomePi.cert.pem myHomePi.private.key myHomePi.public.key start.sh
pi@raspberrypi:~/python_aws_iot $ mkdir certs/
pi@raspberrypi:~/python_aws_iot $ mv myHomePi.cert.pem certs/cert.pem
pi@raspberrypi:~/python_aws_iot $ mv myHomePi.public.pem certs/public.pem
pi@raspberrypi:~/python_aws_iot $ mv myHomePi.private.pem certs/private.pem
次に、root証明書をダウンロードし、
certs/ ディレクトリ配下に 「root.pem」 という名前で保存する。
pi@raspberrypi:~/python_aws_iot $ curl https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%2ication-Authority-G5.pem > ./certs/root.pem
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1758 100 1758 0 0 1723 0 0:00:01 0:00:01 --:--:-- 1725
##3.4 AWS IoTとの連携プログラムの作成、配置
IoTと連携用のPythonスクリプトを準備
接続情報のコンフィグレーションファイル setup.jsonを作成。
"ENDPOINT"
"DEVICE"-"NAME"
を、3.1で作成した「モノ」=Thingの内容に合わせて修正。
設定情報 setup.json
{
"AWSIoT": {
"ENDPOINT":"xxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com",
"CERT_PATH":"./certs/",
"KEYS":["cert.pem", "public.pem", "root.pem"]
},
"DEVICE":{
"NAME":"myHomePi"
}
}
連携スクリプト本体 Tv.py
import os
import sys
import json
import time
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
CONFIG_FILE = './setup.json'
shadow = None
shadow_hundler = None
##########################################################
# include setup.json
###########################################################
def readConfig():
print 'start readConfig func'
try:
# read config file
f = open(CONFIG_FILE, "r")
jsonData = json.load(f)
f.close()
list = {}
list["ENDPOINT"] = jsonData["AWSIoT"]["ENDPOINT"]
list["CERT_PATH"] = jsonData["AWSIoT"]["CERT_PATH"]
list["DEVICE_NAME"] = jsonData["DEVICE"]["NAME"]
return list
except Exception as e:
print 'Config load error'
print e.message
sys.exit()
##########################################################
# Shadow update
##########################################################
def updateThing(report):
try:
report_json = '{"state":{"reported":'+ report + '}}'
print "send currnet status to cloud-shadow"
print report_json
shadow_hundler.shadowUpdate(report_json, None, 5)
return
except Exception as e:
print e.message
sys.exit()
##########################################################
# shadowRegisterDeltaCallback
#
##########################################################
def getDelta(payload, responseStatus, token):
try:
print '======get Delta======'
dict_delta = json.loads(payload)
print(dict_delta)
for x in dict_delta["state"]:
print("/usr/bin/irsend SEND_ONCE room " + x)
cli = "/usr/bin/irsend SEND_ONCE room " + x
os.system(cli)
report = json.dumps(dict_delta["state"])
updateThing(report)
return
except Exception as e:
print "Error on Delta function"
print e.message
raise
################################################
# Shadow session
################################################
def initShadow(Config):
##--need device cert / private / rootCA--
# rootCA: get from symantec
ROOT_KEY = Config['CERT_PATH'] + 'root.pem'
CERT_KEY = Config['CERT_PATH'] + 'cert.pem'
PRIVATE_KEY = Config['CERT_PATH'] + 'private.pem'
try:
# init shadow connect procedure
global shadow
shadow = AWSIoTMQTTShadowClient(Config["DEVICE_NAME"])
shadow.configureEndpoint(Config["ENDPOINT"], 8883) # Setting URL-ENDPOINT & Port
shadow.configureCredentials(ROOT_KEY, PRIVATE_KEY, CERT_KEY ) # Cert file setting
shadow.configureConnectDisconnectTimeout(10)# CONNACK wait time (sec)
shadow.configureMQTTOperationTimeout(5) # QoS1 publish (sec)
print 'start connct shadow'
shadow.connect()
print 'shadow connect'
return
except Exception as e:
print 'Error on Init Shadow'
raise
####
if __name__ == '__main__':
Config = readConfig()
try:
initShadow(Config)
print 'satrt subscribe shadow'
shadow_hundler = shadow.createShadowHandlerWithName(Config['DEVICE_NAME'], True)
shadow_hundler.shadowRegisterDeltaCallback(getDelta)
while True:
pass
except KeyboardInterrupt:
print 'Keyboard Interrupt'
sys.exit()
except Exception as e:
print e.message
sys.exit()
Tv.pyの大まかな処理内容
- 指定のIoTデバイスと接続
- IoT シャドウからdesiredとreportedの差分値であるdeltaが発生を検知
- deltaを検知後、delta内容を取得し、内容に応じた処理を実施
登録するstate名をそのままリモコン登録した名前に(例えば tv_on)すると、
desiredされたstate=Key名をそのままリモコン操作コマンドに割り当てることが可能。
for x in dict_delta["state"]:
print("/usr/bin/irsend SEND_ONCE room " + x)
cli = "/usr/bin/irsend SEND_ONCE room " + x
os.system(cli)
- コマンド実行後、desired内容をreportedに登録(差分を埋める)
##3.5 IoT シャドウの登録
AWS IoTの登録したモノの、シャドウを編集
{
"desired": {
"tv_on": 0,
"tv_off": 0,
"volup": 0,
"voldown": 0,
"ch1": 0,
"ch2": 0,
"ch4": 0,
"ch5": 0,
"ch6": 0,
"ch7": 0,
"ch8": 0,
"ch9": 0,
"ch10": 0
},
"reported": {
"tv_on": 0,
"tv_off": 0,
"volup": 0,
"voldown": 0,
"ch1": 0,
"ch2": 0,
"ch4": 0,
"ch5": 0,
"ch6": 0,
"ch7": 0,
"ch8": 0,
"ch9": 0,
"ch10": 0
}
}
ちなみに、初期設定で、aws_xxxx というstateが入っており、上記上書きしても消えません。
その場合は、消したいstateの desired / reported それぞれに「null」 を記載して保存すると、消えます。
これ公式ドキュメントに乗っているんですが、なかなか気がつかなかった。。
初期有用なTipsですw
##3.6 AWS IoT受信プログラム実行
これで実行準備が整ったので、受信プログラムを起動してみる。
AWS IoT側の設定と違っている箇所があるとエラー(config errorなど)になるので、その場合は再度よく確認しておくこと。
pi@raspberrypi:~/python_aws_iot $ python Tv.py
start readConfig func
start connct shadow
shadow connect
satrt subscribe shadow
##3.7 AwS IoTのシャドウから直接操作
AWS IoTのコンソールから該当デバイスのシャドウの値のdesiredで、操作したいボタンの値を0->1に修正して編集、保存。
(ここで、reportedは修正しないこと。
受信プログラムはdesiredとreportedの差分を検知して動作するので、この部分が一緒だと差分が発生せずに動きません。)
「tv_on」の場合、以下のログを出力しつつ、TVが付くことを確認
pi@raspberrypi:~/python_aws_iot $ python Tv.py
start readConfig func
start connct shadow
shadow connect
satrt subscribe shadow
======get Delta======
{u'timestamp': 1510989882, u'state': {u'tv_on': 1}, u'version': 116, u'metadata': {u'tv_on': {u'timestamp': 1510989882}}}
/usr/bin/irsend SEND_ONCE room tv_on
send currnet status to cloud-shadow
{"state":{"reported":{"tv_on": 1}}}
これでAWS IoTのシャドウからデバイス「myHomePi」(RaspberryPi)をTV操作する環境ができた。
#4 Lambda -> AWS IoT -> RaspberryPi
ピタゴラスイッチ2歩目。
今度はAWS IoTをLambdaから操作可能なようにする。
ここでは、Lambda経由でIoTを動かすための仮のテスト関数。
Alexa用には別途作成予定。
また仮ってことで、この関数だけサンプルと同じ、Nodejsで。
本番のAlexa連携用はPython利用予定。
##4.1 Lambda関数の作成
関数名 : myHomePi-Tv-ctrl
言語 : Node.js 6.10
IAMPolycy : LambdaデフォルトのIAM権限+AWSIoTDataAccess
mem : 128NB
Timeout : 3s
プログラム内容
エンドポイントの箇所を環境に合わせて修正。
プログラム概要
- デバイスの状態(シャドウ)をAWSIoTから取得
- eventから入力された操作タイプのstateのdesierdだけ、変更する。
- (この時、値が0の場合に1を、1の場合に0を指定することで、スイッチのトグルが実装される。)
- 変更した値をシャドウにupdateする。
と、やっていることは単純。
console.log('Loading function');
var aws = require('aws-sdk');
var endpoint = 'xxxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com';
var thingName = 'myHomePi';
exports.handler = function(event, context) {
// AWS IoT Data APIに接続
var iotdata = new aws.IotData( { endpoint: endpoint } );
// デバイスシャドウを取得
var params = { thingName: thingName };
iotdata.getThingShadow(params, function (err, data) {
if (!err) {
// シャドウドキュメントから現在の設定を取得
var payload = JSON.parse(data.payload);
var conditionDetail = {};
var ctrl_type = event.ctrl_type;
var current_value = payload["state"]["desired"][ctrl_type]
// 指定ctrl_typeをトグルし、シャドウドキュメントに合わせたオブジェクトを生成
if(current_value == '0') {
conditionDetail[ctrl_type] = Number(1);
} else {
conditionDetail[ctrl_type] = Number(0);
}
var desiredState = {
state: {
desired: conditionDetail
}
};
// デバイスシャドウを書き込む
var params = {
thingName: thingName,
payload: JSON.stringify(desiredState)
};
iotdata.updateThingShadow(params, function (err, data) {
if (!err) {
context.succeed();
} else {
context.fail(err);
}
});
} else {
context.fail(err);
}
});
};
##4.2 Lambda実行テスト
テスト用jsonを以下の様に用意
{
"ctrl_type": "tv_off"
}
テスト実行結果が、「成功」となり、以下の様なログが出ればOK。
テストjsonのctrl_typeを変更して、想定通りにTV操作が可能なことを確認する。
START RequestId: 6f59c7bf-cc3f-11e7-986a-299ff7ed3d52 Version: $LATEST
2017-11-18T09:04:08.550Z 6f59c7bf-cc3f-11e7-986a-299ff7ed3d52 Write state :{"state":{"desired":{"tv_on":0}}}
2017-11-18T09:04:08.667Z 6f59c7bf-cc3f-11e7-986a-299ff7ed3d52 thing Data Update Done.
END RequestId: 6f59c7bf-cc3f-11e7-986a-299ff7ed3d52
REPORT RequestId: 6f59c7bf-cc3f-11e7-986a-299ff7ed3d52 Duration: 515.47 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 34 MB
これで、Alexa Skillに登録するまでの下準備がようやく完了。
長くなったので、一旦ここまで。
次回後半編で、実際にAlexaとの連携を実行していく。
またタイミング的に日本語版が発売した時期なので、日本語対応化予定。
続きを書きました。
Amazon Echo で TVリモコン操作への道 (RPizero & AWSIoT & Lambda & AlexaSkill) 後編