今回はラズパイを使った目覚まし時計を作ってみたのでご紹介。
sshでアクセスしてcronに登録するようなちょっと特殊な仕様なので市販品のようにボタンやつまみでセットするような機能はない。
構想自体は1年以上前にあって、その時はArduinoで作ろうとしてたんだけど色々面倒で放置していた。
thom.hateblo.jp
今回は仕様を変更して好きな音楽ファイルを目覚ましに使いたいので、最初はDFPlayer miniというArduino用のmp3モジュールを使うつもりだったんだけど最近DFPlayer miniはチップ違いのものが複数出回っていて公式ライブラリで動かない。画像と違うものが届くので何度か発注と返品を繰り返して諦めた。
次にラズパイZeroも検討したけどこちらは音楽出力用のジャックがなく、最近のラズパイOSでGPIOからの音楽出力がうまくいかないという記事を見たので断念。結局ラズパイ3B+に落ち着いた。
たかが目覚ましにラズパイ3B+ってのはかなりオーバースペックなんだけど、マイコンと違ってLinux機なのでOSの処理が優秀でかなりシンプルに作れる。時刻はNPTで勝手に合わせてくれるし音楽はaplay呼べば済むし目覚ましのトリガーもcronで済むのでマジで楽。
ハード構成
仮組みではあるが、次のような構成になっている。
ブレッドボードのまま使うつもりはないし停止スイッチが生タクトなのはもう少しなんとかする予定。
スヌーズスイッチだけはロフトベッドから届く位置に付けるので長いコードを繋いでいる。こんなに長くなくていいけどとりあえず仮で。
ラズパイはGPIO17がスヌーズ、GPIO18が停止である。
ボタンはラズパイの内蔵プルアップ抵抗を使うので抵抗器は不要となっている。
コード
アラーム用のコードを先に紹介するのが自然だけど、仕組みの説明上あえて停止ボタン用のコードを先に紹介する。
wokeup.py (停止ボタン用コード)
実はまだあまり作りこんでなくて、次のアラームを鳴らないようにする抑止機能しかない。音楽を止めるのはあくまでスヌーズ。
まぁこのへんはすぐ作りこみできそうなので割愛。出来てから書けばと言われそうだけど、熱が冷めないうちに記事書かないと運用始まったら多分モチベ落ちてるので何卒ご容赦を。
まずGPIOの18番をプルアップ設定し、コールバック関数を指定しておく。
ボタンが押される度にstamp.txtに現在時刻yyyymmddHHMMSSを上書き書き込みするという仕組み。
ただそれだけ。これを使ってどうやって停止になるのかはアラーム機能で説明。
"nohup python wokeup.py &"とコマンド実行しておけばssh抜けてもバックグラウンドで実行されるので、そんな感じで常駐させる想定。
手動でkillするときに他のpythonコードと区別したいのでsetproctitleでプロセスタイトルを付けている。
import setproctitle as sp import RPi.GPIO as GPIO import datetime import time BUTTON_PIN = 18 sp.setproctitle("wokeup") def main(): GPIO.setmode(GPIO.BCM) GPIO.setup(BUTTON_PIN,GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=callback, bouncetime=300) try: while(True): time.sleep(1) except KeyboardInterrupt: print("break") GPIO.cleanup() def callback(channel): dt_now = datetime.datetime.now() f = open('/home/thom/stamp.txt', 'w') f.write(dt_now.strftime('%Y%m%d%H%M%S')) f.close() if __name__ == "__main__": main()
alarm.py (アラーム & スヌーズ用コード)
これはcronで実行する想定のコード。つまり8時に起きたければ8時に起動させる。
スヌーズと言ってるけど実は7:00、7:30、8:00、8:15のように4回スヌーズタイミングがほしければそれぞれcron登録するというだけの代物。
市販の目覚ましは5分後にスヌーズとかあるけど、アレ嫌いなんだ。2度寝するなら5分じゃ物足りない。ギリギリまで寝てたいけどギリギリすぎると怖い。余裕をもって起こされた後、あと30分寝させて、さらにもう30分、うーん。。。まだいける、15分!て感じで起きたい人間なのでこういう仕様にした。
実行するとまずstamp.txtから時刻を取得し、現在時刻と比較する。
差が10,800秒以内、つまり3時間以内であればそのままプログラムを終了する。
つまり7:00、7:30、8:00、8:15と設定していても、7時の段階で停止ボタンが押されていれば以降3つのアラームはスキップされるという仕組み。
要するに停止というよりはコード内で指定した時間スキップする機能なので翌日のアラームは普通に鳴る。
過去3時間以内に停止ボタンが押されていなければaplayでwavファイルが繰り返し再生されるが、スヌーズを押すとプログラムが終了される。
スヌーズと言いつつもプログラム的に見れば停止ボタン。cronで次の指定時刻に起動されるので機能としてはスヌーズである。
初回起動時に前回アラームが鳴ってる可能性があるのでalarmおよびaplayをkillした後、自身がalarmを名乗る。(先に名乗ると自分もkillされる。)
ボタンでaplayとプログラムをkillするが、念のためatexitでプログラム終了時にもaplayをkill。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import atexit import setproctitle as sp from subprocess import Popen import sys import RPi.GPIO as GPIO import time from datetime import datetime BUTTON_PIN = 17 def main(): f = open('/home/thom/stamp.txt', 'r') data = f.read() mydate = datetime.strptime(data, '%Y%m%d%H%M%S') td = datetime.now() - mydate if td.total_seconds() < 10800: print("exit") sys.exit() global proc cmd = "sudo pkill alarm" proc = Popen(cmd.strip().split(" ")) proc.wait() cmd = "sudo pkill aplay" proc = Popen(cmd.strip().split(" ")) proc.wait() sp.setproctitle("alarm") GPIO.setmode(GPIO.BCM) GPIO.setup(BUTTON_PIN,GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=callback, bouncetime=300) try: while(True): cmd = "aplay /home/thom/th06_05.wav" proc = Popen(cmd.strip().split(" ")) proc.wait() except KeyboardInterrupt: print("break") GPIO.cleanup() def callback(channel): global proc print("Close") proc.kill() sys.exit() @atexit.register def force_umplay(): global proc try: proc.kill() except: print("No process running.") if __name__ == "__main__": main()
今後の運用と改良
現状はGoogle Homeを目覚ましに使っているので、とりあえず並行運用してみて安定稼働できそうなら本番稼働させようと思う。
仮運用はとりあえずブレッドボードで。
今後の改良としては停止ボタンで音楽も止まるようにするのと、音楽ファイルのランダム再生機能の実装と、本当に起きなければいけない時間以降は音楽をマジのアラームに切り替える機能の実装がある。
マジで起きなければならない時間を1分でも超過した際には空襲警報ばりのヤバいやつを鳴らしたい。
現在時刻アナウンス機能も欲しいな。そうなるとボタン2ついるか。
LinuxなのでWeb UIでアラーム設定とかもできそう。
Arduinoでの製作を諦めたからにはせめてラズパイのポテンシャルをちゃんと活かしてよりスマートな目覚まし時計を目指していきたい。
以上