約1カ月間割と余裕をもって勉強しました。
8月中に取りたかったので、1週間の帰省兼家族旅行中に一日空いてる日があったのでそのタイミングで梅田テストセンターで受験。
次は Advanced Networking - Speciality を受験します。
こちらは最も難関とも言われているので余裕をもって1カ月、9月末までには取りたい。
約1カ月間割と余裕をもって勉強しました。
8月中に取りたかったので、1週間の帰省兼家族旅行中に一日空いてる日があったのでそのタイミングで梅田テストセンターで受験。
次は Advanced Networking - Speciality を受験します。
こちらは最も難関とも言われているので余裕をもって1カ月、9月末までには取りたい。
タイピングが遅いのが少しコンプレックスなので、タイピング速いと思われたくてちょくちょく練習してます。
ひたすらタイプウェルをやって、苦手なワードは TypeLighter で繰り返し練習です。
とは言うものの、ガチタイパーの方々と比べれば赤ちゃんみたいなもんなので、特に練習の戦略はありません。
ひたすら打ちまくるだけです。
ずっと成績が伸びなかったんですが、タイピングの姿勢を変えたところ 50 秒を切ることができました。
苦手語句を表示してくれるのはうれしいが、簡単な単語ばかりが並んでいて謎です。
Spaceキーのミスもカウントされるんですかね?
試しに寿司打をやってみたところ、今一歩 2 倍のスコアには届きませんでした。
タイプウェルとは異なり、ワードの先読みができないので秒あたりのタイプ数は少なめですね。
ワード見てから打ち始めるまでに速くて 0.6 秒もかかるくらい動体視力がよくないので(ネット見てると大体みんな意識しなくても 0.5 秒くらいはいけてるみたい)。
ワード慣れすれば 2 倍くらいはいけるかもしれません。
ガチタイパーになりたいわけではないので、50 秒をコンスタントに取れるようになれば一旦タイピング練習はやめようと思います。
いつになるやら。。。
SAP 合格しました。
2週間くらい勉強サボってたので実質2週間の勉強期間。不安だったが一応合格(821)。
一番ムズいって言われてるが、正直 SAA と違いがあまりわからなかった。
次は DOP 受けます。そのあとは SAP と並んで難しいと言われてる Advanced Network Specialist かな。
今はラボ試験ないんですね~。いつ復活するのかしら。
次は SAP 取得します! ムズいらしいが1カ月以内には取りたいのでとりあえず三週間後くらいに受けます。
958点で合格しました。思いのほか高得点!
次は今月末あたりにSOAを受けようかと思います。現在はラボ試験は中止されてるんですね。
いつもナビタイムの「次のバス」スキルにお世話になっていました。
これは「最寄りのバス停を登録しておくと "次のバスのスキルを開いて" というだけで次のバスが何分後に来るか、さらにその次のバスは何時何分か」を教えてくれる便利なスキルです。
ところが、このスキルが今月末にサービス終了するようです。
これは非常に困るので自分で Alexa スキルを作ります。
Alexa を開発できるようにするための事前準備は前回の記事をご参照ください。
まずは結論から。以下のコードで実現できました。※時刻表の値はダミーです。
# -*- coding: utf-8 -*- import logging import ask_sdk_core.utils as ask_utils from ask_sdk_core.skill_builder import SkillBuilder from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.dispatch_components import AbstractExceptionHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_model import Response # 日付時刻系インポート from datetime import date, datetime, timezone, timedelta import jpholiday import math logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # 時刻表マスタ ※祝日はsunday扱い def get_today_table(weekday_en, year, month, day): master_timetable = { "weekday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 23, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 48, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ], "saturday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 45, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ], "sunday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 45, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ] } return master_timetable[weekday_en]; class LaunchRequestHandler(AbstractRequestHandler): def can_handle(self, handler_input): return ask_utils.is_request_type("LaunchRequest")(handler_input) def handle(self, handler_input): # 日時取得 timezone_tokyo = timezone(timedelta(hours=9)) datetime_now_jst = datetime.now(timezone_tokyo) year = datetime_now_jst.year month = datetime_now_jst.month day = datetime_now_jst.day # 曜日判定:月曜0-日曜6 weekday = date(year, month, day).weekday() weekday_en = "" if weekday == 5: weekday_en = "saturday" elif weekday == 6: weekday_en = "sunday" else: weekday_en = "weekday" # 祝日判定 if jpholiday.is_holiday(date(year, month, day)): weekday_en = "sunday" # 時刻表取得 timetable = get_today_table(weekday_en, year, month, day) # 次とその次のバスを取得 next_bus = [] for item in timetable: if datetime_now_jst < item: next_bus.append(item) next_bus = next_bus[:2] # リストのサイズで終バス判断し返却値を出し分ける if len(next_bus) == 0: speak_output = "もう今日のバスはありません。" elif len(next_bus) == 1: diff = next_bus[0] - datetime_now_jst minute_after = math.floor(diff.total_seconds()/60) next_bus_hour = next_bus[0].hour next_bus_minute = next_bus[0].minute speak_output = f"次のバスは{minute_after}分後の{next_bus_hour}時{next_bus_minute}分です。このバスが最終です。" else: diff_1 = next_bus[0] - datetime_now_jst minute_after_1 = math.floor(diff_1.total_seconds()/60) diff_2 = next_bus[1] - datetime_now_jst minute_after_2 = math.floor(diff_2.total_seconds()/60) next_bus_hour = next_bus[1].hour next_bus_minute = next_bus[1].minute speak_output = f"次のバスは{minute_after_1}分後です。その次は{minute_after_2}分後の{next_bus_hour}時{next_bus_minute}分です。" return ( handler_input.response_builder .speak(speak_output) # .ask(speak_output) # これがあるとセッションが維持される .set_should_end_session(True) # 1回しゃべったらセッション切る .response ) sb = SkillBuilder() sb.add_request_handler(LaunchRequestHandler()) lambda_handler = sb.lambda_handler()
めちゃシンプルなので特に解説することもないかと思いますが、どうせ忘れるので自分自身のメモとして残しておきます。
今回は祝日判定のために jpholiday
を利用するので requirements.txt
に追記します。
boto3==1.9.216 ask-sdk-core==1.11.0 jpholiday # 追記
時刻計算のために datetime
、timedelta
を分に換算するために math
をインポートします。
from datetime import date, datetime, timezone, timedelta import jpholiday import math
時刻表はハードコーディングします。自分だけで使うしバス停も1つなのでこっちの方が圧倒的保守性に優れてるためです。
DynamoDB 使うならパーティションキーに バス会社名-路線名-バス停名
、ソートキーに 時刻
がよいでしょうか。
それなら範囲検索できますしおすし(知らんけど)。
def get_today_table(weekday_en, year, month, day): master_timetable = { "weekday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 23, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 48, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ], "saturday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 45, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ], "sunday": [ datetime(year, month, day, 6, 5, 00, tzinfo=timezone(timedelta(hours=9))), datetime(year, month, day, 6, 45, 00, tzinfo=timezone(timedelta(hours=9))), ... # 以下略 ] } return master_timetable[weekday_en];
「あと何分後」を計算するために時刻差を取得する必要がありますが、バス時刻表って日付ごとではなく曜日ごとです。
そのため year
, month
, day
を指定することで "その日" の時刻表を取得できるようにしています。
weekday_en
によって曜日ごとの時刻表を出し分けています。
LaunchRequestHandler
の中身は普通にごり押しで実装してるだけです。
前回、エラーをキャッチしてしまう問題は .ask(speak_output)
していたためでした。これをするとセッションが維持されるっぽいので外しています。
また、Alexa を放っておいてもスキルが勝手に終了せず2回しゃべってしまう問題はAlexa が一回しゃべったらすぐにセッションを切る設定(.set_should_end_session(True)
)をすることで解決しました。
あと、時刻表リストを全部ぶん回して最後に silce
しててあんまりかっこよくはないんですが、時刻表なんか高々数十くらいのオーダーなんでこれでいいです。
動きました。※バス時刻はダミーです。
とりあえず動いてよかったです。
ベータ版テストでしか動かせてないので、次はちゃんとプライベートで公開できるようにしたいですね(Alexa for Business めんどくさそうなのでベータテストを3カ月に1回更新するでもいいかも???)。
いつもナビタイムの「次のバス」スキルにお世話になっていました。
これは「最寄りのバス停を登録しておくと "次のバスのスキルを開いて" というだけで次のバスが何分後に来るか、さらにその次のバスは何時何分か」を教えてくれる便利なスキルです。
ところが、このスキルが今月末にサービス終了するようです。
これは非常に困るので自分で Alexa スキルを作ります。
Alexa スキルは作ったことがなく、「Lambda とか DynamoDB くらいは使うんだろうなー」くらいに考えていましたがどうやらスキルを作るのに AWS は不要らしいです。
もちろん凝ったものを作ろうと思えば別でしょうが、今回は時刻表引いて返すだけのドシンプルなものです。
できれば簡単に作りたいですね。
Alexa Developer Console とゆーのに EC サイト(NOT AWS)の Amazon アカウントでログインします。
スキルの作成
をクリック。
適当にスキルの名前を付けます。「次のバス」って名前を付けちゃうと既存ナビタイムアプリとウェイクワードが衝突します。
まあいいでしょう。どのみち後で変えることになります。
エクスペリエンスのタイプを「その他」に設定。
この項目自体にあまり意味はなくて、次の「モデルを選択する」で選べる選択肢のフィルタリングに使われてるっぽいです。
モデルは「カスタム」に設定。
カスタムにしとけば後で融通利くかなという感じ。変にプレビルド設定されてもよくわからんし。
「ヘルプドキュメント」のリンクが貼られてるが読みませ~ん。家電使うときに困ってから説明書読むタイプです。
ホスティングサービスは「Alexa-hosted(Python)」にしました。
下画像の内容を見る限りだと Alexa-hosted
が Alexa 側で Lambda と DynamoDB の無料枠を使わせてくれるみたい。
Alexa-hosted
でも無料枠を超えたら AWS アカウントに紐づける必要があるようです。
なぜ Node.js でなく Python なのか。わざわざ Python の選択肢あるのにサーバ側 JavaScript で書くの辛くない?ってだけです。
ホストリージョンは「オレゴンに」設定。そもそも東京リージョンという選択肢はない(独自のプロビジョニング
を選択してたら使えるのかな?)。
うーん、バス時刻聞くだけなのに海越えていくなんてロマン溢れる~☆彡
ちな、バージニア北部よりオレゴンの方がちょっと速いみたいです。まあ普通に海底ケーブル太平洋通ってるから西海岸の方が速い。
各AWSリージョンとのネットワーク遅延を計測したい | DevelopersIO
テンプレートは「スクラッチで作成」を選択。これは悩んだが、まぁググればそれっぽいコードがゴロゴロ落ちてるだろという期待。
最終的な設定はこちら。
さっそく意味わからんポータルが出てきた!右側の「スキルのビルド」の部分はビルド設定っぽいが全部チェックマークついてるので無視。
上タブで「テスト」に移動し、非公開
-> 開発中
に変更。
マイクアイコンを長押ししながら発話すると返答が返ってきます。すごい!
上タブで「公開」に移動し、適当に入力。
そうか、オレゴンリージョンにデプロイするからソフトウェア輸出規制に引っかかる恐れがあるのか。個人情報も企業機密や軍事機密もないのでOKでしょう。
ベータテストができないとのことで困る。が、とりあえず進めるところまで進んでみる。
エラーが出てきた!やったー!!これさえ解消すればベータテストできるはず。
サンプルフレーズを入力。これに伴い、スキル名を 次のバス
から バスの時間
に変更。
スキルアイコンを適当に作る。アイコンビルダーないかなーと思ったらやはりあった。神。
適当にバスのアイコンを作って設定。
さっきのベータテストの設定が記入できるようになったので適当に記載し有効化。
※メアド晒したくないので画像上はブランクです。
ベータテストは有効期限付きかー。まあそりゃそうか。
永続的にデプロイしたいがそのためにはやはり「Alexa for Business」が必要なんだろうか。
再度検証するとエラーは全て解消してました。
招待メールが届いているのでリンクをクリックし、スキルをデバイス側に登録する。
さっそく実行!※注意:再生するとあなたの家の Alexa が反応します
あれ。。。全然反応しない。。。
コンソールを見返す。ウェイクワードっぽい設定箇所は以下。
で、さっき 次のバス
から バスの時間
に設定しなおしたのは「公開設定」のみ。
公開設定は文字通り「公開するための設定」であって、Alexa スキルの設定ではないですね。ただのスキルの説明文です。
なので、「スキルの呼び出し名」を変更する必要がありました。
再度スキルをビルドし、テスト。ちゃんと答え返ってきてるっぽい!
コード書くといっても返答を日本語にするだけです。
上タブから「コードエディタ」へ移動。すでにサンプルコードが書かれてる。
以下の GitHub サンプルと同じコードだと思われ。
さっきのテストコードでの返答が "Welcome, you can say Hello or Help. Which would you like to try?" だったので、LaunchRequestHandler
の中身の文字列だけ書き換えればよさそう。
書き換え前
書き換え後
ワードで分岐するような性質のスキルを作るわけじゃないので、handler_input
は触らなくていいですね。
「保存」「デプロイ」し、テストタブに移動してテストする。
ちゃんと動いた!
既に Lambda へ反映されてるのでデバイス側のスキルは更新しなくてもいいかな?
とりま Alexa にしゃべりかける。
動いてはいるが、返答を2回繰り返したり、なんか英語話し出したりしている。
たぶん FallbackIntentHandler
に反応してる。
ちょっとコードの意味を調べる。
an_handle:can_handleメソッドは、SDKによって呼び出され、指定されたハンドラーが受け取ったリクエストを処理できるかどうかを判断します。この関数はハンドラー入力オブジェクトを受け付け、ブール型を返すように想定されています。メソッドがTrueを返せば、ハンドラーによってリクエストが正常に処理されたと考えられます。Falseを返す場合、ハンドラーが入力リクエストを処理できず、したがって実行されず完了もしなかったと考えられます。HandlerInputオブジェクトにはさまざまなアトリビュートがあるため、リクエストを正常に処理できるかどうかをSDKが判別するための任意の条件を作成できます。 handle:handleメソッドは、リクエストハンドラーを呼び出すときにSDKによって呼び出されます。この関数には、ハンドラーのリクエスト処理ロジックが含まれており、ハンドラー入力を受け取り、応答オブジェクトを返します。
Alexaでの意味はユーザーの意図や目的を実現するためのアクション、といったところでしょうか。ユーザーが豆知識を教えてほしいという目的のときは「豆知識」というと「GetNewFactIntent」が実行され、ユーザーがヘルプを聞きたいときは「ヘルプ」と言うと「HelpIntent」が実行されるという感じですね。ユーザーの目的ごとにインテントが必要になる、というイメージです。 今回追加する「FallbackIntent」はユーザーが意図や目的をもってAlexaに話しかけても、Alexa側にそれに対応するためのインテントがない場合に実行されるインテントです。ちょっと特別なインテントですね。 とゆーことで、
LaunchRequestHandler
以外のハンドラーを使わないようにする。
ふむふむ... そーゆーことか!わかったぞぉ!
今回作るスキルはインテントとか全く関係ないのでインテントハンドラーは必要ない。
SkillBuilder.add_request_handler
をコメントアウトすれば無効化できる気がする。
ただし、最後のガードとしてまるっと例外キャッチする sb.add_exception_handler(CatchAllExceptionHandler())
は残しておく。
保存・デプロイして手元の Alexa で試した結果。
2回発話してしまうこと以外は想定通り(ちゃんと最後に例外キャッチした結果が返ってくる)。
今日は夜遅いのでここまで!
次はバス時刻表をしゃべらせるようにコーディングしていきます。
残課題