在宅勤務している人、多いですよね。
いつでも配達を受け取れてとても助かります。
しかし...2階で仕事をしていると、
インターホンの音が聞こえにくい!
他のことに集中していると気づかない!!!!
せっかく配達に来てくれたのだから、一発で受け取りたいものです。
エンジニアらしく仕組みで解決しましょう!
忙しい人のための超要約
- インターホンの室内モニタのA接点を使用します(鳴ると接点が閉じる)
- RaspberryPi Zero WH を用いて、A接点のオンオフによりGPIOの出力3.3VをGPIO17に印加する回路を組みます
- GPIO17に印加されたことをPythonスクリプトで検知します
- 検知したらLINE Messaging APIを使用してpush通知を送信します
この説明で理解できる人は、記事全体を読む必要ないと思います。
電子工作初心者でも理解しやすいよう丁寧に書き上げたので、ぜひご覧ください!
目標
インターホンが鳴ったら、2階にいても確実に気づけるようにする。
原因
インターホンから2階までの距離が遠く、音が聞こえにくいこと。
対策立案
1. インターホンの音を大きくする
よく音楽聴きながらお仕事します。
いくら音を大きくしても聞き逃しそう...なので却下。
2. インターホンが鳴ったことをスマホに通知する
エンジニアらしくていいですね!これにします。
細分化するとこんな感じでしょうか。
- インターホンが鳴ったことを検知する
- 検知したらインターネットを通じてスマホに通知する
20240425 追記
Q: 子機を増設すれば解決するのでは?
A: それ以上いけない
具体的な方法の検討
インターホンが鳴ったことを検知する方法の検討
インターホンが鳴ったら信号を外部に出力するような機能があるかを調べます。
ということで仕様確認をします。
我が家は VL-MWD501
というパナソニック製のインターホン室内モニタが設置されています。
仕様書を見ると...室内モニターにA接点があります!
A接点とは、何かをトリガーとして2端子間が閉じるものです。
この場合、インターホンが鳴った時に2端子間が閉じます。
直接信号を出力するわけではありませんが、使えそうですね。
こんな回路を作れば、鳴ったことを検知できそうと考えました。
GNDに接地している理由
この回路は抵抗とGNDを接続しています。 GNDと接続されてない場合を考えてみましょう。A接点がオンのとき、右下にある端子には電圧がかかります。
しかしA接点がオフの時は、右下の端子はどこにも接続されていない状態になります。
これを 浮いている と言います。
浮いている時は非常にノイズを受けやすく、ちょっとしたことで電圧が大きく変動します。
この浮いている状態を避け電圧を安定させるため、GNDに接地しています。
接地する時に使用している抵抗を プルダウン抵抗 と呼びます。
スイッチがオフのときに電圧をLoに落とすので、ダウンです。
(もしスイッチがオンの時に電圧をHiにする回路ならば、プルアップ抵抗と呼びます。)
この検出回路を実現し、後述のスマホ通知も実現できるデバイスとして、
RaspberryPi Zero WH を選定しました。
選定理由は以下の通り。
- ラズベリーパイって聞いたことある
- 初めての電子工作にちょうどよさそう
- HDMI出力がある
- wifiが使える
- 安い
- GPIOピンがはんだづけ済み
- Python, git, sshがOS標準で使える
(以下、RaspberryPi Zero WH
を ラズパイ
と呼びます)
ラズパイには GPIO(General Purpose Input/Output) という入出力ポートが備え付けられています。
任意の電気信号を入出力できるポートですね。
これを使えば、以下の流れで検知ができそうです。
- インターホンが鳴る
- 室内モニタのA接点が閉じる
- ラズパイの のGPIOに入力される = 検知できる
検知したらスマホに通知する方法の検討
RaspberryPi Zero WH は Raspberry Pi OS をインストールできます。(以前は Raspbian と呼ばれていたようです)
2024年4月現在、このOSはデフォルトで Python と wifi が使用できます。
つまりPythonスクリプトを用いたインターネット通信が可能ということです。
そしてPythonの RPi.GPIO
ライブラリを用いればGPIOの入出力を操作できます。
以下の流れでスマホに通知が実現できると考えました。
- PythonでGPIOへの入力を取得する
- LINE APIを叩いてLINEメッセージを送信する
- LINEの機能としてスマホに通知される
対策実施
ここまでの流れを整理します。
- インターホンが鳴る
- 室内モニタのA接点が閉じる
- ラズパイのGPIOに入力される = インターホンが鳴ったことを検知する
- PythonでGPIOの入力を取得する
- LINE APIを叩いてLINEメッセージを送信する
- LINEの機能としてスマホに通知される = 検知したらスマホに通知する
それでは実装をしていきましょう!
準備したもの
ラズパイ本体
ラズパイに接続するもの
-
KEYESTUDIO GPIO ブレークアウトキット
- 以下が入ってます。色々揃って便利でした
- GPIO拡張ボード
- ブレッドボード
- ジャンパ線
- LED
- 抵抗
- 他
- 以下が入ってます。色々揃って便利でした
- 電源供給用 ACアダプタ 5V 1A(スマホ充電用のやつ)
- 電源供給用 USB micro B ケーブル
- HDMI mini - HDMI ケーブル
- モニタ
- USB micro B → USB A 変換
- USBハブ
- キーボード(USBハブにつける)
- マウス(USBハブにつける)
- microSD(8GB以上)
その他もろもろ
- ジャンパー線 メス-メス 1ピン
- はんだごてセット
- テスター
- PC(microSDにOSを書き込むため・ラズパイにSSHするため)
インターホンが鳴ったことを検知する
インターホンが鳴ったことをラズパイに入力する
最初に考えた回路を実現していきます。
ラズパイのGPIOを使うと回路図はこんな感じ。
20240412追記:
GPIOの入力は各ピン最大16mAです。(全てのピンの合計は最大50mA)
この回路でGPIO17を入力モードにしていれば微小な電流しかながれませんが、
もし出力モードに設定してしまうと大きな電流が流れてしまう可能性があります。
その場合にもラズパイを保護するため、300Ω程度の抵抗を直列に接続した方が安全です。(この図にはその抵抗が書いてありません)
GPIO17を入力ポートとして使いました。他の GPIO**
でも問題ないです。
では実装していきましょう。
まずはモニタのA接点を取り出すためにモニタを外します。
上へ持ち上げると外れます。
GPIO拡張ボードとブレッドボードを繋いで仮実装です。
参考: GPIO拡張ボードとブレッドボードについて
GPIO拡張ボードとは、ラズパイ(に限らず)のGPIOピンを延長して扱いやすくしてくれるものです。ブレッドボードは、各種電子部品やジャンパ線をボードの穴に差し込むだけで、 はんだ付けをしなくても、手軽に電子回路を組むことの出来る基板です。
引用: http://www.ee.ibaraki.ac.jp/09student/Lectures/KisoDenki/BB/BB-intro.html
これでインターホンが鳴ったらラズパイのGPIO17に入力されるはずです。
ここからラズパイの設定と入力を検知するスクリプトを組み、入力を検知していきます。
ラズパイ設定
ざっくりと。
お手持ちのPCで以下の操作をします。
- microSDカードをお手持ちのPCに挿します
- RaspberryPi OSをダウンロードします https://www.raspberrypi.com/software/
- 展開し、インストール先をmicroSDにします
- 初期設定をします
- microSDを抜きます
ラズパイで以下の操作をします。
- OSをインストールしたmicroSDを挿す
- microUSBポートに電源供給(スマホ用のACアダプタがちょうどいいです)
- モニタを繋ぐ
- キーボードとマウスを繋ぐ(USBポートが1つしかないので、USBハブを使用)
ここまで、以下の記事がもっと詳しいのでぜひ参考にしてください。
(参考)OSインストール時にSSHを有効にする方法
microSDカードに書き込んだ後、ルートに ssh
という空のファイルを作成すると、
後述するSSHを有効にする設定が不要になります。
これをすると、モニタ・キーボード・マウスの接続が不要になります。
僕は勉強と経験も兼ねてGUI触ってみたかったのでやりませんでした。
すると、モニタにRaspberryPi OSの起動画面が表示されるはずです。(感動!)
続いて遠隔操作できるようにするためSSHを有効にします。
以下のコマンドで設定画面を開き、SSHを有効にしてください。
$ sudo raspi-config
次に、ラズパイのプライベートIPを確認します。
$ ifconfig
# 中略
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.158 netmask 255.255.255.0 broadcast 192.168.0.255
# 後略
これでssh接続できるようになりました!試してみましょう。
同じネットワーク上にあるお手持ちのPCで、SSHコマンドを実行します。
$ ssh {OSインストール時に設定したユーザ名}@{プライベートIP}
# パスワードを求められるので、OSインストール時に設定したパスワードを入力します。
Linux raspberrypi 6.1.21+ #1642 Mon Apr 3 17:19:14 BST 2023 armv6l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
これでSSH接続できました。
しかしパスワード認証はセキュリティ面から避けたいですよね。
鍵認証できるようにしましょう。
お手持ちのPCで鍵を生成し、ラズパイに公開鍵を送信します。
$ ssh-keygen -t ed25519 -C "{自分のメールアドレス}"
$ scp {作成した公開鍵} {ユーザ名}@{プライベートIP}:~/.ssh/
公開鍵をラズパイのAuthorized_keysに登録します。
$ cat {作成した公開鍵} >> authorized_keys
お手持ちのPCから、鍵でSSHできることを確認します。
$ ssh -i {秘密鍵のパス} {ユーザー名}@{プライベートIP}
Linux raspberrypi 6.1.21+ #1642 Mon Apr 3 17:19:14 BST 2023 armv6l
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
鍵認証できるようになったら、パスワード認証は無効にしましょう。
ラズパイの sshd_config
を編集します。
$ sudo vi /etc/ssh/sshd_config # ssh_configではないので注意!!!
- # PasswordAuthentication yes
+ PasswordAuthentication no
$ sudo systemctl restart sshd
パスワード認証が無効になってることを確認します。
$ ssh {ユーザ名}@{プライベートIP}
{ユーザ名}@{プライベートIP}: Permission denied (publickey).
これでラズパイのモニタ・キーボード・マウスは不要になりました。
次回から全てお手持ちのPCからSSH接続して操作しましょう。
入力を検知するスクリプト作成
お手持ちのPCで、検知するスクリプトを書きます。
書いたスクリプトはGitHubにpushし、ラズパイからpullすれば簡単に同期できます。
入力を検知するだけのコードは以下です
# Raspberry PiのGPIOピンを制御するためのライブラリをインポート
import RPi.GPIO as GPIO
# GPIOピンの番号を指定する方法を設定
# Raspberry Piのチップに基づいたピンの番号付け方式
GPIO.setmode(GPIO.BCM)
# 物理的な位置を指定する場合は↓
# GPIO.setmode(GPIO.BOARD)
# GPIO17ピンを入力モードに設定
input_pin = 17
GPIO.setup(input_pin, GPIO.IN)
while True:
# ピンに入力があれば 1 となる
print(GPIO.input(input_pin))
これをベースにし、ちょちょいのちょいっと
0.1秒ごとに判定します。
import time
import requests
import RPi.GPIO as GPIO
from dotenv import load_dotenv
import os
# Load environment variables
load_dotenv()
# Set up GPIO mode
GPIO.setmode(GPIO.BCM)
# Set up GPIO pin
input_pin = 17
GPIO.setup(input_pin, GPIO.IN)
# Main loop
def main():
while True:
time.sleep(0.1)
# Skip if no input received
if not GPIO.input(input_pin):
continue
print("Input received from GPIO pin {}".format(input_pin))
# このメソッドを実装すればOK
# send_message()
# Sleep for 30 seconds to avoid multiple notifications
time.sleep(30)
main()
試してみましょう。
まずコードをコピーして、以下のコマンドを実行してPythonスクリプトをラズパイ上に作成します。
$ pbpaste > notify.py
実行します。
$ python notify.py
この状態でインターホンを鳴らすと、標準出力されます!
# インターホンを押すと以下が出力される
Input received from GPIO pin 17
これでインターホンが鳴ったことを検知することができました!
次は、検知したらスマホに通知する を実現していきます。
検知したらスマホに通知する
通知するにはLINEを選定しました。
理由はこんなところ。
- 普段から使っている
- 家族も使いやすい(家族にも通知してあげたい)
- APIがある
具体的な通知方法は以下の通りです。
- LINE Developers に登録する
- LINEの公式アカウントを作成する
- そのアカウントを友だち登録する
- Messaging API を用いてpushメッセージを送信する
pushメッセージとは、任意のタイミングで即時送信するメッセージです。
順に設定していきましょう!
LINE Developers に登録する
↓から登録してください。
LINEの公式アカウントを作成する
LINE公式アカウント = LINE BOTです。
とりあえずこれで公式アカウント開設できました!!
QRコードを読み取って友だち登録しましょう。
あなたのユーザーID
と、 長期アクセストークン
は、pushメッセージを送るのに必要なので後で使います。
Messaging API を使いpushメッセージを送る
Pythonからpushメッセージを送る前に、curlで送信してみましょう。
公式ドキュメントを読みます。
ドキュメントによると、以下のリクエストでpushメッセージが送れるようです。
curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-H 'X-Line-Retry-Key: {UUID}' \
-d '{
"to": "U4af4980629...",
"messages":[
{
"type":"text",
"text":"Hello, world1"
},
{
"type":"text",
"text":"Hello, world2"
}
]
}'
必要最低限にします。
curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
"to": "{user id}",
"messages":[
{
"type":"text",
"text":"pushメッセージ送信テスト"
}
]
}'
{channel access token}
と {user id}
は、公式アカウント作成した時のwebページから取得したものを入力してください。
これを実行すると、公式アカウントとのLINEにメッセージが送信されます。
無事pushメッセージを受信することができました。
Pythonスクリプトでpushメッセージを送信する
curlでpushメッセージを送信できたので、pythonスクリプトに組み込みましょう。
今回は単純なのでLINE SDKを使いません。
push送信するメソッドをこんな感じに書いてみました。
# Send LINE message
def line_message():
url = "https://api.line.me/v2/bot/message/push"
access_token = os.getenv('ACCESS_TOKEN')
headers = {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json",
}
message = "インターホンがなりました"
# 環境変数のUSER_IDSでループする
user_ids = os.getenv('USER_IDS').split(',')
for user_id in user_ids:
payload = {
"to": user_id,
"messages": [
{
"type": "text",
"text": message,
}
]
}
response = requests.post(url, headers=headers, json=payload)
print("Response:", response.text)
retry_count = 0
# リトライ数が2回まで かつ ステータスコードが200系でない場合はリトライする
while retry_count < 3 and not (200 <= response.status_code < 300):
response = requests.post(url, headers=headers, json=payload)
print("Failed to send LINE message: " + response.status_code + ", " + response.text)
print("Retry count: " + str(retry_count))
retry_count += 1
if 200 <= response.status_code < 300:
print("Success to send LINE message")
else:
print("Failed to send LINE message: " + response.status_code + ", " + response.text)
あとは入力を検知するコードと組み合わせれば、この通り。
import time
import requests
import RPi.GPIO as GPIO
from dotenv import load_dotenv
import os
# Load environment variables
load_dotenv()
# Set up GPIO mode
GPIO.setmode(GPIO.BCM)
# Set up GPIO pin
input_pin = 17
GPIO.setup(input_pin, GPIO.IN)
# Main loop
def main():
sleep_time_sec = int(os.getenv('SLEEP_TIME_SEC', 30))
while True:
time.sleep(0.1)
# Skip if no input received
if not GPIO.input(input_pin):
continue
print("Input received from GPIO pin {}".format(input_pin))
# Send LINE message
line_message()
# Sleep to avoid multiple notifications
time.sleep(sleep_time_sec)
# Send LINE message
def line_message():
url = "https://api.line.me/v2/bot/message/push"
access_token = os.getenv('ACCESS_TOKEN')
headers = {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json",
}
# Loop through user_ids
user_ids = os.getenv('USER_IDS').split(',')
for user_id in user_ids:
payload = {
"to": user_id,
"messages": [
{
"type": "text",
"text": os.getenv('MESSAGE'),
}
]
}
response = requests.post(url, headers=headers, json=payload)
print("Response:", response.text)
retry_count = 0
# Retry 3 times if status code is not 200 series
while retry_count < 3 and not (200 <= response.status_code < 300):
response = requests.post(url, headers=headers, json=payload)
print("Failed to send LINE message: " + response.status_code + ", " + response.text)
print("Retry count: " + str(retry_count))
retry_count += 1
if 200 <= response.status_code < 300:
print("Success to send LINE message")
else:
print("Failed to send LINE message: " + response.status_code + ", " + response.text)
main()
環境変数として .env
にユーザID・アクセストークン・送信メッセージ・待機秒数を設定する必要があります。
詳しくは GitHub のREADMEを参照してください。
ラズパイにデプロイする
RaspberryPi OS はデフォルトでGitがインストールされてます。素敵。
cloneするためラズパイで鍵を作成し、GitHubに公開鍵を置いておきましょう。
鍵を作成
$ ssh-keygen -t ed25519 -C "{your email}"
秘密鍵と公開鍵が作成されるので、 末尾が pub
の公開鍵の中身をGitHubに登録してください。
参考:
無事GtiHubとSSHできるようになったら、cloneしましょう。
$ git clone git@github.com:gakisan8273/notify_interphone.git
READMEに従って .env
を生成して各環境変数を設定してください。
$ cp .env.example .env
# その後 .env の環境変数を設定してください
このPythonスクリプトは無限ループします。
とりあえず実行してみましょう。
$ python notify_interphone/notify.py
そしてインターホンを鳴らします。
ログが標準出力されます。
# インターホンを鳴らすと標準出力される
Input received from GPIO pin 17
Success to send LINE message
それに加え、LINEにも通知がきます。
これにて
- インターホンが鳴ったことを検知して
- スマホに通知する
が実現できました!!!!えらい!
でもちょっとだけ問題が残ってます。
ラズパイが再起動したときにスクリプトを自動実行する
ラズパイの電源が切れたとします。コンセントを引っこ抜いてしまったとかでね。
電源を入れ直せばラズパイの電源は入ります。
この状態でインターホンを鳴らすと...通知がきません。
Pythonスクリプトが実行されてないからです。
再起動のたびにSSHしてスクリプト実行して...は馬鹿らしいですよね。自動化しましょう。
ラズパイが起動したらスクリプトを自動実行する 設定をします。
$ crontab -e
+ @reboot /home/{user名}/notify_interphone python notify.py
cronの @reboot
は、起動時に1度だけ実行するコマンドを設定するものです。
これでスクリプトが自動実行できるようになりました!
きれいに収納する
これで機能としては インターホンが鳴ったらスマホに通知する
が実現できました。
だがしかし!現状はこのありさまです。
ひどいですね。
ブレッドボードが大きすぎて邪魔です。
見えてませんがラズパイの基板も剥き出しですし、導線もいつ外れるかわかりません。
見栄えよくしましょう。
GPIO拡張ボードを使って実現した回路を、拡張ボードなしで実現します。
GPIOのピンアサインを確認します。
引用: https://www.ekit-tech.com/?p=1069
今回使用した、3.3V / GPIO17 / GND のピンの位置を確認します。(GNDは複数箇所のどこでもいい)
こんな感じに GPIO17 - GND
間の抵抗をはんだづけしました。
他のGPIOピンははんだづけせずにソケットを使用しました。
コネクタではないため、ソケットが抜けにくくなるようピンに少しだけはんだを盛ってあります。(個人使用だからセーフ)
(間違えて電源供給のUSBコネクタ部も封じてしまったので、後で穴を開けました)
モニタのA接点からの導線が短かったので、先ほどと同様にソケットで繋ぎ延長します。
絶縁もしっかりと。
配線を隠すボックスがおうちにあるので、この中にラズパイを入れてしまいます。
効果の確認
インターホンが鳴るとスマホに通知される仕組みができました!
遅延は1秒程度です。十分実用的でしょう。
実際に2階で仕事をしていて、宅配を取りこぼすことがなくなりました。
これには業者もニッコリ。
歯止め
妻のLINEユーザIDを取得し、環境変数に追記しました。
妻もインターホンのスマホ通知できるようになりご満悦です。(個人の感想です)
この記事を書くことで標準化ができました。
また、スクリプトはGitHubに残してあります。
終わりに
初めての電子工作でした。
ものづくり、たのしいね!
ラズパイの扱いがわかったので、他にも何か作ってみようと思います!
いい感じならそれも記事にしますので、応援してくれると嬉しいです。
8歳息子と、手回し発電機でLED光らせてみました。
電気には向きがあるんじゃぞ。