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:

    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. 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. 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:

          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

            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. 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:
                  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. 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:
                    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:

                      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;
                        }
                        
                        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 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:

                                  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

                                          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. 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:
                                            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)

                                              On your Google TV, open Settings -> System -> About and click Android TV OS build 7 times to enable Developer options. Open Settings -> System -> Developer options and enable USB debugging (or Wireless debugging). Note the Google TV's IP address (<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)

                                                Install Downloader by AFTVnews from the Google Play Store on your Google TV. In Settings -> Security & Restrictions (or Apps -> Special app access -> Install unknown apps), allow Downloader to install unknown apps. 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.