#!/usr/bin/env python3
"""
Doorbell PTT server for Pi Zero W.
Serves a single-page web app that:
* shows the Frigate WebRTC live feed (video + camera mic if present)
* provides a push-to-talk button that streams phone mic audio over a
WebSocket; this script decodes and plays it out ALSA.
Deployment:
* listens on 127.0.0.1:5555; expose publicly via Caddy reverse proxy
* runs under systemd as the 'pi' user
* requires: python3-flask, flask-sock, ffmpeg, alsa-utils
"""
import subprocess
from flask import Flask, render_template_string
from flask_sock import Sock
app = Flask(__name__)
sock = Sock(app)
# Cameras to offer in the page's dropdown. First entry is the default.
# Names must match go2rtc stream names in frigate_config/config.yml and
# the corresponding camera must be enabled in Frigate.
CAMERAS = ["front_door"]
PAGE = """
Doorbell
"""
@app.route('/')
def index():
return render_template_string(PAGE, cameras=CAMERAS)
@app.route('/healthz')
def healthz():
return 'ok'
@sock.route('/audio')
def audio(ws):
ff = subprocess.Popen(
[
'ffmpeg',
'-loglevel', 'error',
'-f', 'webm', '-i', 'pipe:0',
'-f', 'alsa', 'default',
],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
while True:
data = ws.receive()
if data is None:
break
if isinstance(data, (bytes, bytearray)):
try:
ff.stdin.write(data)
ff.stdin.flush()
except BrokenPipeError:
break
finally:
try:
ff.stdin.close()
except Exception:
pass
try:
ff.terminate()
ff.wait(timeout=2)
except Exception:
ff.kill()
if __name__ == '__main__':
# 127.0.0.1 only -- Caddy reverse-proxies from the public domain
app.run(host='127.0.0.1', port=5555, threaded=True)