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
Two Reolink RLC-811A (P430) 4K 8MP PoE Spotlight IP cameras are connected to the 10.0.0.0/24 LAN on interface eth77 of host ul:
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)
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:
0xf0debc0a, followed by cmd_id (uint32), payload length (uint32), channel_id (uint8), status code (200 OK, 400 Bad Request), and payload offset.
Modern Nonce Authentication (cmd_id = 1):
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.
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 = 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:
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.
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
discover_cameras):
/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.
Hybrid Instant-Snapshot + FFmpeg MJPEG Streaming (mjpeg_stream_generator):
<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:
<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.
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 -
Stage 3 (Automatic Fallback): If the RTSP socket ever drops, the generator transparently falls back to streaming Baichuan JPEG snapshots at the requested interval.
Integer State Normalization for Spotlight Control:
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:
/etc/dhcpd.conf on danknasty (Recommended):
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;
}
On-Camera Static IP via Baichuan cmd_id = 75 (SetLocalLink):
<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?
RLC-811A Firmware v3.1.0.4695_2504301440): No native IPv6 support.
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.
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.
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
Speaker Volume
0 .. 100 (%)
92
265 (SetAudioCfg)
Speaker Volume slider (action: "volume")
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)
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:
luke, ikura, haruka
Enforcement Layer 1 (Edge ForwardAuth PolicyBinding):
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):
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:
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:
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
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.
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.
Persistent Authentik SSO Cookie Jar: Enables CookieManager persistence (setAcceptCookie(true) and setAcceptThirdPartyCookies(true)) so you only log in to Authentik once (luke, ikura, or haruka in camera_access).
D-Pad Remote Navigation:
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)
<googletv-ip>).
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)
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.