いつもナビタイムの「次のバス」スキルにお世話になっていました。
これは「最寄りのバス停を登録しておくと "次のバスのスキルを開いて" というだけで次のバスが何分後に来るか、さらにその次のバスは何時何分か」を教えてくれる便利なスキルです。
ところが、このスキルが今月末にサービス終了するようです。
これは非常に困るので自分で Alexa スキルを作ります。
Alexa を開発できるようにするための事前準備は前回の記事をご参照ください。
talkeyboid.com
コード書くぞ!
まずは結論から。以下のコードで実現できました。※時刻表の値はダミーです。
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)
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
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)
.set_should_end_session(True)
.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
github.com
時刻計算のために 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
しててあんまりかっこよくはないんですが、時刻表なんか高々数十くらいのオーダーなんでこれでいいです。
動かすぞ!
動きました。※バス時刻はダミーです。
www.youtube.com
とりあえず動いてよかったです。
ベータ版テストでしか動かせてないので、次はちゃんとプライベートで公開できるようにしたいですね(Alexa for Business めんどくさそうなのでベータテストを3カ月に1回更新するでもいいかも???)。