-
Notifications
You must be signed in to change notification settings - Fork 13
Home Assistant
By default a MiniSpeedCam uploads every pass to your account at minispeedcam.com. Instead, you can point a device at a local Home Assistant webhook, so each capture — speed, photo, and the per-pass telemetry — arrives in HA as an automation trigger you can turn into sensors, dashboards, notifications, and automations.
This works because the device's data destination is user-editable. The
Wifi Settings → Data Server field lets you replace the cloud URL
with any endpoint, and the firmware automatically uses plain HTTP (not HTTPS) when
the URL starts with http://, which is exactly what a LAN Home Assistant speaks.
⚠️ Re-pointing the Data Server replaces the cloud, it doesn't add to it. A device sends to one destination. While it's pointed at Home Assistant, your captures go to HA only — they will not appear on minispeedcam.com. To go back, clear the field (see Reverting to the cloud).
- Firmware with the Data Server field. Open the portal → Wifi Settings tab. If you don't see an API Base URL box, update first — see Firmware Update.
- The device and Home Assistant on the same local network. The webhook is reached over plain HTTP on your LAN; it is not encrypted, so keep it to a trusted home network.
-
Home Assistant reachable at a stable address — ideally a fixed/reserved IP for
your HA server (a DHCP lease change would break the URL). The default HA port is
8123.
The device builds its upload URL as <API Base URL> + /capture. So if you set the
base to your HA webhook root, the capture POST lands on a webhook whose ID is capture:
API Base URL: http://192.168.1.50:8123/api/webhook
capture POST → http://192.168.1.50:8123/api/webhook/capture
You create a Home Assistant webhook automation (or a webhook-triggered template
sensor) listening on webhook_id: capture, and HA receives the JSON body of every pass.
The device also sends two housekeeping POSTs to the same base —
…/register_deviceat boot and…/cameraas a periodic health heartbeat. With only thecapturewebhook defined, HA logs a harmless "Received message for unregistered webhook" for those. You can ignore them, or capture the heartbeat too — see Optional: device health.
You can do this entirely in YAML (paste into configuration.yaml or a package). Choose
one of the two patterns below — both register the same capture webhook, so you
can't use both at once.
🔑 The webhook ID must be exactly
capture— that's the suffix the firmware appends. You choose the rest of the URL (host/port/path) via the Data Server field, but not this last segment.
A trigger-based template sensor that turns each pass into a sensor with attributes.
No photo. Paste into configuration.yaml and restart HA:
template:
- trigger:
- platform: webhook
webhook_id: capture
allowed_methods: [POST]
local_only: true # only accept from the local network
sensor:
- name: "MiniSpeedCam Last Speed"
unique_id: minispeedcam_last_speed
state: "{{ trigger.json.speed_actual }}"
unit_of_measurement: "mph" # change to "km/h" if the device is set to KPH
state_class: measurement
attributes:
direction: >-
{{ {'0':'unknown','1':'approaching','2':'receding'}[trigger.json.direction] }}
peak_snr: "{{ trigger.json.peak_snr }}"
mean_speed: "{{ trigger.json.mean_speed }}"
frame_count: "{{ trigger.json.frame_count }}"
duration_ms: "{{ trigger.json.duration_ms }}"
has_photo: "{{ trigger.json.send_photo }}"
captured_at: "{{ now().isoformat() }}"sensor.minispeedcam_last_speed now holds the most recent speed, and its attributes carry
the rest of the pass data. Good enough if you just want the numbers in HA.
Since a webhook can have only one owner, do everything from a single automation:
save the JPEG with a shell_command, then re-broadcast the data as an event so a template
sensor can read it. This replaces Pattern A.
1. Decode helper — add a shell_command that base64-decodes the photo to a file:
shell_command:
minispeedcam_save_photo: >-
mkdir -p /config/www/speedcam &&
printf '%s' '{{ photo_b64 }}' | base64 -d > '/config/www/speedcam/{{ stamp }}.jpg' &&
cp '/config/www/speedcam/{{ stamp }}.jpg' /config/www/speedcam/latest.jpg2. Webhook automation — owns the capture webhook; saves the photo and fires an event:
automation:
- alias: "MiniSpeedCam capture"
trigger:
- platform: webhook
webhook_id: capture
allowed_methods: [POST]
local_only: true
action:
# Only passes that exceeded Photo Speed carry an image (send_photo == "true").
- choose:
- conditions: "{{ trigger.json.send_photo == 'true' }}"
sequence:
- service: shell_command.minispeedcam_save_photo
data:
photo_b64: "{{ trigger.json.photo.contents }}"
stamp: "{{ now().strftime('%Y%m%d-%H%M%S') }}"
# Re-broadcast the pass so the template sensor below can pick it up.
- event: minispeedcam_capture
event_data:
speed: "{{ trigger.json.speed_actual }}"
direction: "{{ trigger.json.direction }}"
peak_snr: "{{ trigger.json.peak_snr }}"
mean_speed: "{{ trigger.json.mean_speed }}"
frame_count: "{{ trigger.json.frame_count }}"
duration_ms: "{{ trigger.json.duration_ms }}"
has_photo: "{{ trigger.json.send_photo }}"3. Speed sensor — a template sensor driven by that event (no webhook conflict):
template:
- trigger:
- platform: event
event_type: minispeedcam_capture
sensor:
- name: "MiniSpeedCam Last Speed"
unique_id: minispeedcam_last_speed
state: "{{ trigger.event.data.speed }}"
unit_of_measurement: "mph" # change to "km/h" if the device is set to KPH
state_class: measurement
attributes:
direction: >-
{{ {'0':'unknown','1':'approaching','2':'receding'}[trigger.event.data.direction] }}
peak_snr: "{{ trigger.event.data.peak_snr }}"
mean_speed: "{{ trigger.event.data.mean_speed }}"
frame_count: "{{ trigger.event.data.frame_count }}"
duration_ms: "{{ trigger.event.data.duration_ms }}"
has_photo: "{{ trigger.event.data.has_photo }}"
captured_at: "{{ now().isoformat() }}"4. Show the latest photo — a local_file camera pointed at the file the helper writes:
camera:
- platform: local_file
name: "MiniSpeedCam Latest"
file_path: /config/www/speedcam/latest.jpgEach pass overwrites latest.jpg (live view) and keeps a timestamped copy in
/config/www/speedcam/ for history. Because they're under www/, you can also open any
of them in a browser at http://<ha>:8123/local/speedcam/latest.jpg.
📸 Photo notes.
base64andprintfare present on Home Assistant OS / Container installs; a stripped-down Core/venv install may need GNU coreutils. If the camera entity errors with a path warning, add the folder tohomeassistant: allowlist_external_dirs: ["/config/www/speedcam"]. Only passes whose top speed reached Photo Speed include an image; slower passes are speed-only.
-
Open the device portal and go to the Wifi Settings tab.
-
In API Base URL (blank = default), enter your HA webhook root — host and port, no trailing
/capture:http://192.168.1.50:8123/api/webhook(Use your HA server's actual IP and port.)
-
Click Save Settings. The device reboots and starts sending captures to HA.
The device prints the active base on its USB serial console at boot (
[API] base URL: http://…), which is a quick way to confirm what it's using.
- Trigger a pass (walk a vehicle — or yourself fast enough — past the unit).
- In Home Assistant:
-
Pattern A: open Developer Tools → States and check
sensor.minispeedcam_last_speed. -
Pattern B: the same sensor updates, and
camera.minispeedcam_latestshows the photo (for passes over Photo Speed).
-
Pattern A: open Developer Tools → States and check
- Still nothing? See Troubleshooting.
Every capture POST is JSON (Content-Type: application/json). Fields available as
trigger.json.<field>:
| Field | Meaning |
|---|---|
speed_actual |
Top speed for the pass, in the device's units (MPH or KPH per the MPH/KPH setting). |
send_photo |
"true" if a photo is attached, "false" for a speed-only pass. |
photo.filename |
image.jpg (present only when send_photo is "true"). |
photo.contents |
The JPEG, base64-encoded (present only when send_photo is "true"). |
direction |
0 = unknown, 1 = approaching (front plate), 2 = receding (rear plate). |
mag_trend |
Echo-energy centroid 0–100 (50 = symmetric pass; 255 = too few echoes). |
peak_mag |
Strongest radar echo magnitude in the pass (closest approach). |
peak_snr |
Strongest detection SNR (decimal) — detection confidence. |
mean_speed |
Mean of the valid speed samples (decimal) — steady vs. braking shape. |
frame_count |
Number of valid radar samples that made up the pass. |
duration_ms |
Wall-clock length of the pass, in milliseconds. |
device_token |
The unit's identity secret. Useful as a per-device key if you point several units at one HA. |
💡 Multiple devices → one HA. They all POST to the same
capturewebhook. Branch ontrigger.json.device_token(or give each unit its own HA via separate base URLs) to tell them apart.
The periodic …/camera heartbeat carries a device-health snapshot. To capture it, add a
second webhook with ID camera (same pattern as above). Its fields:
| Field | Meaning |
|---|---|
rssi |
WiFi signal strength (dBm). |
free_heap / min_free_heap
|
Current and lifetime-low free heap (bytes). |
uptime_s |
Seconds since boot. |
boot_count / reset_reason
|
Reboot count and last reset cause. |
esp_fw / stm_fw
|
Installed ESP32 and STM32 firmware versions. |
reject_speed / reject_proximity
|
Lifetime counts of discarded readings (corrupt replies / distant traffic). |
ip_address |
The device's current LAN IP. |
These make handy diagnostic sensors (signal, uptime, firmware version on a dashboard).
To send captures back to minispeedcam.com:
- Portal → Wifi Settings → API Base URL.
- Clear the field (leave it blank) and click Save Settings.
A blank value restores the built-in default server on reboot. (You can also type a full URL to point at a specific environment.)
| Symptom | Likely cause / fix |
|---|---|
| No webhook ever fires | Confirm the device and HA are on the same LAN, and the base URL is exactly http://<ha-ip>:8123/api/webhook with no /capture and no trailing slash. Check the device serial log line [API] base URL: …. |
HA log: "Received message for unregistered webhook" for register_device / camera
|
Normal — the device also sends boot-registration and heartbeat POSTs. Ignore them, or define those webhooks (see device health). |
| Connection refused / nothing arrives, but URL looks right | The device only uses plain HTTP when the base starts with http://. An https:// base triggers a TLS handshake your local HA won't answer. For local use, keep it http://. |
| Speed value looks doubled/halved | The sensor's unit_of_measurement doesn't match the device's MPH/KPH setting. The number is whatever the device measures; set the unit to match (see Device Settings → MPH/KPH). |
direction is unknown a lot |
Direction needs enough echoes / proximity tuning. It's best with the proximity thresholds set — see Device Settings → Radar signal (proximity). |
| Photo never saves (Pattern B) | Check base64/printf exist in your HA environment, that /config/www/speedcam is writable, and add it to allowlist_external_dirs if the camera entity warns about the path. Remember only passes over Photo Speed include an image. |
| Captures stopped appearing on minispeedcam.com | Expected — a device sends to one destination. Revert to the cloud to restore cloud uploads. |
| Portal has no API Base URL field | Your firmware predates this feature. Update via Firmware Update. |