S.A.A.D. is a LAN web dashboard and agent API for V360 Pro / CloudBirds camera-car devices. It lets a browser, a human operator, or an automation agent drive the camera car, view the live camera feed, use two-way audio, and call the same control surface through HTTP.
This project was built from reverse-engineering and live testing of a V360 Pro-compatible camera car using the CloudBirds/V360 Pro Android app family.
V2 is being worked on which should be much better. This was done in a few hours and its quite janky. That said, here's a video showing a little of what is possible in this version video
- Start a live camera session from the browser.
- Show the camera feed in the web dashboard through an ffmpeg-backed MJPEG proxy.
- Drive the car with a browser remote or HTTP API.
- Send stop commands with safe auto-stop behavior for agent driving.
- Capture microphone audio from the browser and send it to the car speaker.
- Type text into the web dashboard and speak it through the car speaker.
- Change camera speaker/input volume.
- Request device info and set mapped camera settings such as motion sensitivity, TF recording mode, timezone, and voice language.
- Capture camera audio to WAV for downstream STT.
- Expose an agent-friendly REST API for Codex, Claude Code, Hermes, OpenClaw, or other local tools.
- Keep controlling an established LAN-direct session after camera internet is blocked.
Live S.A.A.D. dashboard with an active V360 Pro camera-car feed.
The public release uses the same camera-first controller design: live view in the center, movable drive/comms cards, debug tools under the inspector, and one-button live start.
Tested against a V360 Pro / CloudBirds camera-car device class using:
- Android app package:
com.dayunlinks.cloudbirds - Device type observed in testing:
43 - Reset AP family observed in testing:
HAP-... - Transport: proprietary PPCS through the V360 Pro Android native library
Purchase link: https://www.aliexpress.us/item/3256809055284076.html
Price observed: $45
Listings often describe the device generically as a V360 Pro camera car, smart mobile car CCTV, moving pet camera, or two-way voice camera car.
V360 Pro app / APK references:
- Official app listing: https://play.google.com/store/apps/details?id=com.dayunlinks.cloudbirds
- APKPure download page: https://apkpure.net/v360-pro/com.dayunlinks.cloudbirds/download
- V360 Pro app site: https://v360pro.camapp365.com/
The required libPPCS_API.so can be found inside the V360 Pro APK. This repository does not bundle the APK or vendor native library.
Browser dashboard
|
v
Python web server / agent API
|
v
PPCS backend boundary
|
v
Android bridge on TCP 8766
|
v
V360 Pro libPPCS_API.so
|
v
Camera car over PPCS / LAN-direct session
The web app does not speak RTSP or plain HTTP to the camera. It talks to the Python server. The Python server talks to a local Android bridge. The Android bridge loads the V360 Pro libPPCS_API.so and creates PPCS sessions by device ID.
Once PPCS rendezvous completes, tested sessions can land LAN-direct to the camera. After that, the camera can keep streaming/control locally even if its internet is blocked. A fresh session still needs camera outbound internet for PPCS rendezvous.
The code has an explicit PPCS backend boundary in src/v360pro_camcar/ppcs_backend.py. The current working backend is AndroidBridgePPCSBackend. NativePPCSBackend is intentionally present but unavailable until the proprietary PPCS transport is fully ported and verified.
src/v360pro_camcar/web.py: HTTPS web server, dashboard static serving, bridge proxy, MJPEG feed, agent API.src/v360pro_camcar/client.py: high-level PPCS session helpers.src/v360pro_camcar/protocol.py: APK-derived command and media frame encoders/parsers.src/v360pro_camcar/audio.py: PCM16/G.711A conversion for talk/audio paths.src/v360pro_camcar/ppcs_bridge.py: JSON-lines client for the Android bridge.src/v360pro_camcar/ppcs_backend.py: backend factory and boundary for the current bridge backend and future native PPCS backend.bridge/android/: Android bridge app source. It expects the user-provided V360 Pro PPCS native library.src/v360pro_camcar/static/: browser dashboard assets.tests/: protocol, API, web, audio, and bridge behavior tests.
- Python 3.10+
- ffmpeg available on PATH for live browser playback and camera-speaker TTS
- Android SDK / adb
- An Android ARM64 device or emulator capable of loading the V360 Pro native PPCS library
- V360 Pro APK source for
libPPCS_API.so - A V360 Pro-compatible camera car already bound or ready to pair
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e .Check the CLI:
saad --helpThe legacy command alias also exists:
v360cam --helpThe web app needs the Android bridge for PPCS. The bridge project is in bridge/android/.
The V360 Pro native library file is named:
libPPCS_API.so
It is found inside the V360 Pro APK. This repository intentionally does not include it.
Build and run the bridge after placing your vendor library in the Android project's expected JNI library location:
cd bridge/android
gradle :app:assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.v360pro.camcar.bridge/.MainActivity
adb forward tcp:8766 tcp:8766Verify from the project root:
saad bridge --host 127.0.0.1 --port 8766 info
saad bridge --host 127.0.0.1 --port 8766 initFor local-only testing:
saad web --host 0.0.0.0 --port 8765For browser microphone support over a LAN IP, use HTTPS. Browsers block microphone capture on plain HTTP origins except localhost.
saad web --host 0.0.0.0 --port 8765 \
--tls-cert .local/saad-web.crt \
--tls-key .local/saad-web.keyOpen the printed LAN URL from a browser on the same network.
The dashboard title is Super Awesome AI Driver (S.A.A.D.) and the footer links to https://gainsec.com.
If an external tool has already created and live-started a PPCS session, you can attach the dashboard feed directly:
http://127.0.0.1:8765/?session=SESSION&autoplay=1
S.A.A.D. includes helpers for APK-derived pairing payloads and discovery, but the simplest first setup is still to bind the camera with the official V360 Pro app, then use S.A.A.D. for local control.
QR payload format:
S<ssid>
<wifi-password>
Open network payload:
S<ssid>
Useful commands:
saad pair qr "YourWiFi" "YourPassword"
saad pair hap-did "HAP-YOUR-DEVICE-DID"
saad pair listen --bind-host 0.0.0.0 --port 8899 --timeout 60Important behavior:
-
Pairing/binding is still easiest with the official V360 Pro app first.
-
After the camera is bound, the phone app does not need to be online for S.A.A.D. control.
-
A fresh PPCS session needs the camera car to have outbound internet for rendezvous/presence.
-
The Android bridge and
libPPCS_API.soare still required while using S.A.A.D.; they own the active PPCS session and carry control, audio, and video. -
Once a PPCS session is established and lands LAN-direct, the camera car's internet can be blocked while that existing session stays alive.
-
If the current session drops, the camera reboots, Wi-Fi changes, the Android bridge stops, or the Python web server loses its remembered session, allow camera internet again and start a new session.
-
The phone app is what becomes optional after binding. The Android bridge does not become optional in the current implementation.
-
A fresh PPCS session needs the camera car to have outbound internet for rendezvous.
-
The phone app does not need to be online once the camera is already bound.
-
Once a session is established and LAN-direct, you can block camera internet and keep using the current session until it drops.
-
If the camera reboots, Wi-Fi changes, the bridge restarts, or the session drops, allow camera internet again for a fresh rendezvous.
Use your own DID, password, device type, and user/remote ID:
curl -sk https://127.0.0.1:8765/api/agent/start \
-H 'Content-Type: application/json' \
-d '{
"did": "YOUR-DID",
"device_type": "43",
"password": "YOUR-CAMERA-PASSWORD",
"user_id": 0,
"timeout": 25
}'The response includes the PPCS session and live media details when successful.
Discover the API:
curl -sk https://127.0.0.1:8765/api/agent/specGet status:
curl -sk https://127.0.0.1:8765/api/agent/statusAttach to an existing bridge session after a web server restart:
curl -sk https://127.0.0.1:8765/api/agent/attach \
-H 'Content-Type: application/json' \
-d '{"session":42}'Drive briefly:
curl -sk https://127.0.0.1:8765/api/agent/drive \
-H 'Content-Type: application/json' \
-d '{"direction":"up","speed":0,"duration_ms":350}'Stop:
curl -sk -X POST https://127.0.0.1:8765/api/agent/stopSpeak through the agent's normal channel:
curl -sk https://127.0.0.1:8765/api/agent/say \
-H 'Content-Type: application/json' \
-d '{"text":"I am at the doorway.","channel":"agent"}'Speak through the camera car speaker:
curl -sk https://127.0.0.1:8765/api/agent/say \
-H 'Content-Type: application/json' \
-d '{"text":"Hello from S.A.A.D.","channel":"camera"}'Set camera audio volume:
curl -sk https://127.0.0.1:8765/api/agent/volume \
-H 'Content-Type: application/json' \
-d '{"volume":100}'Mapped camera settings:
curl -sk https://127.0.0.1:8765/api/agent/device-info -H 'Content-Type: application/json' -d '{"session":42}'
curl -sk https://127.0.0.1:8765/api/agent/set-motion -H 'Content-Type: application/json' -d '{"session":42,"channel":2,"sensitivity":75}'
curl -sk https://127.0.0.1:8765/api/agent/set-record-mode -H 'Content-Type: application/json' -d '{"session":42,"record_type":17,"enabled":true}'
curl -sk https://127.0.0.1:8765/api/agent/set-timezone -H 'Content-Type: application/json' -d '{"session":42,"gmt_offset_hours":-4,"local_time":"2026-07-17;12:13:14"}'
curl -sk https://127.0.0.1:8765/api/agent/set-voice -H 'Content-Type: application/json' -d '{"session":42,"code":"en"}'
curl -sk https://127.0.0.1:8765/api/agent/get-voice -H 'Content-Type: application/json' -d '{"session":42}'Capture audio for external STT:
curl -sk https://127.0.0.1:8765/api/agent/listen-capture \
-H 'Content-Type: application/json' \
-d '{"seconds":5}'Capture and transcribe with the local Whisper CLI:
curl -sk https://127.0.0.1:8765/api/agent/listen-transcribe \
-H 'Content-Type: application/json' \
-d '{"seconds":5,"model":"turbo","language":"en"}'Live hardware testing confirmed the camera mic writes a valid 8 kHz mono PCM16 WAV from camera audio frames. Local STT also worked through the optional whisper CLI; the phrase "sandwich, peanut butter jelly sandwich, peanut butter jelly sandwich, peanut butter jelly sandwich" transcribed from the car mic. Intelligibility depends on the small camera microphone, ambient noise, and current session buffering.
This release includes a portable agent skill at skills/saad-agent-control. Give that folder to Codex, Claude, Hermes, OpenClaw, or another agent runtime so it can quickly learn the safe S.A.A.D. workflow and the /api/agent/* endpoints.
Give your agent the dashboard URL and tell it to read:
GET /api/agent/spec
Recommended agent workflow:
- Call
/api/agent/status. - Call
/api/agent/startif no session is active. - If the bridge still has a valid session after a web server restart, call
/api/agent/attach. - Use short
/api/agent/drivecalls with explicitduration_ms. - Use
/api/agent/saywithchannel:"agent"for normal chat output. - Use
channel:"camera"only when you want audio from the car speaker. - Use
/api/agent/listen-captureto save car microphone audio for your own STT pipeline, or/api/agent/listen-transcribewhen a local Whisper CLI is available. - Use settings endpoints only for explicit tasks: device info, motion sensitivity, record mode, timezone, or voice language.
- Call
/api/agent/stopbefore ending the run.
The drive endpoint always sends a stop after the requested duration. Duration is capped server-side to reduce runaway movement risk.
You can use S.A.A.D. as a normal human remote:
- Open the web dashboard.
- Enter your DID, password, type, and user ID under Inspector -> Debug.
- Click Start Live.
- Use the drive pad.
- Use Talk/Mute for browser microphone audio.
- Type into Speak Through Car to use the car speaker.
- Use Debug tools for manual PPCS commands and frame reads.
You can also use the CLI:
saad bridge connect YOUR-DID --device-type 43
saad bridge login SESSION YOUR-CAMERA-PASSWORD --user-id 0
saad bridge live-start SESSION
saad bridge ptz SESSION up
saad bridge ptz SESSION stopConfirmed behavior from live testing:
- Phone/app internet is not required for S.A.A.D. after the camera is bound.
- Camera outbound internet is required to create a fresh PPCS session.
- Established LAN-direct sessions can survive after camera internet is blocked.
- The Android bridge is still required for the established session because it owns the PPCS socket through
libPPCS_API.so. - If the current session drops, the bridge restarts, or the web process loses state, allow camera internet again and start a new session.
For tight firewall rules, capture DNS/egress from the camera IP while creating a fresh PPCS session with the phone offline. The relevant path is the camera's PPCS rendezvous/presence traffic, not necessarily the app's account HTTPS endpoints.
- Do not publish your DID, camera password, remote/user ID, or MAC address.
- Do not expose the S.A.A.D. web server directly to the internet.
- Use HTTPS for browser microphone support on LAN.
- Treat the camera as an untrusted IoT device.
- Prefer a segmented network and temporary outbound internet only when creating new sessions.
Run tests:
python3 -m pytest -qCheck JavaScript syntax:
node --check src/v360pro_camcar/static/app.js- PPCS is still handled by the vendor Android native library through the Android bridge.
- Pure-Python PPCS is not implemented.
- The native PPCS backend boundary exists, but the native backend is not usable yet.
- Browser playback uses MJPEG transcoding, not direct H.265 playback.
- RTSP/local web services are not confirmed for the tested camera-car class.
- The camera speaker is small and may not be suitable for high-quality TTS.
- STT is optional and local.
/api/agent/listen-transcribeshells out to the installedwhisperCLI;/api/agent/listen-captureremains the raw WAV handoff path. - Audio capture duration can include queued live audio frames from the active PPCS stream, so exact WAV duration may differ from the requested capture window.
Made by Jon "GainSec" Gaines.

