いつもナビタイムの「次のバス」スキルにお世話になっていました。
これは「最寄りのバス停を登録しておくと "次のバスのスキルを開いて" というだけで次のバスが何分後に来るか、さらにその次のバスは何時何分か」を教えてくれる便利なスキルです。
ところが、このスキルが今月末にサービス終了するようです。
これは非常に困るので自分で 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()
めちゃシンプルなので特に解説することもないかと思いますが、どうせ忘れるので自分自身のメモとして残しておきます。
import
今回は祝日判定のために 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回更新するでもいいかも???)。