1.開発の動機
妻から声をかけられたが、ヘッドホンで大音量で音(推しのASMR配信)を聞いていたので応答できず怒られた。
殴る、蹴る、棒でつつく、等の方法を提案されたが、痛いのは嫌なので細工で乗り切ることにした。
具体的にはChatGPTでスクリプトを書かせてそれを配線する作業となる。簡単。
2.用意したもの
- ハードウェア
- RaspberryPi PICO W
- プッシュスイッチ(モーメンタリ)
- 100均のUSB-MicroBケーブル
- ビニル被覆ワイヤー 少量
- ブレッドボード、QIコネクタと圧着工具
- ソフトウェア環境等
- Windows11
- MicroPython
- Thonny
- 利用するもの
- ChatGPT 5.2 Thinking
充分な検証をせずマネしないでください
また、詳しいことを訊かれても筆者は知識が無いのでお答えできません
ASMR配信を聴く場合は背後等に注意しましょう
3.やりたいこと
(1)PICO Wの任意のGPIOをGNDに落とす(ONにする)
(2)デスクトップPCに通知が飛び、チャイムが鳴る
(3)怒られずに済む ←重要
3-1.ChatGPTに投げたプロンプト
今回あなたは組込製品のソフトウェアの専門家です。下記のような要望があり、指定されたハードウェア上で動作するスクリプトをPythonで記述してください。
1) やりたいこと ボタンを押すと指定されたPCのWindowsに通知が出て音が鳴る。 WEB会議や動画視聴の際、棒で背中をつつくとかしなくて良くなる。
2) ハードウェア
・ボタン側 RaspberryPi PICO W
・表示する側 Windows11が動作しているPC
3) 使用言語 Python
4)詳細仕様 ・ボタンを押す→Windows11のPCでアラート音が鳴る(ウェブ会議の相手に聞こえるのは問題ないものとする)
・ネットワーク環境は、PICO WからネットワークへはWi-Fiを使用し、PCはLANケーブルでネットワークに接続しており、自分の所属するネットワーク内だけにメッセージが届けば良いものとする。
・通知音の音源は当方で用意するので、スクリプト内で設定できるように変数として構成し、初期値はDummy.wavとする
・PICO Wに設定するAP情報とパスワードは、当方で入力するので、スクリプト内で設定できるように変数として構成し、AP名の初期値はAP12345678とし、パスワードはABCD1234とする
5)環境説明
・LANネットワークアドレス周り 192.168.0.0/24 DGW 192.168.0.254 DNS 192.168.0.7
6) 不明な点については事前に確認をお願いします。では、よろしくお願いします。
3-2.作成されたプロンプト PICO W側
※途中でLEDの表示・点滅機能を利用しようと思い立ったので追加改修しています
# main.py (Raspberry Pi Pico W / MicroPython)
# ボタン押下で LAN内へ「急用」通知要求をUDP送信する
# 改修:
# 1) 内蔵LED: 待機中は常時点灯、通報時は5秒間点滅
# 2) 連続動作防止: 1回押した後はCOOLDOWN_SECの間は無効化
import time
import socket
import network
from machine import Pin
# ===== ユーザー設定(要望の初期値) =====
WIFI_SSID = "AP12345678"
WIFI_PASSWORD = "ABCD1234"
# 送信先:まずはブロードキャスト推奨(LAN内のWindowsにだけ届く)
DEST_IP = "192.168.0.255"
DEST_PORT = 5005
# 簡易認証(同じLANの別端末が誤送信しないようにしたい場合)
SHARED_KEY = "CHANGE_ME" # 使わないなら空文字 "" にしてOK
# ===== 連続動作防止(改修: 変更可能な変数) =====
COOLDOWN_SEC = 5 # 1回押した後に作動しない時間(秒)
# ===== LED制御(改修) =====
ALERT_BLINK_SEC = 5.0 # 通報時に点滅する時間(秒)
BLINK_PERIOD_MS = 200 # 点滅周期(ms) 200ms -> 約2.5Hz
# ボタン
BUTTON_PIN = 15
DEBOUNCE_MS = 80
# ===== Wi-Fi接続 =====
def wifi_connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
return wlan
print("Connecting Wi-Fi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
t0 = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), t0) > 20000:
raise RuntimeError("Wi-Fi connect timeout")
time.sleep_ms(200)
print("Wi-Fi connected:", wlan.ifconfig())
return wlan
# ===== UDP送信 =====
def send_urgent(sock):
# メッセージ形式: key|text
# textは固定「急用」
msg = "{}|{}".format(SHARED_KEY, "急用")
sock.sendto(msg.encode("utf-8"), (DEST_IP, DEST_PORT))
# ===== LED点滅(改修) =====
def blink_led_for(led: Pin, seconds: float, period_ms: int):
end_ms = time.ticks_add(time.ticks_ms(), int(seconds * 1000))
while time.ticks_diff(end_ms, time.ticks_ms()) > 0:
led.toggle()
time.sleep_ms(period_ms)
# 終了後は待機状態(常時点灯)へ戻す
led.value(1)
def main():
wifi_connect()
# 内蔵LED(Pico Wは "LED" で参照可能)
led = Pin("LED", Pin.OUT)
led.value(1) # 待機中:常時点灯(改修)
# UDPソケット(ブロードキャスト送信用)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)
last_state = button.value()
cooldown_until_ms = time.ticks_ms() # 次に作動可能になる時刻
print("Ready. Press button to send '急用'.")
while True:
state = button.value()
# 作動禁止期間中は入力を無視(改修)
now = time.ticks_ms()
if time.ticks_diff(cooldown_until_ms, now) > 0:
# 待機中LEDは常時点灯
led.value(1)
last_state = state
time.sleep_ms(10)
continue
# 押下検出:HIGH→LOW
if last_state == 1 and state == 0:
# デバウンス
time.sleep_ms(DEBOUNCE_MS)
if button.value() == 0:
try:
# 送信(UDPなので取りこぼし対策で2回)
send_urgent(sock)
time.sleep_ms(50)
send_urgent(sock)
print("Sent: 急用")
# 通報時:LEDをALERT_BLINK_SEC秒間点滅(改修)
blink_led_for(led, ALERT_BLINK_SEC, BLINK_PERIOD_MS)
# 連続動作防止:COOLDOWN_SEC秒間無効化(改修)
cooldown_until_ms = time.ticks_add(time.ticks_ms(), int(COOLDOWN_SEC * 1000))
except Exception as e:
print("Send error:", e)
# エラー時も見た目は待機(常時点灯)
led.value(1)
# ボタンが離されるまで待つ(チャタリング/連打対策)
while button.value() == 0:
time.sleep_ms(10)
last_state = state
time.sleep_ms(10)
if __name__ == "__main__":
main()
3-2.Windows11側の準備
(1)フォルダの準備
C:\pico_urgent
(2)venvの準備
PowerShellを起動し、下記のコマンドを実行して実行フォルダ内にvenv環境を作る
python -m venv C:\pico_urgent\venv
(3)venv環境の確認
Test-Path C:\pico_urgent\venv\Scripts\python.exe
Test-Path C:\pico_urgent\venv\Scripts\pythonw.exe
両方ともTrueになればOK
(4)venv側のpipを更新する
C:\pico_urgent\venv\Scripts\python.exe -m pip install --upgrade pip
(5)スクリプトファイルを格納する
receiver_sound_only.py
(6)スクリプトをChatGPTからコピペ
# receiver_sound_only.py (Windows 11 / Python)
# Pico W からの UDP を受けて、指定WAVを鳴らす(通知は出さない)
# 目的: 「音だけ出ればOK」
import os
import socket
import threading
import time
# ===== ユーザー設定 =====
LISTEN_IP = "0.0.0.0"
LISTEN_PORT = 5005
SOUND_WAV = "Dummy.wav" # 初期値: Dummy.wav(スクリプトと同じフォルダに置くのが確実)
# Pico 側と合わせる(使わないなら "" でOK)
SHARED_KEY = "CHANGE_ME"
# 連続受信時の暴発防止(秒): 0 にすると無制限
MIN_INTERVAL_SEC = 0.5
def resolve_wav_path(path: str) -> str:
if os.path.isabs(path):
return path
here = os.path.dirname(os.path.abspath(__file__))
return os.path.join(here, path)
def play_wav(path: str) -> None:
# WAV再生: winsound は標準ライブラリ
import winsound
if os.path.exists(path):
# 非同期再生(鳴らしながら受信待ち可能)
winsound.PlaySound(path, winsound.SND_FILENAME | winsound.SND_ASYNC)
else:
# ファイルが見つからない場合はビープ
winsound.MessageBeep()
_last_play_ts = 0.0
_lock = threading.Lock()
def handle_message(data: bytes, addr) -> None:
global _last_play_ts
text = data.decode("utf-8", errors="ignore").strip()
# 形式: key|急用
if "|" in text:
key, msg = text.split("|", 1)
else:
key, msg = "", text
if SHARED_KEY and key != SHARED_KEY:
return
if msg.strip() != "急用":
return
# 連続再生抑止
now = time.time()
with _lock:
if MIN_INTERVAL_SEC > 0 and (now - _last_play_ts) < MIN_INTERVAL_SEC:
return
_last_play_ts = now
wav = resolve_wav_path(SOUND_WAV)
play_wav(wav)
print("Played WAV for '急用' from", addr)
def main():
wav = resolve_wav_path(SOUND_WAV)
print("=== Pico Urgent Receiver (sound only) ===")
print("Listen:", LISTEN_IP, LISTEN_PORT)
print("WAV:", wav)
print("MIN_INTERVAL_SEC:", MIN_INTERVAL_SEC)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((LISTEN_IP, LISTEN_PORT))
while True:
data, addr = sock.recvfrom(2048)
# 念のため別スレッドで処理
threading.Thread(target=handle_message, args=(data, addr), daemon=True).start()
if __name__ == "__main__":
main()
4.タスクスケジューラに登録
4-1.
タスク スケジューラ ライブラリ → タスクの作成
4-2.
「全般」タブ → 名前(わかりやすければ何でも良い。英数字推奨)、「最上位の特権で実行する」をON
4-3.
「トリガー」タブ → 新規 → 「タスクの開始」を「ログオン時」に設定
4-4.
ここがキモなので注意してください
「操作」タブ
プログラム/スクリプト → C:\pico_urgent\venv\Scripts\pythonw.exe
引数の追加 → "C:\pico_urgent\receiver_sound_only.py"
開始 → c:\pico_urgent
4-5.
「設定」タブ
「タスクが既に実行中の場合に適用される規則」を「新しいインスタンスを開始しない」に設定
4-6.
登録されているのが確認できたら、いったんサインアウトして再ログオン
5.RaspberryPi PICO Wの組立
ブレッドボードで組立てました。
ケースにしまいたかったので100均で適当なUSBのケーブルを買ってきてB側を剥いて+5Vが来ているところを探します。探せたらケースにケーブルとちょうどいいサイズの穴を開けて通しましょう。
5-1.電源
+5V → GP40
GND → GNDならどこでも良い。(自分は38を使った)
5-2.プッシュスイッチ
検出側 → GP15
反対側 → GNDならどこでも良い
6.試験
Youtubeでも流しながらプッシュスイッチを押す
LEDの動作
点灯→点滅
PCの動作
指定したチャイム(Dummy.wav)が鳴る
最後に
ASMRは背後に気をつけましょう。
よいヘッドホンライフを!