
2026/01/04 0:46
**Show HN:** *自宅オフィス用の「通知停止」デバイスを作りました*
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
要約
著者は、MacBook のカメラがアクティブかどうかを表示する低コストの ESP32 ベースのデバイスを作成しました。
- 検出:Apple のシステムログ(
/AVCaptureSessionDidStartRunningNotification
)を、ネイティブ API が存在しないために Bun サーバー経由で監視します。AVCaptureSessionDidStopRunningNotification - ネットワークと電源:動的 IP を扱いポーリングを減らすために mDNS でローカル命名を追加し、HTTP ポーリングから Bluetooth Low Energy(BLE)へ切り替えてリアルタイム更新を実現しました。ユニットは外部モニタの USB ポート経由で電源供給されるので、ノートパソコンが抜かれると自動的にオフになります。
- プロトコル:カスタムバイナリ「DoorFrame Protocol v1」が単一バイト(例:0xC0/0xC1 でカメラ状態)と時間オフセットを送信し、JSON のような肥大化したペイロードを回避します。著者は RFC スタイルでプロトコルを文書化しました。
- UI とエンクロージャ:Mac メニューバーアイコンは Apple の SF Symbols に触発され、接続中にアニメーション波形インジケータが表示されます。物理ケースは Fusion 360 で設計し、スナップフィットと許容差を考慮した 3D プリント(初期版では透明テープを追加)で製造しました。
- 進化と今後の作業:単なる通知から「過剰設計」プロジェクトへ発展し、現在は BLE 信号強度による部屋検知も含まれています。著者は既存プロトコルに顔認識、サイレン、その他スマートホーム連携を追加する予定です。
このデバイスはノートパソコンのカメラに対して目立たないプライバシーアラートを提供し、メーカー向けツールで低コストにハードウェアレベルのカメラ制御を実現できることを示しています。
本文
私は両親と一緒に住み、在宅勤務をしています
自作のオフィスルームがあるものの、母は「入っていい?」と頻繁に尋ねてきます。そこで私は「忙しい」「会議終了時刻」などを知らせる専用デバイスを作り上げました。この装置は、ユーザー1名だけが利用する独自バイナリプロトコルで動作します。
完成した機器は小さな植物の隣に置かれています
カレンダーを共有すればいいのでは?
- 電話の問題 – 母は携帯を持ち歩かず、枕の奥にしまいがちです。
- サプライズ会議 – カレンダーに載っていない「クイック・シンク」Slackのハドルや45分間のデバッグセッションなどがあります。
- 人為的要因 – たまに部屋に入る必要があるので、「私はいるけれど見えません」と伝える必要があります。
- 情報過多 – 赤いライトだけでなく、会議終了時刻や残り時間、さらにはNVIDIA株価(最後の要素は不要かもしれない)を知りたいです。
ESP32と退屈の芸術
以前のプロジェクトで使わなくなったESP32を手に入れました。アイデアはシンプル:MacBookのカメラがONかOFFかで画面にステータスを表示するだけです。
Apple の過保護姿勢
カメラ状態を直接クエリできるネイティブAPIはありませんでした。そこで
AVCaptureSessionDidStartRunningNotification(カメラON)と AVCaptureSessionDidStopRunningNotification(カメラOFF)のシステムログを監視する手法にハッキングしました。
const CAMERA_ON_EVENT = "AVCaptureSessionDidStartRunningNotification"; const CAMERA_OFF_EVENT = "AVCaptureSessionDidStopRunningNotification"; const COMMAND = `log stream --predicate '(eventMessage CONTAINS "${CAMERA_ON_EVENT}" || eventMessage CONTAINS "${CAMERA_OFF_EVENT}")'`; export const createAppleCameraStatus = () => { let childProcess: Bun.Subprocess | null = null; let cameraStatus: "running" | "stopped" | "unknown" = "unknown"; return { start: async () => { childProcess = Bun.spawn({ cmd: ["/bin/sh", "-c", COMMAND], stdout: "pipe", stderr: "pipe", }); for await (const line of childProcess.stdout) { const message = new TextDecoder().decode(line); if (!message.includes("com.apple.cameracapture")) continue; if (message.includes(CAMERA_ON_EVENT)) cameraStatus = "running"; else if (message.includes(CAMERA_OFF_EVENT)) cameraStatus = "stopped"; } }, }; };
Bun のサーバーを立ち上げ、ESP32 をノートPCのIPに接続し、1.8インチ OLED ディスプレイを取り付けました。すべて順調に動作しました。
より進化させる
二つの問題が浮上しました:
- 動的 IP – ルーターはアドレスを変更するので、毎回デバイスを再構築しなければならない。
- ポーリング疲労 – 500 ms ごとに HTTP ポーリングすると電力が無駄になり、遅延も発生。
解決策
- mDNS を学び、
がhttp://192.168.0.5:1337
に変わりました。http://Apoorvs-MacBook-Pro.local:1337 - HTTP から BLE に切り替え、Mac が即座に更新をプッシュできるようにしました(理論上は信号強度で在席確認も可能です)。
ハードウェア設計
プロフェッショナルな外観が欲しかったので、3D プリントへ踏み切りました。Fusion 360 を数週間学び、デジタルキャリパーで測定し、スナップフィットやカンチレバーを試行錯誤した結果、ESP32 と OLED を安全に保持できるケースを印刷しました(バージョン1ではテープで補正が必要でした)。
過剰設計
- BLE – ポーリング不要。
- カスタムトレイアイコン – Apple の SF Symbols からインスパイアし、接続中は波形アニメーションを追加。
- カスタムバイトプロトコル – JSON より軽量な単一バイトメッセージ:
ハンドシェイク (Host → Peripheral) 0xDF 0x01 // DF マジックバイト、バージョン1 コマンド カメラステータス: 0xC0 (OFF), 0xC1 (ON) 時間交換: バイトに詰め込み 時間バイト (0x7H): 例)0x79 = 午前/午後9時 分数バイト (0x8M): 例)0x86 = XX:30(5分刻み)
プロトコルの完全なドキュメントはオンラインで公開しています。
終わりに
とても楽しく、たくさん学びました。現在、このデバイスはドア枠に設置され、外部モニターの USB ポートから電源を供給し、ノートPCが抜かれれば自動でオフになります。今後もフェイシャル認証やサイレンなどさらなる過剰設計アイデアは歓迎ですが、今回はここまでにします。