The Home Automation Bridge
Doorbell, Grafana, Pi-camera — all into MeshHold.
Bridge GPIO sensors, alerting platforms, and cheap IP cameras into your private mesh — without exposing any of them to the public internet.
Pi or Pi-class hardware? The
.debpackage is the path of least friction on Raspberry Pi OS (Bookworm and later). If you'd rather keep your Pi's host clean and run everything from containers, themeshhold:latest-arm64Docker image is the same daemon and lets you share the SD card root with Home Assistant and friends without rpm/deb conflicts. The auto-answer +call_mediaconfig below works in either deployment — only the install command differs.
Inbound webhooks
POST/GET /api/v1/hooks/<token> — endpoints in the shape of Slack /
Mattermost / Discord. Configured in config.yaml only — there is no
CRUD UI; rotation = edit YAML + restart.
call action — fire a video call
webhooks:
- name: "doorbell"
token: "long-random-token-1"
action: "call"
target_node_id: "12D3KooW..."
video: true
Wire a GPIO button to curl /api/v1/hooks/long-random-token-1 and the
doorbell calls your phone.
message action — post to a chat room
- name: "grafana-alerts"
token: "long-random-token-2"
action: "message"
room_id: "vault-uuid-2"
fallback_text: "Alert (no payload)"
Parses Slack-shape JSON, payload=<json>, and text/plain bodies.
Headless Pi-camera (auto-answer)
The same daemon runs in call_media mode on a Raspberry Pi:
node:
call_media:
enabled: true
video_devices:
- "/dev/video0"
- "/dev/video2"
audio_device: "default"
# video_input_format defaults to "mjpeg" — leave it unless your
# camera is raw-only (see the capture-format note below).
Capture format (why HD video may not appear). The daemon captures with ffmpeg's
-input_format mjpegby default. Almost every USB UVC webcam can only deliver 720p/1080p at 30 fps as Motion-JPEG; raw YUYV at those sizes exceeds USB 2.0 bandwidth, so the camera caps at ~10 fps and ffmpeg's default raw capture stalls or throwsInput/output error— the call connects with audio only and no video. Check what your camera actually supports withv4l2-ctl --list-formats-ext. If it exposes only a raw format (noMJPG), setvideo_input_format: "yuyv"(or the listed fourcc); use"auto"to let ffmpeg choose. When video capture fails mid-call the daemon now logsheadless: video capture ended unexpectedlyat warn level instead of silently dropping video.
Issue an mgmt-key with the camera cap; the holder can place video
calls and the Pi auto-answers. Remote camera switching is supported
(NEXT / PREV / SPECIFIC requests). Digital zoom + pan over a call
applies to browser-based endpoints — the headless ffmpeg pipeline here
streams the full frame and does not crop, so zoom requests to this Pi
are a no-op.
Device permissions.
call_mediashells out toffmpeg, which opens/dev/video*and the ALSA devices directly. On a.deb/.rpminstall the daemon runs as the unprivilegedmeshholdsystem user, which must belong to thevideo(camera) andaudio(microphone) groups — those device nodes areroot:video/root:audio, mode0660. Packages from v0.7.179 onward add this membership automatically in the post-install hook. On an older install, or when you run the daemon under a custom user, grant it yourself and restart so the new groups take effect (membership is only read at process start):
sh sudo usermod -aG video,audio meshhold sudo systemctl restart meshholdVerify the daemon user can actually open the camera before debugging further up the stack — this is the most common reason a call connects but carries no remote video:
sh sudo -u meshhold ffmpeg -hide_banner -loglevel error \ -f v4l2 -i /dev/video0 -t 1 -f null - && echo "camera OK"
ffmpegand alibvpx-vp9encoder must also be present (ffmpeg -encoders | grep vp9); install the distro'sffmpegpackage if missing.
Pi as a "baby monitor"
Same setup as above with a single static video_devices entry and a
mgmt-key shared with the parents' phones.