Skip to main content

ip cameras

Service Summary: The DrHeck Camera Hub (https://cam.drheck.dev) provides automatic LAN discovery, low-latency live streaming, optical zoom/spotlight/siren hardware controls, bandwidth adaptation, and a sideloadable Google TV interface for the Reolink PoE IP cameras on 10.0.0.0/24. Access is restricted via Authentik ForwardAuth to members of the camera_access group (luke, ikura, and haruka).

Hardware Inventory & Detected Cameras

Property Camera 1 (RLC-811A (122)) Camera 2 (RLC-811A (123))
IPv4 Address 10.0.0.122 10.0.0.123
MAC Address ec:71:db:f1:25:5a ec:71:db:6d:84:ce
Vendor / OUI Reolink (ec:71:db) Reolink (ec:71:db)
Model / Item No. RLC-811A / P430 RLC-811A / P430
Hardware Revision IPC_560B158MP IPC_560B158MP
Firmware Version v3.1.0.4695_2504301440 v3.1.0.4695_2504301440
Serial Number 141484731968753 141484731989233
Ethernet Link 100M_FULL (802.3af PoE) 100M_FULL (802.3af PoE)
Optical Zoom Range 5X Motorized (ZoomPos 0..28, FocusPos 0..238) 5X Motorized (ZoomPos 0..28, FocusPos 0..238)
Illumination IR Night Vision + 450 lm Warm Floodlight (1..100%) IR Night Vision + 450 lm Warm Floodlight (1..100%)
Audio Two-Way Audio (16 kHz AAC) + Integrated Siren Two-Way Audio (16 kHz AAC) + Integrated Siren

Camera Services, Network Ports & Internal Operation

On firmware v3.1.0.4695_2504301440, Reolink cameras lock down legacy plaintext web/streaming ports out of the box and expose only the proprietary Baichuan management port (9000/tcp) until explicitly instructed to enable standard streaming protocols.

1. Port & Service Matrix (cmd_id = 37 GetNetPort / cmd_id = 36 SetNetPort)

Service Protocol / Port Factory Default Active State on ul Purpose & Operation
Baichuan (server) 9000/tcp Enabled Enabled Proprietary binary + XML control, telemetry, snapshot, PTZ/zoom, and configuration bus.
RTSP (rtsp) 554/tcp, 554/udp Disabled Enabled (Auto-enabled by cam) Real-Time Streaming Protocol serving hardware-encoded main (H.265 4K) and sub (H.264 360p) video + AAC audio.
ONVIF (onvif) 8000/tcp Disabled Disabled (Toggleable) Open Network Video Interface Forum profile for third-party NVR integration.
RTMP (rtmp) 1935/tcp Disabled Disabled (Toggleable) Flash/RTMP H.264 sub-stream (rtmp://<ip>:1935/bcs/channel0_sub.bcs).
HTTP (http) 80/tcp Disabled Disabled (Toggleable) Embedded camera web UI and /cgi-bin/api.cgi JSON endpoint.
HTTPS (https) 443/tcp Disabled Disabled (Toggleable) Self-signed TLS embedded camera web UI and /cgi-bin/api.cgi JSON endpoint.

2. How the Baichuan Protocol (9000/tcp) Works

All discovery, hardware control, and instantaneous snapshots are performed over TCP port 9000 using Reolink's Baichuan protocol:

  1. Binary Framing Header: Every Baichuan packet begins with a 20-byte or 24-byte little-endian binary header starting with magic bytes 0xf0debc0a, followed by cmd_id (uint32), payload length (uint32), channel_id (uint8), status code (200 OK, 400 Bad Request), and payload offset.
  2. Modern Nonce Authentication (cmd_id = 1):
    • The client sends an initial unauthenticated cmd_id = 1 request.
    • The camera replies with an XML <Encryption> challenge containing a random <nonce>.
    • The client computes uppercase MD5 digests of username + nonce and password + nonce (truncated to 31 characters) and submits the authenticated login frame.
    • Once authenticated, payload bodies are encrypted using AES-128-CFB (or XOR stream cipher depending on message class) keyed from the session nonce.
  3. Key Baichuan Command IDs Used by cam.drheck.dev:
    • cmd_id = 80 (GetDevInfo): Model (RLC-811A), item number (P430), firmware version, serial number.
    • cmd_id = 37 / 36 (GetNetPort / SetNetPort): Inspects and enables rtsp (554/tcp), onvif (8000/tcp), rtmp (1935/tcp), http (80/tcp), and https (443/tcp).
    • cmd_id = 76 / 75 (GetLocalLink / SetLocalLink): Reads or writes DHCP vs. static IPv4 settings and MAC address.
    • cmd_id = 182 (Snap): Captures an immediate hardware-encoded JPEG frame (main 4K or sub 640x360) directly over the control socket in ~110 ms.
    • cmd_id = 56 / 57 (GetEnc / SetEnc): Reads and updates H.265/H.264 resolution, frame rate (frame), and target bitrate (bitRate in kbps) for mainStream and subStream.
    • cmd_id = 26 / 25 (GetImage / SetImage): Reads and updates ISP sensor parameters (bright, contrast, saturation, hue, sharpen, DayNight, nr3d).
    • cmd_id = 264 / 265 (GetAudioCfg / SetAudioCfg): Reads and updates speaker volume (0..100).
    • cmd_id = 44 / 45 (GetOsd / SetOsd): Reads and updates On-Screen Display overlays, including the Reolink logo watermark (<OsdChannelName><enWatermark>0|1</enWatermark></OsdChannelName>), camera name overlay (<OsdChannelName><enable>0|1</enable></OsdChannelName>), and timestamp overlay (<OsdDatetime><enable>0|1</enable></OsdDatetime>).
    • cmd_id = 288 / 290 (SetWhiteLed / GetWhiteLed): Controls the warm-white floodlight/spotlight (<FloodlightManual><status>0|1</status></FloodlightManual>) and brightness (1..100).
    • cmd_id = 295 (StartZoomFocus): Moves the 5X motorized varifocal lens to an exact zoom step (ZoomPos 0..28) or focus step (FocusPos 0..238).
    • cmd_id = 276 (AudioAlarmPlay): Triggers the built-in audible security siren.

3. RTSP Stream Endpoints (554/tcp)

Once rtspEnable = 1 is set via Baichuan cmd_id = 36, each camera serves two RTSP streams:

  • Sub Stream (Low-Latency H.264):
    rtsp://admin:<password>@10.0.0.122:554/h264Preview_01_sub
    rtsp://admin:<password>@10.0.0.123:554/h264Preview_01_sub
    
    Encoded as H.264 High Profile (yuv420p, 640x360, default 10 fps at 256 kbps, GOP = 4) with 16 kHz mono AAC audio.
  • Main Stream (4K UHD H.265/HEVC):
    rtsp://admin:<password>@10.0.0.122:554/h265Preview_01_main
    rtsp://admin:<password>@10.0.0.123:554/h265Preview_01_main
    
    Encoded as HEVC / H.265 (yuvj420p, 3840x2160, default 25 fps at 6144 kbps, GOP = 2) with 16 kHz mono AAC audio.

Architecture of cam.drheck.dev

The web service on host ul (/data/docker/cam) bridges the isolated IPv4 camera LAN (10.0.0.0/24) to an authenticated, browser-compatible HTTPS service at https://cam.drheck.dev.

Component Configuration on Host ul
Service Directory /data/docker/cam (app.py, static/index.html, static/tv.html, static/cam-googletv.apk, tests/app_test.py)
Persistent State /data/docker/cam/data/cameras.json
Container Name cam (cam-hub:latest, Python 3.12 + FastAPI + reolink-aio + FFmpeg 7.1)
ULA IPv6 Binding [fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000 on eth77
Nginx Reverse Proxy /etc/nginx/sites-available/cam (X-Accel-Buffering: no, proxy_buffering off, WebSocket + streaming support)
Authentik SSO & Group ProxyProvider (cam-provider, ForwardAuth) -> Application (cam) bound to Group (camera_access)
Backup & Restore /home/luke/git/backups/ul/scripts/backup/cam & /home/luke/git/backups/ul/scripts/restore/cam (.tar.xz + par2 -r10)

How cam.drheck.dev Communicates With the Cameras

  1. Zero-Config Discovery (discover_cameras):
    • Reads the host kernel ARP table (/proc/net/arp mounted read-only at /host_proc_arp).
    • Filters for Reolink IEEE MAC OUI prefixes (ec:71:db and 9c:8e:cd) and probes TCP ports 9000 and 554 across 10.0.0.0/24 (excluding infrastructure router/switch IPs).
    • Connects to each discovered Reolink camera on 9000/tcp, queries hardware metadata (cmd_id = 80), network ports (cmd_id = 37), encoder settings (cmd_id = 56), ISP image settings (cmd_id = 26), and audio volume (cmd_id = 264).
    • If rtsp (554/tcp) is disabled on a newly plugged-in camera, inspect_reolink_camera automatically sends SetNetPort(rtspEnable=1) (cmd_id = 36) so live streaming works immediately without requiring the proprietary Reolink desktop/mobile client.
  2. Hybrid Instant-Snapshot + FFmpeg MJPEG Streaming (mjpeg_stream_generator):
    • Standard web browsers cannot natively decode raw RTSP or H.265 streams inside <img> tags, and waiting for an RTSP H.264 IDR keyframe (GOP = 4) introduces a 1–2 second blank delay on initial load.
    • Furthermore, RLC-811A sub-streams output limited-range yuv420p(progressive) frames; FFmpeg 7.1's mjpeg encoder rejects non-full-range YUV unless explicit color-range conversion (format=yuvj420p) and -strict unofficial are specified.
    • To solve both problems, /api/cameras/{ip}/mjpeg executes a two-stage pipeline:
      1. Stage 1 (<150 ms First Frame): Immediately fetches a single JPEG snapshot via Baichuan cmd_id = 182 and yields it as the first --frame part so the browser displays an image immediately.
      2. Stage 2 (Continuous Live Transcode): Spawns ffmpeg over TCP RTSP:
        ffmpeg -nostdin -loglevel error -rtsp_transport tcp -analyzeduration 1500000 \
          -flags low_delay -i rtsp://admin:@10.0.0.123:554/h264Preview_01_sub -an \
          -vf "fps=10,scale=640:-2,format=yuvj420p" -strict unofficial \
          -f mpjpeg -boundary_tag frame -q:v 5 -
        
      3. Stage 3 (Automatic Fallback): If the RTSP socket ever drops, the generator transparently falls back to streaming Baichuan JPEG snapshots at the requested interval.
  3. Integer State Normalization for Spotlight Control:
    • Reolink's Baichuan SetWhiteLed (cmd_id = 288) XML parser requires <status>1</status> or <status>0</status> and rejects Python boolean strings (<status>True</status>) with status code 400. app.py normalizes state via normalize_floodlight_state(req.state) to integer 1 or 0.

Static IPs vs. DHCP Reservations & IPv6 Support

1. Should the Cameras Be Configured for Static IPs?

Yes — pinning the cameras' IPv4 addresses via static DHCP reservations on danknasty is recommended.

Currently, both cameras use DHCP (<Dhcp><enable>1</enable></Dhcp>) and received 10.0.0.122 and 10.0.0.123 from the dynamic pool (range 10.0.0.114 10.0.0.250;) on danknasty. There are two ways to assign permanent IPs:

  1. Static DHCP Reservation in /etc/dhcpd.conf on danknasty (Recommended):
    • Binds each camera's factory MAC address (ec:71:db:f1:25:5a and ec:71:db:6d:84:ce) to a fixed IPv4 address at the router level while leaving the cameras themselves in DHCP mode.
    • Why this is better than on-camera static IPs: If the LAN subnet or gateway ever changes, a camera with a hardcoded static IP becomes unreachable until someone physically climbs up to the camera housing and holds the hardware reset button on the PoE pigtail for 10 seconds. With a DHCP reservation on danknasty, IP assignment is centrally managed and versioned alongside pikvm, nodegrid, kikuo, and fireboardctl:
      host cam1 {
        hardware ethernet ec:71:db:f1:25:5a;
        fixed-address 10.0.0.122;
      }
      host cam2 {
        hardware ethernet ec:71:db:6d:84:ce;
        fixed-address 10.0.0.123;
      }
      
  2. On-Camera Static IP via Baichuan cmd_id = 75 (SetLocalLink):
    • The camera firmware stores its IPv4 configuration in <LocalLink> (cmd_id = 76 to read, cmd_id = 75 to write):
      <LocalLink version="1.1">
        <Dynamic>
          <Dhcp><enable>0</enable></Dhcp>
          <Ip>
            <ip>10.0.0.122</ip>
            <mask>255.255.255.0</mask>
            <gateway>10.0.0.1</gateway>
          </Ip>
          <Dns>
            <auto>0</auto>
            <dns1>10.0.0.1</dns1>
            <dns2>0.0.0.0</dns2>
          </Dns>
        </Dynamic>
      </LocalLink>
      
    • Even if DHCP leases ever rotate before a reservation is added, cam.drheck.dev automatically discovers the new IP by matching ec:71:db:* in /proc/net/arp.

2. Do the Cameras Support IPv6?

  • Camera Hardware (RLC-811A Firmware v3.1.0.4695_2504301440): No native IPv6 support.
    • Inspection of ip -6 neigh show dev eth77 confirms that neither ec:71:db:f1:25:5a nor ec:71:db:6d:84:ce configures an IPv6 link-local (fe80::/10), SLAAC, or DHCPv6 address, and the Baichuan LocalLink (cmd_id = 76) schema only supports 32-bit IPv4 fields.
  • Service Frontend (cam.drheck.dev): Full Dual-Stack & IPv6 Support.
    • The cam container on host ul binds to dedicated ULA IPv6 address fd77:7777:5010:0:1337:f1ac:d00d:ca30 (port 8000) and is served publicly by Nginx over dual-stack IPv4 and IPv6 (2603:3001:626:9de0:1337:f1ac:d00d:aaaa). Host ul translates incoming IPv6 HTTPS requests to IPv4 Baichuan/RTSP camera traffic on 10.0.0.0/24.

Available Camera Settings & How to Adjust Them

All camera settings can be adjusted interactively in the DrHeck Camera Hub web UI (https://cam.drheck.dev/ — expand Camera Hardware Encoder, Bandwidth, Image & Protocol Settings on any camera card) or programmatically via POST /api/cameras/{ip}/control.

Category Setting Supported Range / Values Factory Default Baichuan cmd_id Web UI / API Action
Optics Optical Zoom (ZoomPos) 0 .. 28 (1X .. 5X optical) 0 295 (StartZoomFocus) Zoom slider (action: "zoom")
Optics Optical Focus (FocusPos) 0 .. 238 (plus Autofocus) Auto 295 (StartZoomFocus) Focus slider (action: "focus")
Lighting Floodlight / Spotlight State 0 (Off) or 1 (On) 0 (Off) 288 (SetWhiteLed) Spotlight: ON/OFF (action: "spotlight")
Lighting Spotlight Brightness 1 .. 100 (%) 85 288 (SetWhiteLed) Light % slider (action: "spotlight")
Audio / Alarm Security Siren Trigger 1x / Manual Off 276 (AudioAlarmPlay) Siren (1x) button (action: "siren")
Audio / Alarm Live Microphone Audio 16 kHz AAC -> 64 kbps MP3 Enabled (<audio>1</audio>) 57 (SetEnc) + RTSP Audio: Muted / LIVE button (GET /api/cameras/{ip}/audio.mp3)
Audio / Alarm Speaker Volume 0 .. 100 (%) 92 265 (SetAudioCfg) Speaker Volume slider (action: "volume")
OSD Overlays Reolink Logo Watermark 0 (Hidden) or 1 (Visible) 0 (Disabled by cam) 45 (SetOsd) Reolink Logo Watermark (action: "osd", watermark: false)
OSD Overlays Camera Name & Timestamp 0 (Off) or 1 (On) 1 (On) 45 (SetOsd) OSD Camera Name / Timestamp (action: "osd")
Sub-Stream H.264 Bitrate (640x360) 64, 128, 160, 192, 256, 384, 512 kbps 256 kbps 57 (SetEnc) Sub-Stream Bitrate (action: "encoding")
Sub-Stream H.264 Frame Rate 4, 7, 10, 15 fps 10 fps 57 (SetEnc) Sub-Stream Frame Rate (action: "encoding")
Main-Stream H.265 4K Bitrate (3840x2160) 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192 kbps 6144 kbps 57 (SetEnc) Main-Stream Bitrate (action: "encoding")
Main-Stream H.265 4K Frame Rate 2, 4, 6, 8, 10, 12, 15, 16, 18, 20, 22, 25 fps 25 fps 57 (SetEnc) Main-Stream Frame Rate (action: "encoding")
Image (ISP) Mounting / Orientation (mirror, flip) Normal, 180° Upside-Down, Flip, Mirror 0 / 0 (Normal) 25 (SetImage / SetIsp) Flip 180° button & Mounting / Orientation (action: "image", flip: true, mirror: true)
Image (ISP) Brightness (bright) 0 .. 255 128 25 (SetImage) Brightness slider (action: "image")
Image (ISP) Contrast (contrast) 0 .. 255 128 25 (SetImage) Contrast slider (action: "image")
Image (ISP) Saturation (saturation) 0 .. 255 128 25 (SetImage) Saturation (action: "image")
Image (ISP) Sharpness (sharpen) 0 .. 255 128 25 (SetImage) Sharpness slider (action: "image")
Protocols Network Ports rtsp (554), onvif (8000), rtmp (1935), http (80), https (443) 9000 + 554 36 (SetNetPort) API (action: "port")
REST API Examples for Adjusting Camera Settings
# Toggle Spotlight ON at 75% brightness on 10.0.0.122
curl -X POST http://[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000/api/cameras/10.0.0.122/control \
  -H "Content-Type: application/json" \
  -d '{"action": "spotlight", "state": true, "brightness": 75}'

# Set Optical Zoom to 14 (2.5X)
curl -X POST http://[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000/api/cameras/10.0.0.122/control \
  -H "Content-Type: application/json" \
  -d '{"action": "zoom", "value": 14}'

# Reduce on-camera H.264 sub-stream encoder to 128 kbps @ 7 fps
curl -X POST http://[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000/api/cameras/10.0.0.122/control \
  -H "Content-Type: application/json" \
  -d '{"action": "encoding", "sub_bitrate": 128, "sub_fps": 7}'

# Adjust ISP Image Brightness and Sharpness
curl -X POST http://[fd77:7777:5010:0:1337:f1ac:d00d:ca30]:8000/api/cameras/10.0.0.122/control \
  -H "Content-Type: application/json" \
  -d '{"action": "image", "bright": 135, "sharpen": 140}'

Authentik Access Control (camera_access Group)

Access to https://cam.drheck.dev is restricted exclusively to members of the camera_access group in Authentik:

  • Authorized Members: luke, ikura, haruka
  • Enforcement Layer 1 (Edge ForwardAuth PolicyBinding):
    • In Authentik, the Cameras application (slug = "cam") has an active PolicyBinding targeting group camera_access.
    • Every HTTP request to https://cam.drheck.dev triggers an Nginx auth_request /outpost.goauthentik.io/auth/nginx subrequest. Unauthenticated users are redirected to https://auth.drheck.dev, and authenticated users who are not in camera_access are denied at the proxy edge.
  • Enforcement Layer 2 (Application Defense-in-Depth):
    • Nginx forwards X-authentik-username and X-authentik-groups headers to the cam backend.
    • check_camera_access_headers() in /data/docker/cam/app.py splits X-authentik-groups on | and verifies camera_access membership on every route (/, /tv, /cam-googletv.apk, /api/*), returning HTTP 403 Forbidden if missing.
Managing camera_access Group Membership via CLI
ssh ul 'docker exec -i authentik-server ak shell << "PY"
from authentik.core.models import Group, User
grp = Group.objects.get(name="camera_access")
print("Current members:", [u.username for u in grp.users.all()])
# To add or set members:
grp.users.set(User.objects.filter(username__in=["luke", "ikura", "haruka"]))
grp.save()
PY'

Bandwidth Adjustments for Unreliable or Slow Internet

When viewing two cameras simultaneously over a slow, high-latency, or lossy internet connection (cellular tethering, hotel Wi-Fi, or rural WAN), total downstream bandwidth for $N$ cameras is governed by:

$$B_{\text{total}} = N \times f_{\text{fps}} \times S_{\text{frame}}(W, H, q) \times 8 \quad \text{bits/sec}$$

where $f_{\text{fps}}$ is the frame rate, $W \times H$ is the scaled resolution, and $q \in [2, 24]$ is the FFmpeg MJPEG quantization factor (-q:v).

cam.drheck.dev provides two independent bandwidth controls:

1. Per-Viewer Stream Presets (Top Toolbar on cam.drheck.dev & /tv)

Changing the Bandwidth / Mode dropdown in the top bar adjusts server-side scaling, frame rate decimation, and JPEG compression for your browser session without affecting other viewers:

Preset Mode Resolution Frame Rate JPEG q Avg Frame Size Per-Camera Bitrate 2-Camera Total WAN Bitrate Best For
HD Main Stream 1280x720 10 fps 5 ~25 KB ~2.0 Mbps ~4.0 Mbps Fast fiber / LAN full-screen inspection
Standard Live (Default) 640x360 10 fps 5 ~6.2 KB ~500 kbps ~1.0 Mbps Normal broadband / Wi-Fi
Low Bandwidth 480x270 5 fps 10 ~4.0 KB ~160 kbps ~320 kbps Congested Wi-Fi / mobile hotspot
Ultra-Low WAN 320x180 2 fps 15 ~3.1 KB ~50 kbps ~100 kbps Throttled cellular / international roaming
Unreliable WAN Snapshot (2s) 640x360 0.5 fps (1 / 2s) Native ~9.0 KB ~36 kbps ~72 kbps High packet loss / TCP reset prone links
Minimal WAN Snapshot (5s) 640x360 0.2 fps (1 / 5s) Native ~9.0 KB ~14 kbps ~28 kbps Extreme low-speed satellite / 2G fallback

Tip for High Packet Loss Links: Continuous multipart MJPEG streams use a single long-lived HTTP TCP response. On connections with heavy packet loss where TCP head-of-line blocking stalls long streams, switch the dropdown to Unreliable WAN Snapshot (Every 2s) or Minimal WAN Snapshot (Every 5s). Snapshot modes issue independent, stateless HTTP GET requests that recover immediately from dropped packets.

2. On-Camera Hardware H.264 Sub-Stream Bitrate Tuning

Inside each camera card's Camera Hardware Encoder, Bandwidth, Image & Protocol Settings drawer, you can also lower the camera's internal H.264 hardware encoder (SetEnc cmd_id = 57) from 256 kbps @ 10 fps down to 64 kbps @ 4 fps (64, 128, 160, 192, 256, 384, 512 kbps).


Google TV / Android TV Sideloadable App (cam-googletv.apk)

A signed, sideloadable Android TV / Google TV application (DrHeck Cameras, package dev.drheck.cam) is built and hosted directly on cam.drheck.dev at:

  • Download URL: https://cam.drheck.dev/cam-googletv.apk
  • 10-Foot TV Web Interface: https://cam.drheck.dev/tv
  • Source Directory on ul: /data/docker/cam/googletv_src/ (AndroidManifest.xml, MainActivity.smali, res/drawable/tv_banner.png, res/drawable/ic_launcher.png)

Features of the Google TV App

  1. Native Google TV Home Screen Banner (LEANBACK_LAUNCHER): Declares android.intent.category.LEANBACK_LAUNCHER and a 320x180 android:banner resource so the app appears natively in the Google TV Your Apps row.
  2. Wake-Lock (FLAG_KEEP_SCREEN_ON): Sets WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON (0x80) on the window so Google TV never dims or enters ambient screensaver mode while monitoring the cameras.
  3. Zero-Sign-In Baked-In Authentication (v2.0): Embeds a 256-bit pre-shared token (TV_AUTH_TOKEN) directly into the WebView CookieManager (cam_tv_token), User-Agent (CamTVToken/...), and request headers (X-Cam-Tv-Token), verified at both the Nginx edge and FastAPI backend (hmac.compare_digest) so the Android TV app never requires interactive sign-in.
  4. D-Pad Remote Navigation:
    • Left / Right / Up / Down: Highlight Camera 1 (10.0.0.122), Camera 2 (10.0.0.123), 2-Up Grid, Mode (cycles Standard 10fps -> Low-BW 5fps -> Ultra-Low 2fps -> HD 720p 10fps), or Spotlight Toggle.
    • Center / OK (DPAD_CENTER / Enter): Toggle the selected camera between 2-Up Grid and Full-Screen view.
    • Up / Down (while Full-Screen): Smoothly step the camera's 5X motorized optical zoom (+2 / -2).
    • Back Button: Exits full-screen camera view back to the 2-Up Grid.

How to Sideload onto Google TV

Option A: Via ADB over Wi-Fi / Ethernet (Fastest from a Laptop/Desktop)

  1. On your Google TV, open Settings -> System -> About and click Android TV OS build 7 times to enable Developer options.
  2. Open Settings -> System -> Developer options and enable USB debugging (or Wireless debugging). Note the Google TV's IP address (<googletv-ip>).
  3. Download and install the APK from your workstation:
    curl -L -o /tmp/cam-googletv.apk https://cam.drheck.dev/cam-googletv.apk
    adb connect <googletv-ip>:5555
    adb install -r /tmp/cam-googletv.apk
    

Option B: Directly on the Google TV (Using the "Downloader" App)

  1. Install Downloader by AFTVnews from the Google Play Store on your Google TV.
  2. In Settings -> Security & Restrictions (or Apps -> Special app access -> Install unknown apps), allow Downloader to install unknown apps.
  3. Open Downloader, enter https://cam.drheck.dev, sign in with your Authentik account (luke, ikura, or haruka), and click the Google TV App (.apk) button in the top-right toolbar to download and install cam-googletv.apk.