mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 19:06:34 +00:00
feat: add shell kiosk prototype for end-to-end testing
This commit is contained in:
parent
94e316a207
commit
44e4b7f3af
1 changed files with 249 additions and 0 deletions
249
kiosk/prototype.sh
Normal file
249
kiosk/prototype.sh
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# BetterFrame kiosk prototype — pairs with server, pulls bundle, displays cameras.
|
||||||
|
# Requires: curl, jq, gst-launch-1.0
|
||||||
|
#
|
||||||
|
# Usage: ./prototype.sh [server-url]
|
||||||
|
# If no URL given, auto-discovers via localhost → betterframe.local → frame.betterportal.cloud
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE_DIR="${HOME}/.betterframe-kiosk"
|
||||||
|
KEY_FILE="${STATE_DIR}/kiosk.key"
|
||||||
|
SERVER_FILE="${STATE_DIR}/server.url"
|
||||||
|
BUNDLE_FILE="${STATE_DIR}/bundle.json"
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
|
||||||
|
# ---- Discovery ---------------------------------------------------------------
|
||||||
|
|
||||||
|
discover_server() {
|
||||||
|
if [[ -n "${1:-}" ]]; then
|
||||||
|
echo "$1"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Saved from previous run
|
||||||
|
if [[ -f "$SERVER_FILE" ]]; then
|
||||||
|
local saved
|
||||||
|
saved=$(cat "$SERVER_FILE")
|
||||||
|
if curl -sf "${saved}/healthz" >/dev/null 2>&1; then
|
||||||
|
echo "$saved"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local candidates=(
|
||||||
|
"http://localhost:18081"
|
||||||
|
"http://betterframe.local:18081"
|
||||||
|
"https://frame.betterportal.cloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
for url in "${candidates[@]}"; do
|
||||||
|
echo " trying ${url}..." >&2
|
||||||
|
if curl -sf --connect-timeout 3 "${url}/healthz" >/dev/null 2>&1; then
|
||||||
|
echo "$url" > "$SERVER_FILE"
|
||||||
|
echo "$url"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "ERROR: could not find BetterFrame server" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Pairing -----------------------------------------------------------------
|
||||||
|
|
||||||
|
pair_with_server() {
|
||||||
|
local server="$1"
|
||||||
|
local hostname
|
||||||
|
hostname=$(hostname)
|
||||||
|
local hw_model
|
||||||
|
hw_model=$(cat /proc/device-tree/model 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
echo "Requesting pairing code..."
|
||||||
|
local resp
|
||||||
|
resp=$(curl -sf -X POST "${server}/api/pair/initiate" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"proposed_name\":\"${hostname}\",\"hardware_model\":\"${hw_model}\",\"capabilities\":[\"rtsp\",\"gstreamer\"]}")
|
||||||
|
|
||||||
|
local code
|
||||||
|
code=$(echo "$resp" | jq -r '.code')
|
||||||
|
local expires
|
||||||
|
expires=$(echo "$resp" | jq -r '.expires_at')
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
echo " PAIRING CODE: ${code}"
|
||||||
|
echo ""
|
||||||
|
echo " Enter this code in the BetterFrame admin"
|
||||||
|
echo " Expires: ${expires}"
|
||||||
|
echo ""
|
||||||
|
echo "============================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Poll until admin confirms
|
||||||
|
echo "Waiting for admin to confirm..."
|
||||||
|
while true; do
|
||||||
|
local claim
|
||||||
|
claim=$(curl -s -o /dev/null -w "%{http_code}" -X POST "${server}/api/pair/claim" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"code\":\"${code}\"}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$claim" == "200" ]]; then
|
||||||
|
local claim_resp
|
||||||
|
claim_resp=$(curl -sf -X POST "${server}/api/pair/claim" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"code\":\"${code}\"}")
|
||||||
|
|
||||||
|
local kiosk_key
|
||||||
|
kiosk_key=$(echo "$claim_resp" | jq -r '.kiosk_key')
|
||||||
|
local kiosk_name
|
||||||
|
kiosk_name=$(echo "$claim_resp" | jq -r '.kiosk_name')
|
||||||
|
|
||||||
|
echo "$kiosk_key" > "$KEY_FILE"
|
||||||
|
chmod 600 "$KEY_FILE"
|
||||||
|
|
||||||
|
echo "Paired as: ${kiosk_name}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Bundle ------------------------------------------------------------------
|
||||||
|
|
||||||
|
fetch_bundle() {
|
||||||
|
local server="$1"
|
||||||
|
local key
|
||||||
|
key=$(cat "$KEY_FILE")
|
||||||
|
|
||||||
|
echo "Fetching bundle..."
|
||||||
|
curl -sf "${server}/api/kiosk/bundle" \
|
||||||
|
-H "Authorization: Bearer ${key}" \
|
||||||
|
> "$BUNDLE_FILE"
|
||||||
|
|
||||||
|
local cam_count
|
||||||
|
cam_count=$(jq '.cameras | length' "$BUNDLE_FILE")
|
||||||
|
echo "Bundle received: ${cam_count} camera(s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- GStreamer ----------------------------------------------------------------
|
||||||
|
|
||||||
|
launch_cameras() {
|
||||||
|
local pids=()
|
||||||
|
|
||||||
|
local cam_count
|
||||||
|
cam_count=$(jq '.cameras | length' "$BUNDLE_FILE")
|
||||||
|
|
||||||
|
if [[ "$cam_count" -eq 0 ]]; then
|
||||||
|
echo "No cameras in bundle. Waiting..."
|
||||||
|
sleep 30
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in $(seq 0 $((cam_count - 1))); do
|
||||||
|
local name
|
||||||
|
name=$(jq -r ".cameras[$i].name" "$BUNDLE_FILE")
|
||||||
|
local type
|
||||||
|
type=$(jq -r ".cameras[$i].type" "$BUNDLE_FILE")
|
||||||
|
|
||||||
|
local rtsp_uri=""
|
||||||
|
|
||||||
|
if [[ "$type" == "rtsp" ]]; then
|
||||||
|
# Use direct RTSP URL, or first stream's URI
|
||||||
|
rtsp_uri=$(jq -r ".cameras[$i].rtsp_url // empty" "$BUNDLE_FILE")
|
||||||
|
if [[ -z "$rtsp_uri" ]]; then
|
||||||
|
rtsp_uri=$(jq -r ".cameras[$i].streams[0].rtsp_uri // empty" "$BUNDLE_FILE")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# ONVIF — use first discovered stream URI
|
||||||
|
rtsp_uri=$(jq -r ".cameras[$i].streams[0].rtsp_uri // empty" "$BUNDLE_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$rtsp_uri" ]]; then
|
||||||
|
echo " [${name}] no RTSP URI, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " [${name}] launching: ${rtsp_uri}"
|
||||||
|
|
||||||
|
# Try hardware decode first (Pi5 V4L2), fall back to software
|
||||||
|
gst-launch-1.0 \
|
||||||
|
rtspsrc location="$rtsp_uri" latency=300 protocols=tcp \
|
||||||
|
! rtph264depay \
|
||||||
|
! h264parse \
|
||||||
|
! queue max-size-buffers=1 leaky=downstream \
|
||||||
|
! avdec_h264 \
|
||||||
|
! videoconvert \
|
||||||
|
! autovideosink sync=false \
|
||||||
|
2>/dev/null &
|
||||||
|
pids+=($!)
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#pids[@]} -eq 0 ]]; then
|
||||||
|
echo "No cameras could be launched"
|
||||||
|
sleep 10
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Displaying ${#pids[@]} camera(s). Press Ctrl+C to stop."
|
||||||
|
|
||||||
|
# Wait for any to exit
|
||||||
|
wait -n "${pids[@]}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Heartbeat ---------------------------------------------------------------
|
||||||
|
|
||||||
|
start_heartbeat() {
|
||||||
|
local server="$1"
|
||||||
|
local key
|
||||||
|
key=$(cat "$KEY_FILE")
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
curl -sf -X POST "${server}/api/kiosk/heartbeat" \
|
||||||
|
-H "Authorization: Bearer ${key}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"kiosk_app_version":"prototype-0.1"}' \
|
||||||
|
>/dev/null 2>&1 || true
|
||||||
|
sleep 60
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Main --------------------------------------------------------------------
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo "BetterFrame Kiosk Prototype"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Discover server
|
||||||
|
echo "Discovering server..."
|
||||||
|
local server
|
||||||
|
server=$(discover_server "${1:-}")
|
||||||
|
echo "Server: ${server}"
|
||||||
|
|
||||||
|
# Pair if needed
|
||||||
|
if [[ ! -f "$KEY_FILE" ]]; then
|
||||||
|
pair_with_server "$server"
|
||||||
|
else
|
||||||
|
echo "Already paired (key in ${KEY_FILE})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch bundle
|
||||||
|
fetch_bundle "$server"
|
||||||
|
|
||||||
|
# Start heartbeat in background
|
||||||
|
start_heartbeat "$server" &
|
||||||
|
|
||||||
|
# Launch cameras
|
||||||
|
launch_cameras
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
kill $(jobs -p) 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'kill $(jobs -p) 2>/dev/null; exit' INT TERM
|
||||||
|
main "$@"
|
||||||
Loading…
Reference in a new issue