The P3 phone will sit in the AI Foundry lab on Wi-Fi and Tailscale, with no computer plugged in. Here is the infrastructure that keeps it updatable and resettable from home, and what still works when each piece fails.
eth0)./deploy-phone.sh --http pushes the signed APK to POST /api/upgrade (`:8080`) or Keeper (`:8081`), and ./deploy-phone.sh --spacesheep stages the APK on GitHub release ota-latest and triggers upgrade_url over thermal/p3-control — both verified over Wi-Fi and over cellular LTE (`rmnet_data2`) with Wi-Fi disabled. In the lab, ./deploy-phone.sh --usb deploys straight over a USB-C cable.StorageUtils.evaluateNetworkFailover() checks whether Wi-Fi has NET_CAPABILITY_VALIDATED. When Wi-Fi drops, it binds the process via ConnectivityManager.bindProcessToNetwork(cellularNetwork) and triggers an immediate WebRTC ICE reconnect (`SpacesheepPublisher.onNetworkTransportChanged`) so 25 fps thermal video, 12 Hz telemetry, and thermal/p3-control continue uninterrupted over the SIM card (and switch back to Wi-Fi or USB-Ethernet when restored).com.thermal.p3stream and com.thermal.p3keeper are directBootAware="true", hold persistent IUsbManager USB permission, and hold WRITE_SECURE_SETTINGS to re-assert adb_wifi_enabled = 1 and adb_allowed_connection_time = 0 on boot and every 30 s.0x40/0xEE poke at 1.5 s / 3.0 s; a wedged bulk endpoint (EP0 alive, 0 frames) triggers kernel resetDevice() + CLEAR_FEATURE(ENDPOINT_HALT); and a 64-bit FNV-1a pixel hash (`lastFrameHash`) auto-triggers softReset() if 150 consecutive frames (~6 s) are identical.?action=preset&name=visualize|raw). One call switches the camera pipeline between calibrated °C viewing and unsmoothed 14-bit NUC raw capture (protocol guide), persisting across USB reconnects and reboots.ActivityManager.getHistoricalProcessExitReasons, and Device-Protected last_crash.json.com.thermal.p3keeper v1.0.1, port 8081): 30 s health check, auto-restart after 3 misses, independent rollback/reboot/upgrade, listenPorts discovery, and kick_tailscale.last_good.apk after 25 s at ≥10 fps, auto-rolls back after 10 min if a new update fails, and records last_crash.json + OS exit reasons.thermal/p3-control: verified in constant time against DEFAULT_TOKEN_SHA256.~/.config/spacesheep/p3-signing-backup.keystore (`chmod 600`).WRITE_SECURE_SETTINGS to keep wireless ADB on across reboots; port discovery works via :8080, Keeper :8081, or a 128-thread TCP scan (`32768–60999`), plus ./deploy-phone.sh --usb in the lab.Verified live on the Realme 14T 5G (`RMX5074`) over Wi-Fi, Tailscale (`100.98.192.61`), Google Fi LTE SIM, and direct USB-C (`./deploy-phone.sh --usb`).
Today, without a keeper: the Mac pushes the build over Tailscale straight to the camera app, which installs it with no tap.
com.thermal.p3stream: P3 driver, port 8080, WebRTC to Spacesheep; installs its own updates and starts on bootThe keeper is a separate app on purpose: a bad camera build can't break the path that replaces it.
| What breaks | Fixed by, automatically | If that doesn't work | Visit? |
|---|---|---|---|
| P3 stream freezes | Camera app: USB re-sync, then re-handshake | Keeper restarts the camera app | No |
| Camera app hangs or crashes | Keeper restarts it within a minute | Keeper reboots the phone | No |
| A bad update | Keeper's 10-minute check fails, it reinstalls the last good build | "Roll back" button, either path | No |
| A WebView update breaks WebRTC | Camera app restarts WebRTC | Keeper restarts the app, then reboots | No |
| The phone reboots | Keeper and camera start on boot; Tailscale comes back as always-on VPN | Nothing else needed: no PIN to type | No |
| Tailscale down, or its key expired | Spacesheep control stream (thermal/p3-control, verified by token_sha256) and editor buttons still reach the camera app & keeper, including full OTA APK upgrades (./deploy-phone.sh --spacesheep) | Nightly reboot reconnects it | No |
| Spacesheep down | Tailscale still reaches the keeper | The camera's LAN viewer still works | No |
| Lab Wi-Fi down | Google Fi LTE SIM: evaluateNetworkFailover() binds the process to cellular & reconnects WebRTC + OTA automatically | Switches back to Wi-Fi (or USB-Ethernet eth0) as soon as it returns | No |
| The keeper itself breaks | Wireless ADB (kept on across reboots via WRITE_SECURE_SETTINGS, port discovered via :8080 or TCP scan) | In-lab laptop via ./deploy-phone.sh --usb | Rare |
| Charger unplugged / power cut | unplugged_alert fires after 10 min on battery (hours before the battery runs flat) | Rung 6 smart-plug reboot (only when battery ≥ 30%) | Only if long outage |
| Android freezes completely | Nothing in software (optional Fingerbot on power button + USB webcam view) | Hold the power button 30 s | Yes, rare |
With cellular LTE failover, WRITE_SECURE_SETTINGS wireless ADB keep-alive, and kick_tailscale, only a full kernel hang requires a physical button press.
reset_stream / ?action=soft_resetresetDevice() + CLEAR_FEATURE(ENDPOINT_HALT) if bulk EP 0x81 wedged). Remote button hard_reset / ?action=hard_resetrestart_app / ./deploy-phone.sh --restart-approllback / ./deploy-phone.sh --rollbacksvc power reboot (auto at 600 s stalled). Remote button reboot_phone / ./deploy-phone.sh --reboot?action=nightly_reboot&enabled=1&hour=4) + 45–360 s charger power-cut detector when battery ≥ 30% (?action=power_cycle_reboot&enabled=1)./deploy-phone.sh --usb)Roman, Nate, or you with a USB-C laptop cableP3_KEYSTORE_FILE or shared debug keystore, backed up to ~/.config/spacesheep/p3-signing-backup.keystore). The version number goes up every build, and the APK carries only the 64-hex SHA-256 digest (DEFAULT_TOKEN_SHA256)../deploy-phone.sh --http (Tailscale/LAN/Cellular), ./deploy-phone.sh --spacesheep (private GitHub release ota-latest + thermal/p3-control over Wi-Fi or Cellular SIM), or ./deploy-phone.sh --usb in the lab.freeStorageMb), snapshots the currently running verified APK to last_good.apk, and backs up settings to com.thermal.p3keeper.PackageInstaller.pictureHealthy == true for 25 s, the build is marked currentBuildVerifiedGood = true and cached as last_good.apk. If 10 minutes pass after an update with the P3 plugged in and the build still hasn't streamed cleanly, it automatically rolls back to last_good.apk.Today (v1.3.8, code 11 + com.thermal.p3keeper v1.0.1, code 2): all five steps, automatic 10-minute verification, cached last_good.apk rollback, Direct Boot (LOCKED_BOOT_COMPLETED), persistent USB permission, cellular SIM failover, and the separate Keeper app on port 8081 are live on the Realme 14T 5G.
:keeper)com.thermal.p3keeper v1.0.1 (port 8081)directBootAware="true") with KeeperDeviceAdminReceiver/health, /restart-camera, /rollback-camera, /reboot, /upgrade-camera, /kick-tailscale) behind token_sha256127.0.0.1:8080/health + re-asserting adb_wifi_enabled = 1 via WRITE_SECURE_SETTINGS; auto-restarts com.thermal.p3stream after 3 misseslistenPorts (from /proc/net/tcp*) so deploy-phone.sh finds the wireless ADB port even when :8080 is downthermal/p3-control polling + KeeperSettingsProvider backup/restore:app)LOCKED_BOOT_COMPLETED + persistent IUsbManager USB grant + WRITE_SECURE_SETTINGS wireless ADB keep-alive)eth0) failover via bindProcessToNetwork + WebRTC ICE reconnectlastFrameHash + pictureHealthy check (auto-resets stream after 150 identical frames; resets wedged bulk EP via resetDevice())com.tailscale.ipn.CONNECT_VPN after 60 s down) + Device Owner freeze policieslast_crash.json + getHistoricalProcessExitReasons + freeStorageMb + wifiRssiDbm + 10-min unplugged_alert + battery_overheatDEFAULT_TOKEN_SHA256 only) + keystore backed up to ~/.config/spacesheep/p3-signing-backup.keystore?action=healthcheck_url, pinged every 5 min when healthy)tools/p3_watchdog.py: probes :8080/health (checking picture_healthy), :8081/health, and Spacesheep telemetry; auto-kicks Tailscale; alerts on unplugged_alert and battery_overheatpython3 tools/p3_watchdog.py --drill: non-disruptive end-to-end test of all 3 control paths (:8080 ping_drill, :8081/health, and Spacesheep thermal/p3-control round-trip)./deploy-phone.sh --usb (fast in-lab USB-C laptop deploy), --http, --spacesheep, --reboot, --rollback, --restart-app, --statusreset_stream, hard_reset, restart_app, rollback, reboot_phoneWhen the Anker USB-C hub (0x291a:0x8355) is plugged into the Realme 14T 5G, the phone is the USB Host (DFP, data_role=host) so it can drive the Thermal Master P3 camera (0x3474:0x45A2). Because USB 2.0 is strict single-host and the Anker hub's USB-C PD input port is power-only while its downstream ports are host-facing, plugging a laptop into a downstream port of the hub will not enumerate the phone as an ADB device. When you walk into the lab with a laptop, use any of these three fast workflows:
./deploy-phone.sh --usbUFP mode and enumerates on `adb devices -l`)../deploy-phone.sh --usb (builds `:app` + `:keeper`, installs both over USB, re-applies `WRITE_SECURE_SETTINGS`, disables app hibernation & 7-day ADB timeout, and starts `p3_proc_helper.sh` in ~12 s)../deploy-phone.sh realme./deploy-phone.sh realme — it queries `:8080/api/status.phone.listenPorts` (or Keeper `:8081/health.listenPorts`, or runs a 1-second 128-thread TCP scan of `32768–60999` if both HTTP servers are down), connects `adb connect`, and sets up `adb forward tcp:8080 tcp:8080` + `tcp:8081 tcp:8081`.| Item | Why | Status |
|---|---|---|
| Anker USB-C hub (`0x291a:0x8355`) with charging pass-through | The P3 and the charger share the phone's one port | In use since 15 Oct: charges at 1.39 A, +28%/h while streaming; plain 5 V, no PD |
| Google Fi data SIM (`rmnet_data2`) | Automatic second network when lab Wi-Fi drops (both live streaming & OTA updates) | Active & verified 15 Oct (automatic failover in v1.3.8) |
| Smart plug with its own cloud app | A reset that needs neither Tailscale nor Spacesheep to reach the phone (cut power 45–360 s; phone auto-reboots when power returns) | Detector + 10-min `unplugged_alert` built in v1.3.8; plug to buy |
| Lab spares drawer (hub, OTG cable, pre-provisioned backup phone) | Swap a failed hub or phone in 2 minutes; backup signing key is at `~/.config/spacesheep/p3-signing-backup.keystore` | Pixel 9 Pro XL available as backup phone |
| A card on the mount (+ optional Fingerbot / Pi host later) | "If it's dark and nothing responds: hold power 30 s" | To print |
com.thermal.p3stream and com.thermal.p3keeper are 100% directBootAware="true" and store their config in Device-Protected storage (/data/user_de/0/...), with persistent USB permission registered in Android's IUsbManager via p3_shell_tool.jar and WRITE_SECURE_SETTINGS keeping wireless ADB on. On LOCKED_BOOT_COMPLETED (before any PIN is entered), the phone immediately starts the P3 USB driver at 25 Hz, starts port 8080 & port 8081 HTTP servers, starts publishing 2.5 fps thermal stills + 12 Hz telemetry to Spacesheep (over Wi-Fi or Google Fi LTE SIM), and starts polling thermal/p3-control. Once unlocked (or immediately on boot if Screen Lock = None), full 25 fps WebRTC video starts too.com.tailscale.ipn) is not Direct-Boot-aware, Android blocks Tailscale from starting until the phone is unlocked (or has no screen lock). Remove the PIN once:
adb shell locksettings clear --old YOUR_PIN/api/status that bootReadiness.lockScreenDisabled == true and bootReadiness.readyForUnattendedReboot == true.python3 tools/p3_watchdog.py --drill (verifies `:8080`, `:8081`, and `thermal/p3-control` round-trip in ~6 s without interrupting streaming)../deploy-phone.sh --reboot realme (or --restart-app, --rollback, --http, --spacesheep, --usb).curl -X POST -H "Authorization: Bearer $SPACESHEEP_API_KEY" "http://100.98.192.61:8080/api/control?action=reboot" (or http://100.98.192.61:8081/reboot).RollbackAndRebootManager detects the power-cut window and triggers a full system reboot.?action=nightly_reboot&enabled=1&hour=4).adb shell dpm set-device-owner com.thermal.p3keeper/.KeeperDeviceAdminReceiver (or com.thermal.p3stream/.P3DeviceAdminReceiver), then re-add accounts. Even without Device Owner, ./deploy-phone.sh grants WRITE_SECURE_SETTINGS, disables app hibernation and 7-day ADB expiry, and starts p3_proc_helper.sh as UID 2000 (com.android.shell, which holds android.permission.REBOOT and MANAGE_USB).Because the Spacesheep live streaming URL (thermal-master-p3-live-camera-stream) is shared publicly on the internet while a personal Google account is signed into the phone, five independent security layers isolate the camera stream from the phone and its accounts:
| Defense layer | What could an internet attacker try? | How v1.3.9 + Keeper v1.0.2 block it |
|---|---|---|
| 1. Public Spacesheep stream & telemetry lockdown | Read internal LAN/Tailscale/Cellular IPs or open ports from thermal/p3-telemetry, or send button/OTA commands over /-/events | publicViewerLock = true and encryptPublicTelemetry = true by default: plaintext IPs, ports, device model, and crash traces are stripped from public telemetry and encrypted with AES-256-GCM (priv_enc, keyed by SHA-256("p3-telemetry-priv-v1:" + apiKey)). Every mutating or privileged command on /-/events and thermal/p3-control requires constant-time verifyAuthToken(). |
2. Network & HTTP port firewall (:8080 & :8081) | Scan or connect to port 8080/8081 over the Google Fi cellular address, or trigger cross-origin CSRF from a malicious web page | StorageUtils.isAllowedHttpPeer(socket) drops any socket arriving on the cellular interface or from a non-RFC1918 / non-Tailscale (100.64.0.0/10) IP before reading a single HTTP byte. Cross-origin browser requests are rejected with 403 Forbidden, non-loopback /api/control requires Bearer token auth by default (requireControlToken = true), unauthenticated /api/status & /health redact IPs/ports/processes, and per-IP rate limiting locks out 5 failed token checks for 5 minutes (429 Too Many Requests). |
| 3. Cryptographic OTA & APK supply-chain lock | Trick the phone into downloading an untrusted APK via upgrade_url | installApkFromUrl on both :8080 and Keeper :8081 enforces StorageUtils.isAllowedOtaUrl(url) (HTTPS-only + strict host allowlist: github.com, api.github.com, *.githubusercontent.com, spacesheep.dev), requires a mandatory 64-hex sha256 digest verified before session.commit(), and Android's PackageInstaller enforces signature match against ~/.config/spacesheep/p3-signing-backup.keystore. |
| 4. Optical RGB camera privacy lock & WebView sandbox | Turn on the phone's front/rear optical RGB camera or microphone to spy on the lab, or exploit WebView file access | rgbLockedOff = true by default: the phone's optical Camera2/WebRTC pipeline is disabled and can only be unlocked via token-authenticated ?action=rgb_lock&enabled=0. AlwaysActiveWebView disables file/content/universal URL access and geolocation, restricts navigation to https://spacesheep.dev, and denies PermissionRequest.RESOURCE_AUDIO_CAPTURE and RESOURCE_VIDEO_CAPTURE. |
| 5. Personal Google account & app isolation | Access Gmail, Chrome, Google Drive, Photos, Calendar, Keep, Maps, or YouTube on the phone, or extract backed-up tokens | android:allowBackup="false" on both APKs prevents Android Auto-Backup from uploading shared_prefs to Google Drive. KeeperSettingsProvider enforces PackageManager.SIGNATURE_MATCH so no third-party app can query backed-up settings. ./deploy-phone.sh --lockdown-google (and ?action=lockdown_google&enabled=1) force-stops and disables (pm disable-user --user 0) Gmail, Chrome, Drive, Photos, YouTube, Maps, Keep, and Calendar while keeping Play Services & Tailscale active. |
com.thermal.p3keeper v1.0.2) is now built and installed alongside the camera app, keeping the Spacesheep path with no server to run./api/control, POST /api/upgrade) and port 8081 Keeper HTTP (/health, /restart-camera, /rollback-camera, /reboot, /upgrade-camera, /upgrade-keeper, /kick-tailscale), authenticated via your Spacesheep token or its SHA-256 digest and firewall-restricted to loopback/LAN/Tailscale; (2) the Spacesheep editor events channel on thermal/p3-telemetry/-/events (protected by publicViewerLock + operator token SHA-256); (3) the outbound Spacesheep control stream thermal/p3-control over Wi-Fi or Google Fi LTE SIM; (4) the 45–360 s charger power-cut detector for smart-plug reboots; and (5) in-lab USB-C deployment (./deploy-phone.sh --usb).#1–#8, and the 5-layer security hardening architecture are built: Direct Boot (LOCKED_BOOT_COMPLETED) + Device-Protected storage, persistent IUsbManager USB permission, WRITE_SECURE_SETTINGS wireless ADB keep-alive, automatic Wi-Fi ↔ Cellular LTE ↔ USB-Ethernet failover, 64-bit FNV-1a pictureHealthy frozen-frame reset, wedged bulk EP resetDevice() recovery, kick_tailscale, 10-min unplugged_alert + battery_overheat, last_crash.json + OS exit reasons, keystore backup, configurable healthcheck_url, AES-256-GCM priv_enc telemetry encryption, personal Google app lockdown (--lockdown-google), and tools/p3_watchdog.py --drill.DFP). To debug over a USB-C cable in the lab, unplug the Anker hub and run ./deploy-phone.sh --usb (or plug a USB-Ethernet adapter into a downstream port of the Anker hub).~/git/thermal-android, the thermal camera hub (the hub, the lab's people) and the AI Foundry hub (the lab's tailnet, the Intel's watchdog).Part of the thermal camera hub. Live stream: thermal-master-p3-live-camera-stream.