Amazon Echo + rasberry pi + Nature Remoで家電操作
前回電気を良い感じに点灯するscriptを書いたので、これをraspberry-piから起動する設定を実施。
以下作業メモ
1. Node-RED Alexa Home Skill Bridgeの登録
Node-RED Alexa Home Skill Bridgeにアクセスし、Registerのリンクをクリックする
下記画面の項目を入力し、「Register」ボタンをクリック https://alexa-node-red.bm.hardill.me.uk/
「Device」→「Add Device」ボタンをクリック
デバイスを登録。
とりあえずは電源のON/OFFを実行したいので以下の設定
Node-RED Alexa Home Skill Bridgeの設定は以上。
2. AlexaスキルにNode-RED追加
iphoneのAlexaアプリより以下の手順を実行
* スキルで「Node-RED」検索
* 「有効にする」ボタンをクリック
* 1で作成したアカウント・パスワードを入力し「Authorize」ボタンを押す。
* 正常にリンクされました のメッセージを確認
* 「端末の検出」ボタンをクリック
* 1で作成した「Light」というデバイスが存在することを確認
以上でNode-Redスキルの設定は完了
3. raspberry-piにNode-Redをインストール
インストール方法はこのRunning on Raspberry Piを参考に実施。
何もしない状態でも確かにNode-Redは入っている模様
念の為Upgradingを実行
一般ユーザで下記コマンドを実行
$ update-nodejs-and-nodered
正常終了することドキドキ待つこと5分あまり、問題なく終了した模様
下記コマンドを実行し、Node-Red自動起動の設定を実施
$ sudo systemctl enable nodered.service
下記コマンドでNode-Redの起動
$ node-red-start
ブラウザからhttp://raspberry-piのIP:1880に接続すると以下画面が起動
Node-RED Alexa Home Skill Bridgeと連携するためのプラグインをインストールする。
* 右上のメニューを開き、「パレットの管理」→「ノードの追加」を選択する。
* 「node-red-contrib-alexa-home-skill」で検索し、「ノードを追加」ボタンをクリックする
* 左下のパレットに「Alexa」が追加されたことを確認
以上で、raspberry-piへのNode-Redインストールは完了。
4. Node-Redでフローを作成する
パレット上で「Alexa Home」をドロップ
「Alexa Home」をダブルクリックすると編集メニューが出てくるので、Accountの鉛筆ボタンをクリック
ログイン画面が出てくるので、1で作ったアカウントでログイン
ログイン完了するとDevice欄に「Light」が表示される
パレットからアイテムをドラッグ&ドロップしてフローを作成する。
完成図は以下の通り
各ノードの設定は以下
まずはswitchノード
プロパティのmsg.commandにAlexaからON/OFFした場合の値を設定
詳細は公式Docを参照のこと
次はONノード
ONノードの場合はmsg.payloadにonを設定している。
この値が次のノードであるexecノードで実行されるshell scriptの引数になる。
(OFFノードは設定値がoffにしてあるだけなので、画面割愛)
最後のexecノードでは実行するshell scriptを指定する
ここのノードで実行されるscriptは以下
# pyenv実行 . ~/py36/bin/activate case $1 in on) python ~/py_project/nature_remo/light/light_on.py;; off) python ~/py_project/nature_remo/light/light_off.py;; esac
pyenvでpython実行環境を設定した後、引数に合わせたpython scriptを実行するのみ。
python scriptについては前回記事 で作成したものを呼んでいる。
これにてフロー作成は完了。
Node-Red画面右上の「デプロイ」ボタンをクリックして設定の反映を行う。
これにて全ての設定が完了。
Nature RemoのAPIを叩く
Nature Remo miniを買ったので、APIを叩いてみる
以下作業手順。
1. アクセストークンの取得
ここにアクセスし、nature remoアカウントでログインする。
ログインするとリクエスト画面になるので、許可するボタンをクリック
「Access tokens」画面になるので、「Generate Access Tokens」ボタンをクリック
するとアクセストークンが表示されるので、これを控える
2. APIを叩く
2.1 デバイス一覧の取得
APIページを参考にAPIを叩いてみる。
まずは登録した機器の一覧を出力。
$tokenに1で取得したアクセストークンを設定した上で、以下コマンドを実行
$ curl https://api.nature.global/1/appliances -H "Authorization: Bearer $token" | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2546 100 2546 0 0 3512 0 --:--:-- --:--:-- --:--:-- 3511 [ { "id": "XXXXXXXX-2518-4bd8-XXXX-c571XXXXXXXX", "device": { "name": "RemoLiving", "id": "d9171fca-XXXX-4647-XXXX-XXXXXXXXXXXX", "created_at": "2018-08-19T01:41:11Z", "updated_at": "2018-08-19T08:55:37Z", "firmware_version": "Remo-mini/1.0.87-g8b06f0e", "temperature_offset": 0, "humidity_offset": 0 }, "model": null, "nickname": "電気", "image": "ico_light", "type": "IR", "settings": null, "aircon": null, "signals": [ { "id": "c9d7c6b6-bb22-4b57-8ecc-04683096caee", "name": "オフ", "image": "ico_io" }, { "id": "b67d7329-f5ac-45ca-a87e-468c5210e1a9", "name": "全光", "image": "ico_io" }, { "id": "fa7459bc-5b0c-4d36-bb2c-ab6f59290d66", "name": "光色(青)", "image": "ico_color_blue" }, { "id": "2482b859-5d6e-48f4-9900-93172606e578", "name": "光色(オレンジ)", "image": "ico_color_yellow" }, { "id": "c46b8bfe-5654-41a0-aaac-76bacce73f1e", "name": "明るさ(UP)", "image": "ico_arrow_top" }, { "id": "071c83e9-2070-49ca-8511-c7edec74ed92", "name": "明るさ(down)", "image": "ico_arrow_bottom" } ] }, (以下略) ]
とズラズラっと登録したデバイス情報が取得できた。
2.2 家電の電源ON/OFF
先程取得したdevice情報の中でsignalsのidを使用し電源のON/OFFを試す。
以下コマンド(これでTVの電源ON/OFFができるはず)
$ curl -X POST https://api.nature.global/1/signals/{signalsのid}/send -H "Authorization: Bearer $token
テレビがつけることに成功。
3. scriptから電気の明るさを変える
我が家ではリモコンから電気をONにすると明るすぎるため、毎回リモコンで明るさを調整していた。 (壁のスイッチをON/OFFだと明るさ設定は保たれるので、なにかしらのやりようはある気がしているが、、) 毎回リモコンから実行するのが面倒なため、pythonからapiを複数回叩いて電気の明るさを良い感じに変えるscriptを書いた
import requests import json from time import sleep # APIを呼んで情報取得 headers = { 'accept': 'application/json', 'Authorization': 'Bearer "アクセストークン"', } signal_id = "1a4a72e8-fb56-45b5-9fd8-19a81be8b6de" # 前光でつける signal_id = "b67d7329-f5ac-45ca-a87e-468c5210e1a9" response = requests.post("https://api.nature.global/1/signals/" + signal_id + "/send",headers=headers) # 光色を暖色にするを7回実行 signal_id = "2482b859-5d6e-48f4-9900-93172606e578" for i in range(7): response = requests.post("https://api.nature.global/1/signals/" + signal_id + "/send",headers=headers) sleep(1) # 明るさdownを3回実行 signal_id = "071c83e9-2070-49ca-8511-c7edec74ed92" for i in range(3): response = requests.post("https://api.nature.global/1/signals/" + signal_id + "/send",headers=headers) sleep(1)
次はこのscriptをAmazon Echoから実行できるように設定する
flask tutorialのDBにMongoDBを使う
flaskを勉強中。
flaskのtutorialを写経するだけではなーと思い、
tutorialではDBにsqlite3 を使っているところ、MongoDBを使ってみた。
以下備忘兼ねて、ハマったところなどメモ
PyMongoでCollectionをJoinするとき
tutorialのblog.py 36行目にて以下SQLでJoinしている箇所
post = get_db().execute( 'SELECT p.id, title, body, created, author_id, username' ' FROM post p JOIN user u ON p.author_id = u.id' ' WHERE p.id = ?', (id,) ).fetchone()
MongoDBでCollectonのjoinのやり方が分からず結構苦労した。
MongoDBでJoin相当の処理を実行する場合はaggregateメソッドを使用して同等の処理ができるらしい。
参考は以下
pymongoのAggregation Examplesを見ても$lookupなどの記載はなかったが、 ここのCollectionに関しての記載を読む感じ、行けそうだったので試してみたところ問題なく実行できた。
コードは以下
posts = db.post.aggregate([ {"$lookup": { "from":"user", "localField":"author_id", "foreignField":"_id", "as":"userInfos" } }, {"$unwind":"$userInfos"}, {"$sort": { "updated" : -1 }}, {"$project": { "id": "$_id", "title":"$title", "body": "$body", "updated": "$updated", "author_id": "$author_id", "username":"$userInfos.username", } } ])
取得した後、listに格納し直した上でrender_templateで変数を渡しているのだけど、
もっとスマートなやり方があるような気がする。。
pymongoでfind_oneメソッドにて_idでfilterする場合は
ObjectIdでなきゃ駄目
それ以上でも以下でもないが、、
以下はNGで
g.user = get_db().user.find_one({'_id': user_id})
以下はOK
g.user = get_db().user.find_one({'_id': ObjectId(user_id)})
Scrapy 備忘メモ
Scrapy メモ
実行方法
- プロジェクトでのscrapy実行時は
> scrapy crawl "name(Spiderの名前)"
各種メソッド
# class topicsのリンクを抜き出す response.css('ul.topics a::attr("href")')
SelectorListオブジェクトのメソッド
extract()
ノードの一覧を文字列のlistとして取得するextract_first()
ノードの一覧の最初の要素を文字列として取得re(regex)
ノードの一覧のうち、引数に指定した正規表現(regex)にマッチする部分のみを文字列のlistとして取得するre_first(regex)
ノードの一覧のうち、引数に指定した正規表現(regex)にマッチする最初の部分を文字列として取得するcss(query)
ノードの一覧の要素に対して、引数に指定したCSSセレクター(query)にマッチするノードの一覧をSelectorListとして取得するxpath(query)
ノードの一覧の要素に対して、引数に指定したXPath(query)にマッチするノードの一覧をSelectorListとして取得する
Scrapy Shellにて(?)
shelp()
Scrapy Shellのヘルプ表示fetch(request_or_url)
引数でしていたRequestオブジェクトまたはURLのページを新しく取得し、requestやresponseなどの変数を置き換えるview(response) 引数でしていたResponseオブジェクトをブラウザーで表示する
細かい使い方
- HTML要素内のテキストのみを取得した場合は、::text疑似セレクターでテキストノードを取得してからextract()を適用するとよし
# title 要素からextract()すると、タグを含む文字列が得られる。 >>> response.css(' title'). extract() ['<title >「 あかつき」 軌道 修正に成功 | 2016/ 4/ 8( 金) 20: 56 - Yahoo! ニュース </ title >'] >>> response.css(' title:: text') # テキストノードを取得 する。 [<Selector xpath =' descendant-or-self:: title/ text()' data ='「 あかつき」 軌道 修正 に 成功 | 2016/ 4/ 8( 金) 20: 56 - Yaho - Yahoo!ニュース'>] # テキスト ノード から extract() する と、 タグ を 含ま ない 文字列 が 得 られる。 >>>response.css('title::text').extract() ['「あかつき」軌道修正に成功|2016/4/8(金)20:56-Yahoo!ニュース'] #extract_first()を使うとlistではなく文字列が得られる。 >>>response.css('title::text').extract_first() '「あかつき」軌道修正に成功|2016/4/8(金)20:56-Yahoo!ニュース'
- 抜き出したい対象(p要素内など)にbr要素やa要素などが含まれてるときに、すべてのテキストを抽出したい CSSセレクターの::textの代わりに、XPathのstring()関数を使って要素の子孫のすべてのテキストを取得できる
>>>response.css('.hbody').xpath('string()').extract_first() '\u3000宇宙航空研究開発機構(JAXA)は8日、金星を回る探査機「あかつき」の軌道修正に成功したと発表した。昨年12月に周回軌道に投入されたが、そのままでは約2年後に日陰に入る時間が長くなり、観測を継続できない恐れがあった。軌道修正で観測期間を5年以上に延長できた。(時事通信)'
errbotからgoogle-calendar-apiを叩いて予定の一覧を取得する
はじめに
以下を参考にさせて頂いた
Python Quickstart | Calendar API | Google Developers
1. OAuth認証 JSONファイルのダウンロード
- Google APIsにアクセスして「認証情報」を選択し、「OAuth同意画面」タブにて[「メールアドレス」、「ユーザに表示されるサービス名」を入力し、「保存」ボタンをクリック
- 「認証情報」タブを選択し、「認証情報を作成」ボタンをクリックし、「OAuth クライアントID」を選択する
- 「クライアントIDの作成」画面にて、「その他」を選択し、名前項目に適当な名前(今回はerrbotとした)を設定し、「作成」ボタンをクリックする
- 「OAuth クライアント」画面が出力されるので、「OK」ボタンをクリック
- 「認証情報」画面より、今回作成したkeyのfile_download (Download JSON)ボタンをクリックし、ファイルを保存する。ファイル名はclient_secret.jsonとする
2. Google Client Libraryのインストール
下記コマンドを実行する
$ pip install --upgrade google-api-python-client
3. errbot pluginを作成
pluginのフォルダ構成は以下。 また、client_secret.jsonをrootフォルダに配置する。
. └── client_secret.json └── plugins ├── GoogleCalendar ├── get_gcal.plug └── get_gcal.py
pluginファイルの作成
[get_gcal.plug]
[Core] Name = Get_Gcal Module = get_gcal [Python] Version = 2+ [Documentation] Description = Example "get my GoogleCalendar" plugin
[get_gcal.py]
from __future__ import print_function import httplib2 import os from apiclient import discovery from oauth2client import client from oauth2client import tools from oauth2client.file import Storage import datetime from errbot import BotPlugin, botcmd try: import argparse flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() except ImportError: flags = None # If modifying these scopes, delete your previouly saved credenrials # at ~/.credentials/calendar-python-quickstart.json SCOPES = 'https://www.googleapis.com/auth/calendar.readonly' CLIENT_SECRET_FILE = 'client_secret.json' APPLICATION_NAME = 'errbot' def get_credentials(): """Gets valid user credentials from Storage If nothing has been stored, or if the stored credenrials are invalid the OAuth2 flow is completed to obtain the new credentials. Returns: Credenrials, the obtained credential. """ home_dir = os.path.expanduser('~') credential_dir = os.path.join(home_dir, '.credenrials') if not os.path.exists(credential_dir): os.makedirs(credential_dir) credential_path = os.path.join(credential_dir, 'calendar-python-quickstart.json') store = Storage(credential_path) credentials = store.get() if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) flow.user_agent = APPLICATION_NAME if flags: credentials = tools.run_flow(flow, store, flags) else: # Need only for compatibolity with Python 2.6 credentials = tools.run(flow, store) print('Storing credentials to ' + credential_path) return credentials class get_gcal(BotPlugin): @botcmd def getGcal(self, msg, args): """Shows basic usage of the Google Calendar API Creates a Google Calendar API service object and outputs a list of next 10 events on the user's a calendar """ credentials = get_credentials() http = credentials.authorize(httplib2.Http()) service = discovery.build('calendar', 'v3', http=http) now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time eventsResult = service.events().list( # 下記calendaridは参照したいgoogle calendar idを設定 calendarId='XXXXX', timeMin=now, maxResults=10, singleEvents=True, orderBy='startTime').execute() events = eventsResult.get('items', []) if not events: return 'No upcoming events found.' for event in events: #start = event['start'].get('dateime', event['start'].get('date')) try: event['start']['date'] except KeyError: start = "{} {}".format(event['start']['dateTime'][:10], event['start']['dateTime'][11:16]) else: start = event['start']['date'] yield "{}: {}".format(start, event['summary'])
実行すると以下の認証画面が起動するので、googleアカウントでログインする
4. errbotの実行
slackで!getGcalを実行すると、Rasberry pi上で以下の画面が起動する。
google accountでログインを実行すると、slack上にカレンダー情報がpostされる
Rasberry Pi 3のセットアップ
Rasberry-piを購入したので、セットアップ
以下のサイトを参考にさせてもらいました
1.ディスクの作成
1.1 Raspbianのダウンロード
下記から「RASPBIAN STRETCH WITH DESKTOP」のダウンロード。
自分が実施したタイミングではRelease-dateは2018-03-13だった
1.2 ディスクのフォーマット
SDメモリカードフォーマッターを利用して
ディスクのフォーマットを実行。
操作は起動後、以下の画面で「フォーマット」ボタンをクリックするだけ。
1.3 イメージの書き込み
イメージの書き込み前にzipファイルを解凍する必要がある。
Raspbianページに記載のあるThe Unarchiverを使用して、2018-03-13-raspbian-stretch.zipファイルの解凍を実行
書き込み対象であるSDファイルのデバイスファイルを確認する
$ df -h ~~ /dev/disk2s1 15Gi 2.4Mi 15Gi 1% 0 0 100% /Volumes/NO NAME
対象は確認できたので、アンマウントした後ddコマンドで書き込みを実行
書き込み完了したら取り出し
$ diskutil umountDisk /dev/disk2 $ sudo dd if=~/Downloads/2018-03-13-raspbian-stretch.img of=/dev/disk2 bs=1m conv=sync $ diskutil eject /dev/disk2
書き込み完了した後Rasberry piを起動したところ、稲妻マークが出て画面が落ちる。
ぐぐってみるとRasberry pi 3は2.5A推奨ってことでA足りてない模様。
自転車で秋葉原にいってきて、電源タップと電源コードを買ってくる。。。
2. OS設定
2.1 WIFI設定
GUIで設定するのみ
2.2 パッケージアップデート
パッケージアップデート。
またデフォルトで日本語フォントが入っていないとのことだったので、
日本語フォントインストール
$ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install fonts-vlgothic
2.3 Raspi-configセットアップ
やったことは
- [Systemsタブ]:host名、パスワード変更
- [Interfacesタブ]: ssh, CameraをEnableに変更
- [Localisation]タブ: Localを日本、 charcter setをUTF-8に変更
- [Localisation]タブ: timezone, keyboardをJapanに変更 設定完了後、リブート
2.4 日本語設定
漢字変換ソフトインストール
$ sudo apt-get update $ sudo apt-get install ibus-mozc
2.5 ipアドレス固定化設定
/etc/dhcpcd.confの以下の値を設定
- interface eth0
- static ip_address
- static routers
- static domain_name_servers
2.6 swapfileサイズ変更
/etc/dphys-swapfileを変更
# 変更前 CONF_SWAPSIZE=100 # 変更後 CONF_SWAPSIZE=1024
変更後にrebootし、以下コマンドで変更されていることを確認
$ free -m
3 各種ソフトインストール
3.1 vncserverインストール
$ sudo apt-get update $ sudo apt-get install tightvncserver $ vncserver
パスワード設定が終わったらMacから接続してみて問題ないことを確認。
vncサーバの自動起動設定をkaraageさんのスクリプトから設定。
$ git clone https://github.com/karaage0703/raspberry-pi-setup.git $ cd raspberry-pi-setup/dotfiles $ chmod 755 vncboot $ sudo mv vncboot /etc/init.d/vncboot $ sudo update-rc.d vncboot defaults
3.2 vsftpdインストール
ftp経由でファイル転送を実行したいので、vsftpdをインストール
$ sudo apt-get install vsftpd
書き込みを有効にするため/etc/vsftpd.conf の更新
write_enable=YES
サービス起動設定
$ sudo systemctl enable vsftpd
3.3 pyenv, virtualenv導入
GitHub - pyenv/pyenv: Simple Python version management
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv $ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile $ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile $ eval "$(pyenv init -)" $ pyenv install 3.6.1
OpenSSLのエラーが出たので、インストールしておく
$ sudo apt-get install libssh-dev $ pyenv install 3.6.1 $ pyenv global 3.6.1
続いてvirtualenv導入
$ pip3 install virtualenv
問題なく終了した!