- 投稿日:2020-08-20T23:07:19+09:00
API Endpointのエントリポイント・コードの書き方
API Gatewayとかから飛んできたリクエストをLambdaでいい感じに処理する、よくあるバックエンドコード
この辺のいい感じの記述を模索してて最近こんな感じになっています。最近の自分の書き方
/** * フォーマットに関してはこいつに任せる。 */ const run = async(event, hoehoe) => { // どでかいtry-catchで例外をアプリ内に封じ込める try { // 悩むけどこの辺の情報はこっちでいいかな const name = event.pathParameters.name; // 処理は極力別クラスに任せる const service = new Service(hoehoe); const pet =service.getPet(name); // 仕様と対応するので正常系の出口は一箇所 return getSuccessResponse({ color: pet.color.value, size: pet.size || 99999, }); } catch(error) { console.error(error); // 最低限の整形をしてエラーを通知する。ここはアプリの最終防衛ラインなので重点的にテスト return getErrorResponse(error); } } /** * エントリポイント * 外部プラットフォーム依存するコードを集約する * ここは結合テストのスコープ(LamndaやAPI Gatewayのテスト機構を使う) */ exports.handler = async(event) => { return await run( event, process.env.HOGEHOGE // 環境変数はスコープがでかいので入り口で捕まえて引き回す ); };
- 投稿日:2020-08-20T20:52:55+09:00
ちょっと古いBraviaもHey! Siriで操作できるようにする。
はじめに
ほんの少し前のことなんですが。
長年使ってたテレビが遂に壊れてしまったんですよ。地デジが始まる頃に買った『KDL-40X5000』って言うソニーのテレビ。
テレビが映らないのは困るなあってんで、すぐに買いに行ったんです。そしたらこの10年ちょっとで、テレビってえらい進化しているんですね。薄く軽くなったのはもちろん、ベゼルが小さくなったんですよ。なので、設置面積はほとんど変わらずだいぶ大きなテレビを置けました。
今回買ったのは『KJ-49X8500F』です。
Android TVなんですねー。
YouTubeとか見られるんですねー。
あこがれの4Kですよー。
画が綺麗になりましたねー。
画面も大きくなりましたよー。
と大満足。そこにニュースが。
「ソニーのAndroid TVがAirPlay 2とHomeKitをサポート」(engadget)
やったー!テレビがSiriで動かせるぞ!
ん?アップデートが提供されているのは、2018年モデルのZ9FとA9Fシリーズ、2019年モデルのZ9G、A9G、X950G、X850Gシリーズ。
Gシリーズ?
僕の「F」じゃん! ほんのちょっと前に買ったのに対象外じゃん!
前置きが長くなりました。
たった一世代で泣いたことがすごく悔しかったので、自力でSiriで操作できるようにしました。
たぶんAndoroid TVの機種であれば、さらに前の機種でも同じだと思います。この記事でやることとセールスポイント
- Raspberry PiにHomebridgeを入れ、braviaプラグインで操作可能にします。
- homebridge-braviaプラグインのReadMeには書かれていない日本国内のチャンネルの選択方法を記載します。
- それだけだと出来ないSiriによるチャンネル変更も(力業で)実現します。
- BRAVIA(テレビ)が持っているAPIを利用します。赤外線スマートリモコンとかは使いません。
今回登場する…
ハードウェア
これらはすべて同じLANに繋がっている必要があります。
- Raspberry Pi Zero WH (ずっと前に買って転がってた)
- Raspbian buster
- Sony Bravia KJ-49X8500F
- Android 8.0.0にアップデート済み
- Homeアプリが使えるiOS / iPadOS / macOSどれかの搭載機
ソフトウェアやら
- node.js
- homebridge(nodeModule)
- homebridge-config-ui-x
- homebridge-bravia
- homebridge-cmd4
- Express(nodeModule)
- bravia(nodeModule)
- node-cron(nodeModule)
- bash
- jq
とりあえずRaspberry PiのOSなどはインストール済みで、LANに接続しているモノとします。
この記事では pizero.local で接続出来るようになっている、と仮定しますね。Pi Zeroに最新のnode.jsを入れる
RaspberryPi Zero のCPU は ARMv6 のため、node.js公式からはv11以降、バイナリが配布されていません。現時点での最新はv14.8.0。LTS版はv12系。後述のhomebridgeでもLTS版を推奨していますが、バイナリの配布はARMv7とARMv8だけです。
とは言えbuildするのもめんどうなので、アンオフィシャルビルドから入れちゃいます。
正直ちょっと古くても大丈夫ですが、node.jsはバージョン変更のトラブルが多いので、v12系の方が安心かと。
まあ、趣味の範囲です。$ sudo npm install -g n #バージョン管理ツールのnを入れます。 $ sudo n latest #最新のnodeを入れてみます。 $ node -v v11.15.0 #ちょっと古いのが入りました。 $ sudo -i #ミラー先をアンオフィシャルに変更するために環境変数をセットします。 $ export N_NODE_MIRROR=https://unofficial-builds.nodejs.org/download/release/ $ sudo -E n latest #いまセットした環境変数を使って、試しにnode最新版を入れてみます。 $ node -v v14.8.0 #入りました。今度はちょっと新しすぎます。 $ sudo -E n 12 #12系の中で最新のものをインストールします。 installing : node-v12.18.3 mkdir : /usr/local/n/versions/node/12.18.3 fetch : https://unofficial-builds.nodejs.org/download/release/v12.18.3/node-v12.18.3-linux-armv6l.tar.xz installed : v12.18.3 (with npm 6.14.6) $ node -v v12.18.3 #LTSバージョンが入りました。 # どのバージョンがLTSかわからなかったら n lts でも大丈夫。homebridge および homebridge-config-ui-xを入れる
homebridgeは入っている前提で良いかもしれませんが、簡単に記載します。
また、homebridge-config-ui-xはhomebridgeの設定をWebブラウザから出来るようになるプラグインです。便利なので標準のつもりで入れています。
詳しくはこちら。
Homebridge & Systemd (Raspbian, Ubuntu, Debian)$ sudo npm install -g --unsafe-perm homebridge homebridge-config-ui-x $ sudo hb-service install --user homebridgeこれでOKです。
homebridge-config-ui-xとhb-service(httpサーバ)の組み合わせで起動すると、
http://pizero.local:8581/
でオシャレ設定画面にアクセス出来ます。
初期アカウントとパスワードは両方ともadminです。プラグイン:homebridge-braviaを入れる
homebridgeにはBraviaを操作するプラグインが複数あります。
今回はそのものズバリな名前の「homebridge-bravia」を使ってみます。これはhomebridge-config-uiで簡単に入れられます。
プラグインのタブでbraviaを検索。「homebridge-bravia」を見つけたら、インストールを押すだけです。
homebridge-braviaはconfig-uiに対応しているので、主な設定は設定UIで可能です。
とりあえず[設定]を押して設定してみて下さい。
configはあとで手で修正する必要があるのですが、とりあえずひな形が出来るので便利です。Name はSiriに呼びかける名前になるので注意して下さい。
ここで「おばあちゃん」とかわかりにくい名前を付けると
「Hey siri, おばあちゃんを消して」とか言う必要があります。家族がギョッとします。
僕は無難にテレビにしておきました。
IP / Hostname は テレビのものを。
Android.localでも大丈夫な気がしたんですが、どうも上手く行きませんでした。
これはいつか検証する必要があります。とりあえずIPアドレスで指定。
TV Source は後述。とりあえず適当に選択して下さい。
それ以外は特に注意点は無いので、デフォルト値か空欄でOKです。
PIN Entry Server Port と言うちょっと意味がわからないのがありますね。
これもデフォルトの8999のままにしておきます。あとで説明します。保存したら、上部のメニューから「コンフィグ」を選んでconfigファイルを直接編集します。
日本国内におけるTV Sourceの設定について
homebridge-braviaのREADMEには、TV Sourceについて以下のように記載があります。
入力 設定値 アンテナ tv:dvbt ケーブル tv:dvbc 衛星 tv:dvbs これはヨーロッパなどで使われている規格です。日本国内ではこの設定は正しくありません。
入力 設定値 地デジ tv:isdbt BS tv:isdbbs CS tv:isdbcs とする必要があります。
詳しく知りたい方は ISDB (wikipedia)をご覧下さい。1 (北米では 2)
結果、config.jsonのbravia部分は、おおよそ以下のようになります。
tvsourceには地デジとBSを指定しました。僕はCS見てないので。
applicationsの部分はTVから起動出来るアプリです。
下の方にあるsourcesはhomeアプリ内に表示したい入力の一覧です。cecを指定しておくと、HDMIに繋がった機器の名称が並びます。HDMI-CECに対応していない機種がある場合はextInput:hdmiを指定することになります。その場合はHDMI-1などの番号で表示されます。config.json{ "tvs": [ { "name": "テレビ", "ip": "10.0.1.xx", "externalaccessory": false, "soundoutput": "speaker", "tvsource": [ "tv:isdbt", "tv:isdbbs" ], "applications": [ { "title": "Prime" }, { "title": "YouTube" } ], "sources": [ "tv:isdbt", "tv:isdbbs", "extInput:cec" ] } ], "platform": "BraviaPlatform" },ここまでで、「Hey siri! テレビを点けて!」が出来るようになりました!
テレビのスイッチを付けてから、
右上にある「再起動」ボタンを押してhomebridgeを再起動しましょう!テレビの前へ急げ
再起動しましたか?
再起動すると、テレビの画面に数字が表示されるはずです。これはPINコード。この番号を知らない機械からの命令は受け付けないよ!と言う意味です。PINコードの有効期限は60秒!慌ててhomebridgeを設定しているPCに戻って下さい。
少し前に「デフォルトでOK」と書いた「 PIN Entry Server Port 」を憶えてますか?デフォルトのままだったら8999なので、
http://pizero.local:8999/
にアクセスして、さっき憶えたPINコードを入力して下さい。
間に合わなかったらもう一回homebridgeの再起動をやり直し!Hey siri! テレビを点けて!
さて、無事に立ち上がったでしょうか?
iOSまたはmacOSで、homeアプリを立ち上げましょう。
テレビ(または おばあちゃん)の名前の付いたアイコンが表示されましたか?
iOSの場合は長押し(macOSの場合はコンテクストメニューから「コントロールを表示」)で、テレビを操作するためのUIが表示されるはずです。
丸い大きなスイッチで、テレビのON/OFFが出来ます。ボタンを押してみて下さい。テレビが消えましたか?
次に、試しに口に出してみましょう 「Hey siri, テレビを点けて!」テレビが点きましたか? おめでとうございます。
Homeアプリのスイッチの下には、チャンネルが選択出来るメニューが表示されます。
表示されませんか?
チャンネルの一覧を取得するのに少し時間が掛かるようです。
でも、少し待てば表示されます。いくら待っても表示されない?
右下の歯車を押してみて下さい。
「アクセサリ」の欄に大きな数字があり、それを選ぶと中にチャンネルの名前のアイコンが並んでいますか?
これはダメな状態。この状態だと、並んだチャンネルを選んでもなにも起きません。
どうやらこれは不具合なんじゃ無いかと思います。
こちらにも報告されていますが、この状態になってしまったら、一度Homeアプリを再起動してみて下さい。
iOSのAppスイッチャーからスワイプして完全に消してしまい、もういちど起動します。
するとメニューの中に現れる(ことが多い)ようです。Hey siri! フジテレビを点けて!
さて。メニューの中に無事チャンネルが現れたでしょうか?
地域にもよりますが、サブチャンネルまで含めて全部表示されるはずです。
メニューから放送局を変更すると、テレビのチャンネルが変わるはずです。やったね!
でも数が多いとちょっと変更するのシンドイですね。
では、口に出してみましょう。 「Hey siri, フジテレビを点けて!」おや。
だめですね。
ダメですよね?そうなんです。どうもsiriは、メニューの中にある項目を変更出来ないみたいなのです。
どうもこれは、iOSの(homekitの?homeアプリの?siriの?)仕様である様子。
これではせっかく声でテレビを点けても、結局リモコンを手に持つか、homeアプリを立ち上げて小さなメニューをくるくるしなければいけません。
ぐぬぬぬぬ。悔しい。
「Gシリーズ」ではそれが出来るのかどうかサッパリわからないけど、出来ないのは悔しい。。。そんなわけで、なんとかして音声でチャンネルが変えられるように挑戦します。
テレビが点けば良いだけの場合は、ここで記事を読むのをやめることが出来ます。nodeでBraviaを操作する
homebridge-braviaでは、iPhoneのhomeアプリからテレビの電源オンオフと、チャンネルの変更が出来ました。
siriに頼んでテレビのオンオフも出来ました。
しかし「Hey siri, チャンネルをTOKYO MXにして!」が出来ない。
ちょっと別のアプローチを取ることにしました。bravia node_moduleを入れる
なんと、Braviaを操作できるnodeモジュールが存在します。
bravia npm
このnodeモジュールでは、テレビに対してかなりいろいろな操作が可能です。
(homebridge-bravia-simpleなどのhomebridgeプラグインはこれのWrapperのようです)
これを使ってみることにします。まずディレクトリを作ってそこにmoduleを入れます。
$ mkdir ~/homebridge/tvChannels && cd $_ $ npm init $ npm install braviaまず、braviaモジュールのページを参考に、TVの設定をして下さい。
テレビのリモコンを持ち、ホームを押してから右上の歯車【設定】を選ぶと設定が可能です。
Pre-Shared Key (PSK) と言うのは自分で決める暗証番号のようなもの。
ここでは8888と設定したと仮定します。では実験してみましょう。
次のコードを書いて保存してください。IPアドレスとPSKを自分のモノに変更することを忘れずに。getContentList.jsconst Bravia = require('bravia'); bravia = new Bravia('10.0.1.X', 80, '8888'); bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbt", 'stIdx':0, "cnt": 200 }) .then(contentList => { for (var i = 0; i < contentList.length; i++) { var content = contentList[i]; console.log( "[Channel]:" + content.dispNum + "\n" + "[Title]:" + content.title + "\n" + "[URI]:" + content.uri + "\n ----"); } });実行します。
$ node getContentList.js
チャンネル一覧が出てくれば正常に動いています。
気が付いていると思いますが、3行目のsource にある tv:isdbtをtv:isdbbsにすれば、BS放送の一覧が出てきます。
出力結果の1行目。dispNumの値は三桁のチャンネル番号です。あとでチャンネルを指定する際にこの数字を使うので、自分がみたい局の番号は控えておいて下さい。
また、出力結果の3行目にあるURIがチャンネルを変更するときに必要になるのですが、それはあとで。3行目で呼び出しているgetContentListは、Braviaが持っているAPI。
その詳細は
BRAVIA Professional Display Knowledge Center
ここで公開されています。これは「法人向けブラビア」用に用意されたページのようですが、一般家庭向けのモノでも使えるようです。たぶん。
第二引数にはバージョンナンバーを指定します。指定しなくても動きますが、複数のバージョンが用意されているAPIではこれで切り替えます。
さっきのは、avContent サービス のgetContentList API を叩きました。
バージョン1.5まで用意されています。バージョン番号を変更して叩き直すと、レスポンス内容が変わるので遊んでみてください。次はappControl サービス のgetApplicationList APIを叩いてみましょう。
getApplicationList.jsconst Bravia = require('bravia'); bravia = new Bravia('10.0.1.X', 80, '8888'); bravia.appControl.invoke('getApplicationList') .then(appList => { for (var i = 0; i < appList.length; i++) { var app = appList[i]; console.dir(app); } });インストールされているアプリの一覧が出たりします。
これはアイコン画像とか提供されていてちょっと面白いです。なお、現在受信している番組の情報はavContent サービス のgetPlayingContentInfo APIで取得出来ます。
日本国外だったりケーブルテレビだったりで、tv:isdbtではない場合はこれで調べることが出来ます。getPlayingContentInfo,jsbravia.avContent.invoke('getPlayingContentInfo','1.0') .then(nowPlayContent => { console.dir(nowPlayContent); });【メモ】
実際のところbraviaと言うnodeモジュールは、このAPIを叩いているだけだ。
APIドキュメントに従って、
$ curl -s -X POST -H "Content-Type: application/json; charset=UTF-8" -H "X-Auth-PSK: 8888" -d '{"method": "getApplicationList", "id": 60, "params": [], "version": "1.0"}' http://10.0.1.X/sony/appControl | jq .
とかやると同じ結果を得ることが出来る。憶えておいても損はない。
またBraviaのAPIは、おおよそテレビで行いたい操作は網羅されている上に、赤外線リモコンをエミュレートするものもある。だいたい何でも出来る。さてこれで、Raspberry PiからBRAVIAを操作する目処が立ちました。
基本的な流れはこんな感じでしょうか。
- フジテレビを点けるように命令
- 電源が入っているかを確認 system > getPowerStatus
- 入ってなければ電源を点ける system > setPowerStatus
- フジテレビのURIを取得 avContent > getContentList
- そのURIにチャンネルを変更 avContent > setPlayContent
あと現在放送中のチャンネルを知る avContent > getPlayingContentInfo
が必要になります。必要なAPIはすべて存在しているようです。
しかし問題は、一番最初の「点けるように命令」ですね。プラグイン homebridge-cmd4を使う
Raspberry PiからBraviaの操作ができることがわかったので、次はhomekitでの操作を考えます。
これには、homebridge-cmd4と言うプラグインを使うことにしました。homekit対応アクセサリーをエミュレートし、shコマンドを発行出来るモノ、だと思えばよろしいです。
とりあえず入れちゃいましょう。
homebridge-config-ui-xから検索してインストールします。手作業で入れるならこうなります$ sudo npm install -g --unsafe-perm homebridge-cmd4これを使って、homekitアクセサリーの「スイッチ」を作ります。
siriにお願いすると、スイッチがオンになります。
スイッチをオンにするとRaspberry Piがコマンドを発行し、BRAVIAを操作します。
スイッチに「フジテレビ」とか「TOKYO MX」とかの名前を付けておけば、「Hey siri, フジテレビを点けて」が出来るようになるというわけです。結果としてチャンネルの数だけ「スイッチ」が並んでしまい、もうどうにかならんのかと言う状況になるが、今のところこれが唯一の解決方法。
改良方法を思いついたらチャレンジすることとします。僕はチャンネルを20個用意しました。
20個のボタンが並ぶとhomeアプリが専有されてしまうので、「チャンネル」と言う名前の部屋を作りました。
正直、なんかもうちょっと良い方法無いのかよ、とは思ってはいます。homebridge-cmd4でスイッチを作った場合、
スイッチをオンにした際に発行されるsetコマンドと、
定期的に現在の状況を得るgetコマンドが
発行されます。setコマンドが発行されたらチャンネルを変更します。
フジテレビが点いている状態でテレビ朝日を選ぶと、一時的に両方ともスイッチがオンになりますが、次にgetコマンドが発行されたタイミングでフジテレビがオフになるわけです。
タイムラグが出てしまいますが、まあ良しとしよう。BRAVIAコントロールサーバを作る
先ほどのnodeモジュールを元に、RestAPI形式のサーバにしました。
まあ、扱いやすいので。そのため、nodeモジュールのExpressを利用しました。
一部cron処理したいのでnode-cronも入れました。
これは普通のcrontabを使っても、settimeout()を使っても良いのですが。$ cd ~/homebridge/tvChannels $ npm install express $ npm install node-cronnodeサーバスクリプト
次にサーバになるスクリプトを用意しました。
長いし汚いですが全文記載します。
参考に好きなように書き換えて下さい。
サーバスクリプト(node index.js)はこちらをクリック(長いので折りたたみました)
index.js'use strict'; //必要なモジュール //Express var express = require('express'); var app = express(); var port = 3000; //ブラビア const Bravia = require('bravia'); var bravia = new Bravia('10.0.1.X', 80, '8888'); //自分の環境に合わせてください //ユーティリティ系 const util = require('util'); const fs = require('fs'); const cron = require('node-cron'); //設定ファイルなど 自分の環境に合わせてください //チャンネルリスト const channelInfoFile = "/home/pi/homebridge/tvChannels/channelInfo.conf"; //現在のチャンネル const currentChannelCache = "/home/pi/homebridge/tvChannels/channelCache.txt"; var currentChannel; //定期実行 //放送局一覧更新(必要なの?) 毎時実行 cron.schedule('0 * * * *', () => refreshCannelList()); //現在チューニングされているチャンネルの情報更新:30秒ごと cron.schedule('*/30 * * * * *', () => getChannelToBravia()); //API サーバ //現在チューニングされているチャンネル情報の取得 app.get('/channel',function(req,res){ getChannel() .then(nowChannel => { res.json( nowChannel ); }); }); //与えられたチャンネルが現在チューニングされているチャンネルかの確認 //cmd4から Get で呼ばれるよ app.get('/channel/:channel',function(req,res){ let channel = req.params.channel; ckChannel(channel) .then(ckResult => { console.log(JSON.stringify(ckResult)); res.json( ckResult ); }); }); //与えられたチャンネルに変更する //cmd4から Set で呼ばれるよ app.put('/channel/:channel',function(req,res){ let channel = req.params.channel; //与えられたチャンネル let result = setChannel( channel ); //チャンネル変更 res.json( result ); }); app.listen(port); console.log("START Bravia Server"); var isset = function(data){ //便利関数 if(data === "" || data === null || data === undefined){ return false; }else{ return true; } }; //ブラビア操作 //与えられたチャンネルに変更する function setChannel( channel ){ let result; //保存済みの Channelリストファイルから uri を取得 util.promisify(fs.readFile)(channelInfoFile, 'utf-8') .then(channels =>{ channels = JSON.parse(channels); if( channels[channel] !== undefined ){ let uri = channels[channel].uri; return uri; }else{ throw new Error('Channel:' + channel + ' is not exist.'); } }) .then(uri=>{ //リストを取得したあとで braviaPowerOn() //テレビの電源がオフなら点ける .then(powerOnResult=>{ //電源が点いたあとで bravia.avContent.invoke('setPlayContent','1.0',{"uri":uri}) //チャンネルを変える .then(setPlayContentResult =>{ getChannelToBravia(); //チャンネル情報を更新しておく result = {"action":"setChannel", "result" : "success", "message" : "Turn Channel to " +channel}; return result }) }); }) .catch((error) => { //エラー result = {"action":"setChannel", "result" : "error", "message" : error}; console.error(result); }) } //現在チューニングされているチャンネル情報の取得 //キャッシュされた情報から返答 function getChannel(){ return new Promise(function(resolve) { if(isset(currentChannel)){ //現在チャンネルが変数にあればそれを返す resolve(currentChannel); }else{ //変数がセットされてないときは util.promisify(fs.readFile)(currentChannelCache, 'utf-8') //キャッシュファイルを見る .then(cache =>{ currentChannel = JSON.parse(cache); if(isset(currentChannel)){ resolve(currentChannel); }else{ //キャッシュファイルも空だったら getChannelToBravia() //BRAVIAに聞きに行く .then(currentChannel => { resolve(currentChannel); }); } }).catch( err =>{ //ファイルが存在しないとき getChannelToBravia() //BRAVIAに聞きに行く .then(currentChannel => { resolve(currentChannel); }); }); } }); } //現在チューニングされているチャンネル情報をテレビに問合せて保存する function getChannelToBravia(){ return new Promise(function(resolve) { bravia.avContent.invoke('getPlayingContentInfo','1.0') .then(currentChannelFromBravia => { fs.writeFile(currentChannelCache, JSON.stringify(currentChannelFromBravia, null, 2), "utf-8",(err) => { currentChannel = currentChannelFromBravia; resolve(currentChannel); }); }) .catch(function(){ //電源が入ってないと失敗するので無視する console.log('[BRAVIA Power Off]'); }); }); } //チャンネル確認リクエスト function ckChannel(ckChannel){ return new Promise(function(resolve) { let result; bravia.system.invoke('getPowerStatus' , '1.0') //電源チェック .then(power =>{ if(power.status !== 'active'){ //電源が入ってないとき result = false; let resultJSON = {"result" : result , "checkChannel": ckChannel,"nowChannel": "powerOff"} ; resolve( resultJSON ); }else{ //電源が入っているとき getChannel() .then(nowPlayContent => { if(nowPlayContent.dispNum === ckChannel){ result = true; }else{ result = false; } let resultJSON = {"action":"ckChannel","result" : result , "checkChannel": ckChannel,"nowChannel": nowPlayContent.dispNum} ; resolve( resultJSON ); }); } }); }); } //電源を取得して OFF なら ON にする function braviaPowerOn(){ return new Promise(function(resolve) { bravia.system.invoke('getPowerStatus' , '1.0') .then(power =>{ if(power.status === 'active'){ resolve(power); }else{ bravia.system.invoke('setPowerStatus' , '1.0' , {"status": true}) .then(result =>{ resolve(result); }) } }); }) } // Bravia から Channelリストを取得して保存する // 一覧はテレビの電源が入って無くても取れる様子 function refreshCannelList(){ console.log('[Channel List Refresh Start]'); Promise.all([ bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbt", "stIdx":0, "cnt": 200}) , //地デジリスト bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbbs", "stIdx":0, "cnt": 200}) //BS リスト //お好みに応じて CS や HDMI なども追加 ]) .then( ([isdbt,isdbbs] ) =>{ let channelInfo = makeChannelInfo( [isdbt,isdbbs] ); fs.writeFile(channelInfoFile, JSON.stringify(channelInfo, null, 2), "utf-8", (err) => { if(err) { console.log(err); }else{ console.log('[Channel List Refreshed]') } }); }) } // Bravia から取得したリストを成形する function makeChannelInfo(channels){ let channelInfo = {}; for (var i = 0; i < channels.length; i++) { for (var x = 0; x < channels[i].length; x++) { channelInfo[ channels[i][x].dispNum ] = channels[i][x]; } } return channelInfo; }【補足メモ】
Braviaが返すuriにはtripletStrと言う三つの数字が連結されたものが含まれています。
これがなんなのかわかりません。ずっと固定なのか定期的に変更されるのかなどの仕様ががわからないですが、チャンネルを変更するたびに問い合わせるのは効率が良いとは思えないので、サーバ上に保存しておいて定期的に更新することにしました。また、一度作ってみたところ、20個ほどあるすべてのボタンが、自分のステータスがオンかオフかを確認するために、数十秒に一回アクセスしに来ます。。その都度テレビに通信が行くのは精神衛生上よろしくなく、また頻繁にタイムアウトエラーも出てしまっていたので、BRAVIAサーバが定期的に取得しに行き、ボタンたちはキャッシュされたモノを見ることにしました。
(それでもたまにタイムアウトエラーが起きている様子)
登録しているチャンネルの数やサーバに使っているマシンのスペックなどを見ながら確認しに行く間隔を調整してください。(間隔の設定は後述のcmd4のconfigで行う)
さらに、これを常に起動させるためにServiceに登録します。
ホントはユーザー権限で入れるのがお行儀良いのだと思いますが、あんまり気にしない。
/etc/systemd/systemに以下のファイルを作ります。sudoしてね。$ sudo vi /etc/systemd/system/bravia.servicebravia.service[Unit] Description = Sony BRAVIA Control Server After = network.target [Service] ExecStart =node /home/pi/homebridge/tvChannels/index.js Restart = always Type = simple [Install] WantedBy = multi-user.target置いたらサービスに登録します
$ sudo systemctl start bravia #起動出来るかな? $ systemctl status bravia #動いてるか確認 $ curl http://localhost:3000/channel #動いていればこれで今受信中の番組情報が出る $ sudo systemctl enable bravia #大丈夫そうなら次回以降も自動起動するように登録これで、Raspberry PiがBRAVIA操作サーバになりました
cmd4の設定
続いて、cmd4の設定を行います。
homebridge-config-ui-xに戻って下さい。
画面上部の【コンフィグ】を押しましょう。このJSONの中に、以下のセクションを追加します。
config.json{ "platform": "Cmd4", "name": "Channel", "accessories": [ { "type": "Switch", "name": "NHK", "state_cmd_suffix": "011", "on": false, "state_cmd": "/home/pi/homebridge/tvChannels/bravia.sh", "polling": [ { "on": true, "interval": 20, "timeout": 8000 } ] }, { "type": "Switch", "name": "Eテレ", "state_cmd_suffix": "021", "on": false, "state_cmd": "/home/pi/homebridge/tvChannels/bravia.sh", "polling": [ { "on": true, "interval": 20, "timeout": 8000 } ] }, ………以下必要なチャンネルだけ続ける ] }アクセサリー
accessoriesとしてスイッチ"type": "Switch"をたくさん作っていきます。
チャンネルの名前も"name":"NHK"付けてあげます。この名前でチャンネルを決めます。
起動した直後の設定はオフ"on": falseです。cmd4はボタンが押されたらSet / 一定間隔でGetが発行させると、前述しました。
そのコマンドは、"state_cmd"に書かれたものになります。
引数としてnameを送りますが、それで管理するのはちょっと不安ですので、"state_cmd_suffix"としてチャンネル番号を付与して送るようにしました。前述のBraviaServerもその前提で書きました。
また、Getコマンドは上記のpollingで設定した間隔(秒)で送られます。timeoutはmsです。この結果、cmd4は以下のようなcmdを実行します。
Set時(Button押したとき)
/home/pi/homebridge/tvChannels/channel.sh Get 'Eテレ' 'On' 021
Get時(20秒に一回)
/home/pi/homebridge/tvChannels/channel.sh Set 'Eテレ' 'On' true 021
引数の数が違うことに注意が必要です。起動用スクリプト
最後にcmd4が叩きにくるスクリプトも用意します。
cmd4が直接BRAVIAサーバにリクエストする作りはちょっと知恵を使わないといけないので、Wrapper替わりになるシェルスクリプトを置きます。
なお、jqを使っていますので入ってなかったら入れておいて下さい。
curlも使ってますが、さすがに標準で入っていると思う。まあ念のため書いておく。$ sudo apt-get install jq $ sudo apt-get install curl以下の起動用スクリプトを
/home/pi/homebridge/tvChannels/bravia.sh(cmd4のコンフィグで指定した場所)
において下さい。bravia.sh#!/bin/bash if [ "$1" = "Get" ]; then JSON=$(curl -s http://localhost:3000/channel/${4}) result=$(echo ${JSON} | jq '.result') if [ ${result} = true ]; then echo 1 else echo 0 fi exit 0 fi if [ "$1" = "Set" ]; then if [ "$3" = "On" ]; then if [ "$4" = "true" ]; then JSON=$(curl -s -XPUT http://localhost:3000/channel/${5}) echo 1 fi fi exit 0 fi exit 1保存したら実行許可を付けておきましょう。
$ chmod 0755 /home/pi/homebridge/tvChannels/bravia.sh【補足】
cmd4で叩かれたスクリプトは標準出力に結果を返す(echoする)必要があります。
返す結果はcmd4で作ったアクセサリの種類(操作するものの種類)によります。
スイッチの場合はOnかOffしかありませんので、どちらの状態なのかを返します。「On」という状態がtrueかfalseかを返す、と理解してください。
上記スクリプトでecho 1となっているものは、On状態がtrue。つまりOn。
echo 0となっているものはOn状態がfalse。つまりOffです。
Getコマンドでは現在のテレビの状態を調べて、ボタン自身のチャンネルと実際にテレビに映っているチャンネルが一致すればtrueを返すのでonになります。一致してなかったらfalseを返すのでoffになります。
その結果、20秒経ってGetコマンドが呼ばれた際に、OnのままになっているチャンネルがOffになり、実際のテレビの状態と一致します。スイッチではなくたとえばボリュームを作った場合は
echo 40のように操作後の値を返す必要があります。この場合は「ボリュームが40」と言う意味になります。最後に
これで、Hey! siriとやりたかったことは実現出来ました。
紆余曲折しちゃったんですが、結局API叩いているので、最初に入れたhomebridge-braviaプラグイン無しで、テレビの電源入れるAPI叩くスイッチ用意すれば良いんじゃないの?とか、これならhomebridgeのプラグインを新しく作るのが良いんじゃないの?とかそんな気もします。BRAVIAのAPIはいろんなことが出来るので、音量調整とかも可能ですし、リモコンをエミュレートすれば視聴中の番組録画なども出来そうです。
ぜひチャレンジしていただければな、と思ったりもします。
- 投稿日:2020-08-20T18:46:36+09:00
第4回:ニュースフィード一覧画面を作る(画面遷移とディレクティブ)
第4回:ニュースフィード一覧画面を作る(画面遷移とディレクティブ)
今回はニュースフィードの一覧画面を作成していきます。
ニュースフィード一覧画面用のコンポーネントを追加する
コンポーネントを追加する
% ng g component components/newsfeeds
src/app/components/newsfeedsにコンポーネントが追加される。モジュール更新内容の確認
src/app/app.module.tsにコンポーネントが追加される。更新された内容
import { NewsfeedsComponent } from './components/newsfeeds/newsfeeds.component';declarations: [ AppComponent, SignInComponent, FeedsComponent ],ルーター更新内容の確認
src/app/app-routing.module.tsにルートを登録する。下記を追記します。
import {NewsfeedsComponent} from './components/newsfeeds/newsfeeds.component';const routes: Routes = [ { path: '', redirectTo: '/sign-in', pathMatch: 'full'}, { path: 'sign-in', component: SignInComponent }, { path: 'newsfeeds', component: NewsfeedsComponent }, ];画面遷移を行う
src/app/components/sign-in/sign-in.component.tsに下記の修正を行うimport { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @Component({constructor( private router: Router ) { } ngOnInit(): void { } onClickSignIn() { // Go to newsfeeds page. this.router.navigate(['./newsfeeds']); }
src/app/components/sign-in/sign-in.component.htmlに下記の修正を行います。
Sign inボタンがクリックされたらonClickSignIn()メソッドが呼ばれます。<button (click)="onClickSignIn()">Sign in</button>ログイン画面の入力内容が正しいときに画面遷移するようチェックする
実際はログイン時にサーバに問い合わせを行い、ログインできたら画面遷移を行うことになりますが、まだサーバがないので何か入力されていたらOKとします
onClickSignIn() { if (this.email !== '' && this.password !== '') { // Go to newsfeeds page. this.router.navigate(['./newsfeeds']); } }ニュースフィード一覧画面にフィードを表示する
ニュースフィード用のデータを用意する
まだサーバもデータベースも用意できていないので、ダミーデータを用意します。
newsfeeds.component.tsに下記のようにnewsfeedsプロパティを追加してください。export class NewsfeedsComponent implements OnInit { newsfeeds = [ { message: 'あけましておめでとう!', createdAt: new Date('2020-01-01T02:23:30'), }, { message: 'メリークリスマス!', createdAt: new Date('2019-12-25T10:52:02'), }, { message: 'ハッピーハロウィーン!', createdAt: new Date('2019-10-31T20:13:55'), }, ]; constructor() { }画面に表示する
newsfeeds.component.htmlを下記のように修正してください。
<div id="newsfeeds"> <article *ngFor="let newsfeed of newsfeeds" class="newsfeed"> <p class="message">{{newsfeed.message}}</p> <p class="createdAt">{{newsfeed.createdAt.toLocaleString()}}</p> </article> </div>
htmlタグ内にngFow=""を入れることで、繰り返しタグを表示することができます。
newsfeeds配列の中から一つずつ要素を取り出し、newsfeed変数に格納されます。
この*ngForをディレクティブと呼びます。他にngIfや*ngSwitch等があります。
ディレクティブを利用して作成した画面もプロパティの値が変更されるとリアルタイムに画面が更新されます。スタイルを変更する
見栄えをよくするために装飾します
#newsfeeds { padding: 20px; background-color: #1877f2; width: 100%; height: 100%; box-sizing: border-box; .newsfeed { width: 100%; background-color: white; border-radius: 5px; padding: 10px; margin-bottom: 20px; box-sizing: border-box; .message { color: #1c1c1c; } .createdAt { color: #a0a0a0; font-size: 10px; text-align: right; } } }最後に
今回はここまでです。次回はパイプを作っていきます。
今回開発したソースコードはGitHubに入っています。
- 投稿日:2020-08-20T18:46:13+09:00
Webアプリ開発ロードマップ
Webアプリ開発ロードマップ
facebookのようなSNSサイトを作っていくことで、Webアプリの開発基礎を学ぶ
ターゲット
プログラミング経験者でWebアプリ開発未経験の人向け。
目次
第1回:Web開発の変遷
第2回:Angularの環境構築
第3回:ログイン画面を作る(データバインディング)
第4回:ニュースフィード一覧画面を作る(画面遷移とディレクティブ)
第5回:ニュースフィードの日付フォーマットを変換する(パイプ)
第6回:Node.jsの環境構築
第7回:ニュースフィード取得サーバIFを作る
第8回:ニュースフィード取得サーバIFをWebアプリから呼び出す
第9回:ニュースフィード投稿サーバIFを作る
第10回:ニュースフィード投稿サーバIFをWebアプリから呼び出す
第11回:データベースを使う
第x回:データベースとサーバIFを繋ぐ
第x回:ログインIFを作る
第x回:ログインIFをWebアプリから呼び出す
第x回:ログイン状態で画面遷移を抑止する(Guard)
第x回:AWS基礎
第x回:Cognitoを使う
第x回:API GatewayをLambdaを使ってみる
第x回:DynamoDBに繋いでみる
- 投稿日:2020-08-20T18:35:37+09:00
Express 入門
公式ドキュメントもかなりシンプルでわかりやすいですが、自分用メモとして。
Express(日本語ドキュメント)最小構成
最低限、これで動かせるよ!というコードです
さくっとWebサーバ立ててなにか試したい時にインストール
$ mkdir myapp $ cd myapp $ npm init # entry point: app.js => 任意のファイル名 $ npm install expressディレクトリ構成
(root) ├ package.json ├ package-lock.json ├ app.js └ node_modulesコード
app.jsconst express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => console.log('Example app listening on port 3000!'))実行
$ node app.js # go to http://localhost:3000/http://localhost:3000/ にアクセスすると「Hello World!」と表示されます
プロジェクトの雛形を作成する
Express プロジェクトの雛形を作成します
テンプレートエンジンの設定や、ディレクトリの作成もしてくれます
本格的にプロジェクトを作成する場合はこちらがおすすめ
詳しくはこちらCLIインストール
雛形作成に必要なパッケージをグローバルにインストールします
$ npm install express-generator -gPJ作成
$ express --view=pug myapp # --view=pug => 任意のテンプレートエンジン(未指定の場合はjadeになります) # myapp => 任意のPJ名 $ cd myapp $ npm install指定できるテンプレートエンジンやその他のオプションは↓で確認できます
$ express -h実行
$ DEBUG=myapp:* npm start # go to http://localhost:3000/ルーティング
メソッド
app.METHOD(PATH, HANDLER)のような形で記述します
Express は、すべてのHTTPリクエストメソッドに対応するメソッドをサポートしています
メソッドの一覧はこちらvar express = require('express') var app = express() app.get('/', function (req, res) { res.send('GET request') }) app.post('/', function (req, res) { res.send('POST request') })また、
all()を用いて、全てのメソッドのミドルウェア的なメソッドを定義することも可能です
next()でそれぞれのメソッドのハンドラに引き渡しますapp.all('/', function (req, res) { res.send('Any request') next() })パス
ルートのパスには正規表現も利用可能です
app.get(/.*fly$/, function (req, res) { res.send('/.*fly$/') })パラメータ
:paramの形で記述します
req.paramsで取得できますapp.get('/users/:userId/books/:bookId', function (req, res) { res.send(req.params) // => { "userId": "hoge", "bookId": "fuga" } })レスポンス
よく使いそうなもの掲載
一覧はこちら// さまざまなタイプのレスポンスを送信します res.send('GET request') res.send('<p>some html</p>') res.send({ some: 'json' }) // etc... // ビュー・テンプレートをレンダリングします res.render('index') // JSON レスポンスを送信します res.json({ user: 'tobi' }) res.status(500).json({ error: 'message' })メソッドをまとめて記述する
GET, POST などのメソッドをルートごとにまとめて記述することができます
すっきりしてよき!app.route('/book') .get(function (req, res) { res.send('Get a random book') }) .post(function (req, res) { res.send('Add a book') }) .put(function (req, res) { res.send('Update the book') })ルートハンドラをモジュール化する
ルートごとにファイルを分けて管理できます
大規模なアプリケーション向きbooks.jsvar express = require('express') var router = express.Router() router.get('/', function (req, res) { res.send('GET Books') }) router.get('/:bookId', function (req, res) { res.send('GET Some Book') }) module.exports = routerapp.jsvar books = require('./books') // ... app.use('/books', books)$ curl http://localhost:3000/books # => GET Books $ curl http://localhost:3000/books/12 # => GET Some Book静的ファイル
画像や CSS/JS など、静的ファイルをロードできるようにします
詳しくはこちらapp.js// public ディレクトリに入っているファイルをロードできるようにする app.use(express.static('public')); // http://localhost:3000/images/kitten.jpg // http://localhost:3000/css/style.css // http://localhost:3000/js/app.js // http://localhost:3000/hello.html
- 投稿日:2020-08-20T17:45:05+09:00
nvmをインストールしたのに、-bash: nvm: command not foundになる。
https://github.com/nvm-sh/nvm#install--update-script
こちらのcurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bashを使ってnvmをインストールし、特にエラーもなく完了したはずなのに
$ nvm --versionで
-bash: nvm: command not found
と言われた。色々試した結果、
$ source ~/.nvm/nvm.shを実行することで解決。
頑張った末に、手順の下の方に”Troubleshooting on macOS”を見つけたので、ドキュメントを読むのが大事だと思いました。
追記(2020年8月21日)
このままだと、起動のたびに同じコマンドを叩かないといけなかったので、起動のたびに有効化してくれるように、/etc/profile.d/以下にシェルを置いておきます。
/etc/profile.d/nvm.sh
$ source ~/.nvm/nvm.sh
- 投稿日:2020-08-20T07:52:26+09:00
npmのライブラリの比較と評価方法:npm trends & Github
人気のあるライブラリをnpm trendsで探す
npm trendsは、指定したライブラリのカテゴリと近いものを提示し、選択するとダウンロード数を比較してくれます。
たとえば、dom parserを比較すると以下の用になります。とりあえず、ネットで一つライブラリを探してきて、npm trendsに入れると、候補を提示してくれるので、それでダウンロード数を比較をします。
npm trendsで提示されたライブラリのGithub上でのstarチェック
npm trendsのグラフだけでは、人気を計るのは不十分です。Githubのstarもチェックします。
例えば、上のdom parserの例だと、parse5が一番多いですが、starの数は、cheerioの方が多いのです。google → npm trends → Githubの順でライブラリの調査
タイトルの順番でライブラリを調査すると良さそうなものが見つかるように思います。
ネットの情報はかならず、「比較」と「評価」が必要になりますが、npmについてはこういう手順もよいのではと思います。







