From 94e343e8a73dda66691ae7c018332df95c55de4b Mon Sep 17 00:00:00 2001 From: janost Date: Thu, 19 Mar 2026 14:56:48 +0100 Subject: [PATCH 1/3] docs: rewrite README for janost/neolink fork Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 646 +++++++++++------------------------------------------- 1 file changed, 133 insertions(+), 513 deletions(-) diff --git a/README.md b/README.md index 316f653c..ffe0cedf 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # Neolink -![CI](https://github.com/QuantumEntangledAndy/neolink/workflows/CI/badge.svg) -[![dependency status](https://deps.rs/repo/github/QuantumEntangledAndy/neolink/status.svg)](https://deps.rs/repo/github/QuantumEntangledAndy/neolink) +![Docker GHCR](https://github.com/janost/neolink/actions/workflows/docker-ghcr.yml/badge.svg) +[![dependency status](https://deps.rs/repo/github/janost/neolink/status.svg)](https://deps.rs/repo/github/janost/neolink) Neolink is a small program that acts as a proxy between Reolink IP cameras and normal RTSP clients. Certain cameras, such as the Reolink B800, do not implement ONVIF or RTSP, but instead use a proprietary "Baichuan" protocol only compatible with their apps and NVRs (any camera that uses "port 9000" will likely be using this protocol). -Neolink allows you to use NVR software such as Shinobi or Blue Iris to receive -video from these cameras instead. +Neolink allows you to use NVR software such as Blue Iris, Frigate, or Shinobi to +receive video from these cameras instead. The Reolink NVR is not required, and the cameras are unmodified. Your NVR software connects to Neolink, which forwards the video stream from the camera. @@ -19,603 +19,223 @@ does has been reverse engineered. ## This Fork -This fork is an extension of -[thirtythreeforty's](https://github.com/thirtythreeforty/neolink) with additional -features not yet in upstream master. - -**Major Features**: - -- MQTT -- Motion Detection -- Paused Streams (when no rtsp client or no motion detected) -- Save a still image to disk - -**Minor Features**: - -- Improved error messages when missing gstreamer plugins -- Protocol more closely follows official reolink format - - Possibly can handle more simulatenous connections -- More ways to connect to the camera. Including Relaying through reolink - servers -- Camera battery levels can be displayed in the log +This is a maintained fork of +[QuantumEntangledAndy/neolink](https://github.com/QuantumEntangledAndy/neolink), +which itself was a fork of +[thirtythreeforty/neolink](https://github.com/thirtythreeforty/neolink). + +**Changes in this fork:** + +- Low-latency RTSP mode (`enable_low_latency`) for reduced stream delay +- Stability fixes: timestamp overflow, memory/FD leaks, pipeline sharing, + keepalive backpressure +- Docker images published to GHCR instead of Docker Hub +- Updated dependencies and Debian Trixie base image +- Disk management, SD card replay/download, and alarm search +- Video encoding/compression settings subcommand +- Audio can be disabled per camera (`enable_audio`) + +**Inherited features from upstream:** + +- MQTT with Home Assistant discovery +- Motion detection +- Paused streams (when no RTSP client or no motion detected) +- Save still images to disk +- Battery camera support with idle disconnect +- PTZ control, PIR, floodlight, siren, status LED +- Multiple discovery methods (local, remote, map, relay, cellular) +- TLS encryption for RTSP ## Installation -Download from the -[release page](https://github.com/QuantumEntangledAndy/neolink/releases) - -Extract the zip +Docker is the preferred way to run Neolink. Binary releases are not currently +provided. -Install the latest [gstreamer](https://gstreamer.freedesktop.org/download/) -(1.20.5 as of writing this). +### Docker (recommended) -- **Windows**: ensure you install `full` when prompted in the MSI options. -- **Mac**: Install the dpkg version on the official gstreamer website over - the brew version -- **Ubuntu/Debian**: These packages should work +Images are published to GHCR on every push to `master` and on tags: ```bash -sudo apt install \ - libgstrtspserver-1.0-0 \ - libgstreamer1.0-0 \ - libgstreamer-plugins-bad1.0-0 \ - gstreamer1.0-x \ - gstreamer1.0-plugins-base \ - gstreamer1.0-plugins-good \ - gstreamer1.0-plugins-bad \ - libssl -``` - -- **Windows**: You may also need to - [install openssl](https://wiki.openssl.org/index.php/Binaries) -- **Macos**: You may also need to - [install openssl](https://wiki.openssl.org/index.php/Binaries) or - `brew install openssl@1.1` -- **Ubuntu/Debian**: Install the `libssl` package - -Make a config file see below. - -## Config/Usage - -### RTSP - -To use `neolink` you need a config file. - -There's a more complete example -[here](https://github.com/QuantumEntangledAndy/neolink/blob/master/sample_config.toml), -but the following should work as a minimal example. - -```toml -bind = "0.0.0.0" - -[[cameras]] -name = "Camera01" -username = "admin" -password = "password" -uid = "ABCDEF0123456789" - -[[cameras]] -name = "Camera02" -username = "admin" -password = "password" -uid = "BCDEF0123456789A" -address = "192.168.1.10" +docker pull ghcr.io/janost/neolink:dev # latest master +docker pull ghcr.io/janost/neolink:latest # latest tagged release ``` -Create a text file called `neolink.toml` in the same folder as the -neolink binary. With your config options. - -When ready start `neolink` with the following command -using the terminal in the same folder the neolink binary is in. +Run with: ```bash -./neolink rtsp --config=neolink.toml -``` - -### Discovery - -To connect to a camera using a UID we need to find the IP address of the camera -with that UID - -The IP is discovered with four methods - -1. Local discovery: Here we send a broadcast on all visible networks asking - the local network if there is a camera with this UID. This only works if - the network supports broadcasts - - If you know the ip address you can put it into the `address` field of the - config and attempt a direct connection without broadcasts. This requires a - route from neolink to the camera. - -2. Remote discovery: Here we ask the reolink servers what the IP address is. - This requires that we contact reolink and provide some basic information - like the UID. Once we have this information we connect directly to the - local IP address. This requires a route from neolink to the camera and - for the camera to be able to contact reolink. - -3. Map discovery: In this case we register our IP address with reolink and ask - the camera to connect to us. Once the camera either polls/recieves a connect - request from the reolink servers the camera will initiate a connection - to neolink. This requires that our IP and reolink are reachable from - the camera. - -4. Relay: In this case we request that reolink relay our connection. Neolink - nor the camera need to be able to direcly contact each other. But both - neolink and the camera need to be able to contact reolink. - -This can be controlled with the config - -```toml -discovery = "local" +docker run \ + --network host \ + --volume=$PWD/neolink.toml:/etc/neolink.toml \ + ghcr.io/janost/neolink ``` -In the `[[cameras]]` section of the toml. - -Possible values are `local`, `remote`, `map`, `relay` later values implictly -enable prior methods. +> `--network host` is only needed for local broadcast discovery. If you connect +> via IP address, normal bridge mode works fine. macOS does not support +> `--network host`. -#### Cellular +**Environment variables:** -Cellular cameras should select `"cellular"` which only enables `map` and -`relay` since `local` and `remote` will always fail +| Variable | Default | Description | +|---|---|---| +| `NEO_LINK_MODE` | `rtsp` | Mode: `rtsp`, `mqtt`, or `mqtt-rtsp` | +| `NEO_LINK_PORT` | `8554` | RTSP listen port | -```toml -discovery = "cellular" -``` +### Building from source -See the sample config file for more details. - -### MQTT - -To use mqtt you will need to adjust your config file as such: - -```toml -bind = "0.0.0.0" - -[mqtt] -broker_addr = "127.0.0.1" # Address of the mqtt server -port = 1883 # mqtt servers port -credentials = ["username", "password"] # mqtt server login details - -[[cameras]] -name = "Camera01" -username = "admin" -password = "password" -uid = "ABCDEF0123456789" -``` - -Then to start the mqtt+rtsp connection run the following: +Requires Rust and GStreamer development libraries: ```bash -./neolink mqtt-rtsp --config=neolink.toml -``` - -OR for only mqtt - -```bash -./neolink mqtt --config=neolink.toml -``` - -Neolink will publish these messages: - -Messages that are prefixed with `neolink/` - -- `/status` Tracks the connection of neolink, `connected` for ready `offline` - for not ready this is a LastWill message -- `/config` The configuration file used to start neolink, you can publish to - this to **temporarily** alter the live configuration -- `/config/status` If you publish to `/config` then any errors from your - publish config will show here, or `Ok(())` if no errors and finished loading - -Messages that are prefixed with `neolink/{CAMERANAME}` - -Control messages: - -- `/control/led [on|off]` Turns status LED on/off -- `/control/ir [on|off|auto]` Turn IR lights on/off or automatically via light - detection -- `/control/reboot` Reboot the camera -- `/control/ptz [up|down|left|right|in|out] (amount)` Control the PTZ - movements, amount defaults to 32.0 -- `/control/ptz/preset [id]` Move the camera to a PTZ preset -- `/control/ptz/assign [id] [name]` Set the current PTZ position to a preset ID - and name -- `/control/zoom (amount)` Zoom the camera to the specified amount. Example: 1.0 - for normal and 3.5 for 3.5x zoom factor. This only works on cameras that support - zoom -- `/control/pir [on|off]` -- `/control/floodlight [on|off]` Turns floodlight (if equipped) on/off -- `/control/floodlight_tasks [on|off]` Turns floodlight (if equipped) tasks on/off - This is the automatic tasks such as on motion and night triggers -- `/control/wakeup (mins)` For cameras that are using `idle_disconnect` this will - force a wakeup for at least the given minutes -- `/control/siren on` Signal the siren, the message is always "on" as there is no - "off" signal for the siren - -Status Messages: - -- `/status disconnected` Sent when the camera goes offline -- `/status/battery` Sent in reply to a `/query/battery` an XML encoded version - of the battery status -- `/status/battery_level` A simple % value of current battery level, only - published when `enable_battery` is true in the config -- `/status/pir` Sent in reply to a `/query/pir` an XML encoded version of the - pir status -- `/status/motion` Contains the motion detection alarm status. `on` for motion - and `off` for still, only published when `enable_moton` is true in the config -- `/status/ptz/preset` Sent in reply to a `/query/ptz/preset` an XML encoded - version of the PTZ presets -- `/status/preview` a base64 encoded camera image updated every 2s. Not - every camera supports the snapshot command needed for this. In such cases - there will be no `/status/preview` message. Only published when - `enable_preview` is true in the config -- `/status/floodlight_tasks` The current status of the floodlight tasks - used updated every 2s by default - -Query Messages: - -- `/query/battery` Request that the camera reports its battery level -- `/query/pir` Request that the camera reports its pir status -- `/query/ptz/preset` Request that the camera reports its PTZ presets -- `/query/preview` Request that the camera post a base64 encoded jpeg - of the stream to `/status/preview` now, ignoring the timer - -### Controlling RTSP from MQTT - -If neolink is started with `mqtt-rtsp` then the `/neolink/config` can be used -to control the RTSP - -Changes made to the config by publishing to `/neolink/config` should be -reflected in the rtsp - -These include changing the: - -- Available users +# Ubuntu/Debian +sudo apt install \ + build-essential libssl-dev ca-certificates \ + libgstrtspserver-1.0-dev libgstreamer1.0-dev \ + libglib2.0-dev protobuf-compiler -```toml -[[users]] - name = "me" - pass = "mepass" +cargo build --release ``` -- Permitted users on a camera - -```toml -[[cameras]] - permitted_users = [ "me" ] -``` - -- Available streams - -```toml -[[cameras]] - stream = "Main" -``` +## Configuration -Setting a value of `None` will disable the stream +Create a `neolink.toml` file. See +[sample_config.toml](https://github.com/janost/neolink/blob/master/sample_config.toml) +for all options. -- Disable the entire camera (mqtt updates and all) - -```toml -[[cameras]] - enabled = false -``` - -### MQTT Disable Features - -Certain features like preview and motion detection may not be desired -you can disable them with the following config options. -Disabling these may help to conserve battery +Minimal example: ```toml bind = "0.0.0.0" -[mqtt] -broker_addr = "127.0.0.1" # Address of the mqtt server -port = 1883 # mqtt servers port -credentials = ["username", "password"] # mqtt server login details - [[cameras]] name = "Camera01" username = "admin" password = "password" uid = "ABCDEF0123456789" -[cameras.mqtt] -enable_motion = false # motion detection - # (limited battery drain since it - # is a passive listening connection) - # -enable_light = false # flood lights only available on some camera - # (limited battery drain since it - # is a passive listening connection) - # -enable_battery = false # battery updates in `/status/battery_level` - # -enable_preview = false # preview image in `/status/preview` - # -enable_floodlight = false # preview image in `/status/floodlight_tasks` - # -battery_update = 2000 # Number of ms between `/status/battery_level` updates - # -preview_update = 2000 # Number of ms between `/status/preview` updates - # -floodlight_update = 2000 # Number of ms between `/status/floodlight_tasks` updates -``` - -#### MQTT Discovery -[MQTT Discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) -is partially supported. Currently, discovery is opt-in and camera features -must be manually specified. - -```toml -[cameras.mqtt] - # - [cameras.mqtt.discovery] - topic = "homeassistant" - features = ["floodlight"] -``` - -Available features are: - -- `floodlight`: This adds a light control to home assistant -- `camera`: This adds a camera preview to home assistant. It is only updated - every 0.5s and cannot be much more than that since it is updated over mqtt - not over RTSP. Not every camera supports the snapshot command needed for - this. In such cases there will be no `/status/preview` message. -- `led`: This adds a switch to chage the LED status light on/off to home - assistant -- `ir`: This adds a selection switch to chage the IR light on/off/auto to home - assistant -- `motion`: This adds a motion detection binary sensor to home assistant -- `reboot`: This adds a reboot button to home assistant -- `pt`: This adds a selection of buttons to control the pan and tilt of the - camera -- `battery`: This adds a battery level sensor to home assistant -- `siren`: Adds a siren button to home assistant - -### Extra Camera Settings - -Listed below are extra camera settings: - -```toml [[cameras]] -name = "Camera01" +name = "Camera02" username = "admin" password = "password" -uid = "ABCDEF0123456789" -debug = false # Displays Debug XML messages from camera -enabled = true # Enable or Disable the camera -update_time = false # When camera connects, force the setting of the camera date/time to now. The default is false -print_format = "None" # Type of format that logs are displayed in (None, Human, Xml). The default is None +address = "192.168.1.10:9000" ``` -- **Debug:** Will dump the various XMLs from the camera as they are recieved -and decrypted. Leave this off unless asked for it to fix an issue. - -- **Enabled:** Useful if you want to remove a camera from rtsp without deleting -it from the config - -- **update_time:** Used to FORCE an update on the camera time. Usually it checks -if it is needed but this -will force it regardless. (Mostly this was introduced to address a specific -ssue a user had) - -- **print_format:** Used for adjusting printing of some values mostly, battery -messages - -### Pause - -To use the pause feature you will need to adjust your config file as such: +Start the RTSP server: -```toml -bind = "0.0.0.0" - -[[cameras]] -name = "Camera01" -username = "admin" -password = "password" -uid = "ABCDEF0123456789" - [cameras.pause] - on_motion = true # Should pause when no motion - on_client = true # Should pause when no rtsp client - timeout = 2.1 # How long to wait after motion stops before pausing +```bash +neolink rtsp --config=neolink.toml ``` -Then start the rtsp server as usual: +Connect your NVR or RTSP client to: -```bash -./neolink rtsp --config=neolink.toml +``` +rtsp://HOST:8554/CameraName ``` -### Idle Disconnects - -To really save battery we need to disconnect the camera when it is idle. +Where "CameraName" matches the `name` field in your config. -To acheieve this you can add `idle_disconnect = true` to the `[[cameras]]` -section +### Per-camera options ```toml -bind = "0.0.0.0" - [[cameras]] name = "Camera01" username = "admin" password = "password" -uid = "ABCDEF0123456789" -idle_disconnect = true -[cameras.pause] - on_client = true # Should pause when no rtsp client - timeout = 2.1 # How long to wait after motion stops before pausing -``` - -When `idle_disconnect = true` neolink will disconnect from the camera 30s -after it stops being used. - -Neolink considers it as being used if there is an active stream running, or -if there is motion being detected or an mqtt command being run - -[Because google remove the api for the push notifications we cannot -reliably use push notifications to wake up, so motion won't wake -up neolink anymore] - -You can make neolink stop active streams when there are no rtsp clients using - -```toml -[cameras.pause] - on_client = true # Should pause when no rtsp client -``` - -Once in the disconnected state. Neolink will stay disconnected until there is a -new requested activation such as a client connecting or an mqtt command - -~Neolink will also wake up on push notifications from the camera. These are usually -sent by the camera on motion or PIR alarms. To disable this you can set -`push_notifications = false` in the `[[cameras]]` config~ - -[Google removed the apis we were using for push notifications] - -### Docker - -[Docker](https://hub.docker.com/r/quantumentangledandy/neolink) builds are also -provided in multiple architectures. The latest tag tracks master while each -branch gets it's own tag. - -```bash -docker pull quantumentangledandy/neolink - -# Add `-e "RUST_LOG=debug"` to run with debug logs -# -# --network host is only needed if you require to connect -# via local broadcasts. If you can connect via any other -# method then normal bridge mode should work fine -# and you can ommit this option. Not all OSes support -# network=host, notably macos lacks this option. -docker run --network host --volume=$PWD/config.toml:/etc/neolink.toml quantumentangledandy/neolink +address = "192.168.1.10:9000" +stream = "mainStream" # "mainStream", "subStream", or "both" (default) +enable_audio = true # disable to save CPU (default: true) +enable_low_latency = false # reduce stream delay (default: false) +discovery = "local" # "local", "remote", "map", "relay", "cellular" +idle_disconnect = false # disconnect when idle to save battery +debug = false # dump raw XML from camera ``` -#### Environmental Variables - -There are currently 2 environmental variables available as part of the container: - -- `NEO_LINK_MODE`: defaults to `"rtsp"` if not set, other options are "mqtt" or "mqtt-rtsp". -- `NEO_LINK_PORT`: defaults to `8554`, set this to your required port value. - -### Image - -You can write an image from the stream to disk using: - -```bash -neolink image --config=config.toml --file-path=filepath CameraName -``` +### Discovery -Where filepath is the path to save the image to and CameraName is the name of -the camera from the config to save the image from. +When connecting by UID, the camera IP is discovered with these methods (each +implicitly enables prior methods): -File is always jpeg and the extension given in filepath will be added or changed -to reflect this. +1. **local** — UDP broadcast on local network +2. **remote** — Ask Reolink servers for the IP, then connect directly +3. **map** — Register our IP with Reolink, camera connects to us +4. **relay** — Reolink relays the connection (neither side needs direct access) -Some cameras do not support the SNAP command that is used to generate the image -on the camera. If this is the case with your camera you can try the -`--use-stream` option which will instead create a jpeg by transcoding the video -stream. +Use `discovery = "cellular"` for cellular cameras (skips local/remote). -### Battery Levels +If you know the IP, set `address` directly and skip discovery entirely. -You can get the battery level and status using +### MQTT -```bash -neolink battery --config=config.toml CameraName +```toml +[mqtt] +broker_addr = "127.0.0.1" +port = 1883 +credentials = ["username", "password"] ``` -This will produce an xml formatted battery status on stdout for processing - -### PIR - -You can control pir using +Start with MQTT + RTSP: ```bash -neolink pir --config=config.toml CameraName [on|off] +neolink mqtt-rtsp --config=neolink.toml ``` -This will turn the PIR on or off - -### Reboot - -You can reboot a camera using +Or MQTT only: ```bash -neolink reboot --config=config.toml CameraName +neolink mqtt --config=neolink.toml ``` -### Status LED +#### Messages -You can control the status LED using +All messages are prefixed with `neolink/` or `neolink/{CAMERA_NAME}/`. -```bash -neolink status-light --config=config.toml CameraName [on|off] -``` - -### Talk +**Control** (`/control/...`): `led`, `ir`, `reboot`, `ptz`, `zoom`, `pir`, +`floodlight`, `floodlight_tasks`, `wakeup`, `siren` -You can talk over the camera using +**Status** (`/status/...`): `battery`, `battery_level`, `motion`, `pir`, +`ptz/preset`, `preview`, `floodlight_tasks` -```bash -neolink talk --config=config.toml --adpcm-file=data.adpc\ - --sample-rate=16000 --block-size=512 CameraName -``` +**Query** (`/query/...`): `battery`, `pir`, `ptz/preset`, `preview` -Where the sounds is ADPCM encoded +See the +[sample config](https://github.com/janost/neolink/blob/master/sample_config.toml) +for per-camera MQTT options (`enable_motion`, `enable_preview`, +`enable_battery`, etc.). -or +#### Home Assistant Discovery -```bash -neolink talk --config=config.toml --microphone CameraName +```toml +[cameras.mqtt.discovery] +topic = "homeassistant" +features = ["floodlight", "camera", "led", "ir", "motion", "reboot", "pt", "battery", "siren"] ``` -Which uses the default microphone which depends on -[gstreamer](https://gstreamer.freedesktop.org/documentation/autodetect/autoaudiosrc.html?gi-language=c#autoaudiosrc-page) - -### PTZ - -You can control the PTZ using +### Pause & Idle Disconnect -```bash -neolink ptz --config=config.toml CameraName control 32 [left|right|up|down|in|out] +```toml +[cameras.pause] +on_motion = true # pause when no motion +on_client = true # pause when no RTSP client +timeout = 2.1 # seconds after motion stops before pausing ``` -Where 32 is the speed. Not all cameras support speed - -Some cameras also support preset positions - -```bash -# Print the list of preset positions -neolink ptz --config=config.toml CameraName preset -# Move the camera to preset ID 0 -neolink ptz --config=config.toml CameraName preset 0 -# Save the current position as preset ID 0 with name PresetName -neolink ptz --config=config.toml CameraName assign 0 PresetName -``` +Add `idle_disconnect = true` to fully disconnect battery cameras when idle. -To change the zoom level use the following: +### Other Commands ```bash -# Zoom the camera to 2.5x -neolink ptz --config=config.toml CameraName zoom 2.5 +neolink image --config=neolink.toml --file-path=snap.jpg CameraName +neolink battery --config=neolink.toml CameraName +neolink pir --config=neolink.toml CameraName [on|off] +neolink reboot --config=neolink.toml CameraName +neolink status-light --config=neolink.toml CameraName [on|off] +neolink ptz --config=neolink.toml CameraName control 32 [left|right|up|down|in|out] +neolink ptz --config=neolink.toml CameraName preset [id] +neolink ptz --config=neolink.toml CameraName zoom 2.5 +neolink talk --config=neolink.toml --microphone CameraName +neolink encoding --config=neolink.toml CameraName +neolink disk --config=neolink.toml CameraName ``` -With 1.0 being normal and 2.5 being 2.5x zoom - ## License Neolink is free software, released under the GNU Affero General Public License v3. - -This means that if you incorporate it into a piece of software available over -the network, you must offer that software's source code to your users. - -## Donations - -If you find this code helpful please consider supporting development. - -[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/G2G5HOYIZ) From b71e723bd30e630755a5aa7a8d9b2ebe4682cb33 Mon Sep 17 00:00:00 2001 From: janost Date: Thu, 19 Mar 2026 15:06:08 +0100 Subject: [PATCH 2/3] docs: credit community contributors for ported upstream PRs Add a section crediting @wafgo, @Maaggs, @fromagge, @lorek123, and @joshkautz for their unmerged upstream PRs (#373, #389, #394, #395, #396, #398, #399, #400) that were ported into this fork. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ffe0cedf..3108938f 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,25 @@ This is a maintained fork of which itself was a fork of [thirtythreeforty/neolink](https://github.com/thirtythreeforty/neolink). -**Changes in this fork:** +This fork incorporates community PRs that were submitted to upstream but never +merged, plus additional improvements: -- Low-latency RTSP mode (`enable_low_latency`) for reduced stream delay -- Stability fixes: timestamp overflow, memory/FD leaks, pipeline sharing, - keepalive backpressure +**Ported community contributions:** + +- Buffer pool bucketing to prevent FD/memory leaks — [@wafgo](https://github.com/wafgo) ([#373](https://github.com/QuantumEntangledAndy/neolink/pull/373)) +- Wireshark dissector fix for deprecated bit32 — [@Maaggs](https://github.com/Maaggs) ([#389](https://github.com/QuantumEntangledAndy/neolink/pull/389)) +- `enable_audio` and `enable_low_latency` config options — [@fromagge](https://github.com/fromagge) ([#394](https://github.com/QuantumEntangledAndy/neolink/pull/394)) +- Encoding subcommand for video compression settings — [@lorek123](https://github.com/lorek123) ([#395](https://github.com/QuantumEntangledAndy/neolink/pull/395)) +- Disk management, SD card replay/download, and alarm search — [@lorek123](https://github.com/lorek123) ([#396](https://github.com/QuantumEntangledAndy/neolink/pull/396)) +- Timestamp overflow fix (u64) preventing corruption after ~71 min — [@joshkautz](https://github.com/joshkautz) ([#398](https://github.com/QuantumEntangledAndy/neolink/pull/398)) +- Channel backpressure fix preventing keepalive timeouts — [@joshkautz](https://github.com/joshkautz) ([#399](https://github.com/QuantumEntangledAndy/neolink/pull/399)) +- Pipeline sharing to prevent per-client resource exhaustion — [@joshkautz](https://github.com/joshkautz) ([#400](https://github.com/QuantumEntangledAndy/neolink/pull/400)) + +**Additional changes:** + +- Low-latency RTSP mode with frame draining, live-mode AppSrc, and reduced buffers - Docker images published to GHCR instead of Docker Hub - Updated dependencies and Debian Trixie base image -- Disk management, SD card replay/download, and alarm search -- Video encoding/compression settings subcommand -- Audio can be disabled per camera (`enable_audio`) **Inherited features from upstream:** From 340c07cc8c601f041ed15b7812df1757864fdb19 Mon Sep 17 00:00:00 2001 From: janost Date: Thu, 19 Mar 2026 15:12:49 +0100 Subject: [PATCH 3/3] docs: add detailed docs, restore donations, skip CI on docs changes - Create docs/mqtt.md with full MQTT message reference, HA discovery, and runtime control via MQTT - Create docs/commands.md with detailed CLI subcommand reference including talk, PTZ presets, encoding, and disk management - Create docs/configuration.md with full config reference including TLS, users, discovery details, pause/idle disconnect - Add documentation links section to README - Restore donations section with note that upstream is unmaintained - Add paths-ignore for *.md and docs/** to both Docker workflows Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/docker-ghcr.yml | 3 + .github/workflows/docker-pr.yml | 3 + README.md | 21 +++++ docs/commands.md | 115 +++++++++++++++++++++++ docs/configuration.md | 114 +++++++++++++++++++++++ docs/mqtt.md | 150 ++++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+) create mode 100644 docs/commands.md create mode 100644 docs/configuration.md create mode 100644 docs/mqtt.md diff --git a/.github/workflows/docker-ghcr.yml b/.github/workflows/docker-ghcr.yml index a6530bf1..eafb0037 100644 --- a/.github/workflows/docker-ghcr.yml +++ b/.github/workflows/docker-ghcr.yml @@ -4,6 +4,9 @@ on: push: branches: [master] tags: ["v*", "*.*.*"] + paths-ignore: + - "*.md" + - "docs/**" concurrency: group: docker-ghcr-${{ github.ref }} diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index e95ee51a..17905f34 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -3,6 +3,9 @@ name: Docker PR Build on: pull_request: types: [opened, synchronize, labeled] + paths-ignore: + - "*.md" + - "docs/**" concurrency: group: docker-pr-${{ github.event.pull_request.number }} diff --git a/README.md b/README.md index 3108938f..e5fdf675 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,17 @@ sudo apt install \ cargo build --release ``` +## Documentation + +Detailed docs are in the [`docs/`](docs/) directory: + +- [Configuration Reference](docs/configuration.md) — all config options, discovery, TLS, users, pause/idle +- [MQTT](docs/mqtt.md) — full message reference, Home Assistant discovery, runtime control +- [CLI Commands](docs/commands.md) — image capture, PTZ, talk, encoding, disk management +- [Setting Up with Blue Iris](docs/Setting%20Up%20Neolink%20For%20Use%20With%20Blue%20Iris.md) +- [Unix Service Setup](docs/unix_service.md) +- [Unix Build Setup](docs/unix_setup.md) + ## Configuration Create a `neolink.toml` file. See @@ -248,3 +259,13 @@ neolink disk --config=neolink.toml CameraName Neolink is free software, released under the GNU Affero General Public License v3. + +## Donations + +The original upstream project was created by +[@thirtythreeforty](https://github.com/thirtythreeforty) and maintained by +[@QuantumEntangledAndy](https://github.com/QuantumEntangledAndy). If you'd like +to support the original author's work (note: the upstream repo is currently +unmaintained): + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/G2G5HOYIZ) diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..ff311eba --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,115 @@ +# CLI Commands + +All commands require `--config=neolink.toml` and a camera name matching the +`name` field in your config. + +## RTSP Server + +```bash +neolink rtsp --config=neolink.toml +``` + +Starts the RTSP proxy. Connect clients to `rtsp://HOST:8554/CameraName`. + +## Image Capture + +```bash +neolink image --config=neolink.toml --file-path=snap.jpg CameraName +``` + +Saves a JPEG snapshot to disk. The `.jpg` extension is added or corrected +automatically. + +Some cameras do not support the SNAP command. Use `--use-stream` to capture +a frame by transcoding the video stream instead. + +## Battery + +```bash +neolink battery --config=neolink.toml CameraName +``` + +Prints XML-formatted battery status to stdout. + +## PIR + +```bash +neolink pir --config=neolink.toml CameraName [on|off] +``` + +Enables or disables the PIR sensor. + +## Reboot + +```bash +neolink reboot --config=neolink.toml CameraName +``` + +Reboots the camera. + +## Status LED + +```bash +neolink status-light --config=neolink.toml CameraName [on|off] +``` + +Toggles the camera's status LED. + +## Talk + +Send audio to the camera speaker. + +From a file (ADPCM encoded): + +```bash +neolink talk --config=neolink.toml \ + --adpcm-file=data.adpc \ + --sample-rate=16000 \ + --block-size=512 \ + CameraName +``` + +From microphone (uses +[GStreamer autoaudiosrc](https://gstreamer.freedesktop.org/documentation/autodetect/autoaudiosrc.html)): + +```bash +neolink talk --config=neolink.toml --microphone CameraName +``` + +## PTZ + +Control pan, tilt, and zoom: + +```bash +# Move with speed 32 (not all cameras support speed) +neolink ptz --config=neolink.toml CameraName control 32 [left|right|up|down|in|out] + +# List preset positions +neolink ptz --config=neolink.toml CameraName preset + +# Move to preset ID 0 +neolink ptz --config=neolink.toml CameraName preset 0 + +# Save current position as preset ID 0 with name "Driveway" +neolink ptz --config=neolink.toml CameraName assign 0 Driveway + +# Zoom to 2.5x (1.0 = normal) +neolink ptz --config=neolink.toml CameraName zoom 2.5 +``` + +## Encoding + +View or modify per-stream video encoding settings (bitrate, fps, rate control, +encoder profile): + +```bash +neolink encoding --config=neolink.toml CameraName +``` + +## Disk Management + +List and manage SD cards, replay recorded video, and search alarm events: + +```bash +neolink disk --config=neolink.toml CameraName +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..d4039eb3 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,114 @@ +# Configuration Reference + +Neolink is configured with a TOML file. See +[sample_config.toml](https://github.com/janost/neolink/blob/master/sample_config.toml) +for a complete example. + +## Global Settings + +```toml +bind = "0.0.0.0" # listen address (0.0.0.0 = all interfaces) +bind_port = 8554 # RTSP port (default: 8554) +``` + +### TLS + +```toml +certificate = "/path/to/cert-and-key.pem" # enables rtsps:// +tls_client_auth = "none" # none | requested | required +``` + +### Users + +Password-protect RTSP streams: + +```toml +[[users]] +name = "me" +pass = "mepass" +``` + +Access with `rtsp://me:mepass@HOST:8554/CameraName`. If no `[[users]]` are +defined, anyone can connect without authentication. + +## Camera Settings + +```toml +[[cameras]] +name = "Camera01" +username = "admin" +password = "password" + +# Connection: use address OR uid (not both) +address = "192.168.1.10:9000" # direct IP connection +uid = "ABCDEF0123456789" # UID-based discovery + +# Stream selection +stream = "both" # "mainStream", "subStream", or "both" (default) +permitted_users = ["me"] # restrict to specific users (default: all) +enabled = true # disable without removing from config + +# Audio & latency +enable_audio = true # set false to save CPU (default: true) +enable_low_latency = false # reduce stream delay (default: false) + +# Diagnostics +debug = false # dump raw XML from camera +print_format = "None" # "None", "Human", or "Xml" for status messages +update_time = false # force camera clock sync on connect + +# NVR channel (for multi-camera NVRs) +channel_id = 0 # 0-indexed camera on NVR +``` + +## Discovery + +When connecting by UID, the camera IP is discovered with these methods. Each +method implicitly enables all prior methods. + +| Method | How it works | Requirements | +|---|---|---| +| `local` | UDP broadcast on local network | Network supports broadcasts | +| `remote` | Ask Reolink servers for camera IP, connect directly | Route to camera + camera can reach Reolink | +| `map` | Register our IP with Reolink, camera connects to us | Our IP and Reolink reachable from camera | +| `relay` | Reolink relays all traffic | Both sides can reach Reolink | +| `cellular` | Alias for map + relay only | For cellular cameras (skips local/remote) | + +```toml +discovery = "local" # default; use "cellular" for cellular cameras +``` + +If you know the camera's IP address, set `address` directly and skip discovery +entirely. + +## Pause & Idle Disconnect + +Pause the stream when not needed to reduce bandwidth and camera load: + +```toml +[cameras.pause] +on_motion = true # pause when no motion detected +on_client = true # pause when no RTSP client connected +timeout = 2.1 # seconds to wait after motion stops before pausing +``` + +### Battery Camera Idle Disconnect + +For battery cameras, fully disconnect when idle to conserve power: + +```toml +[[cameras]] +idle_disconnect = true # disconnect 30s after last activity +``` + +Activity includes: active streams, motion detection, or MQTT commands. + +The camera reconnects when a new RTSP client connects or an MQTT command is +received. + +> **Note:** Push notification wakeup is no longer available (Google removed the +> required APIs). + +## MQTT + +See [mqtt.md](mqtt.md) for full MQTT configuration and message reference. diff --git a/docs/mqtt.md b/docs/mqtt.md new file mode 100644 index 00000000..6e69dba9 --- /dev/null +++ b/docs/mqtt.md @@ -0,0 +1,150 @@ +# MQTT + +Neolink can publish camera status and receive control commands over MQTT, +with optional Home Assistant discovery. + +## Configuration + +```toml +bind = "0.0.0.0" + +[mqtt] +broker_addr = "127.0.0.1" +port = 1883 +credentials = ["username", "password"] + +[[cameras]] +name = "Camera01" +username = "admin" +password = "password" +uid = "ABCDEF0123456789" +``` + +Start with MQTT + RTSP: + +```bash +neolink mqtt-rtsp --config=neolink.toml +``` + +Or MQTT only: + +```bash +neolink mqtt --config=neolink.toml +``` + +## Global Messages + +Prefixed with `neolink/`: + +| Topic | Description | +|---|---| +| `/status` | Connection status: `connected` or `offline` (LastWill) | +| `/config` | Publish here to **temporarily** alter the live configuration | +| `/config/status` | Result of a `/config` publish: errors or `Ok(())` | + +## Per-Camera Messages + +Prefixed with `neolink/{CAMERA_NAME}`. + +### Control Messages + +| Topic | Payload | Description | +|---|---|---| +| `/control/led` | `on\|off` | Toggle status LED | +| `/control/ir` | `on\|off\|auto` | IR lights | +| `/control/reboot` | *(empty)* | Reboot camera | +| `/control/ptz` | `up\|down\|left\|right\|in\|out [amount]` | PTZ movement (default amount: 32.0) | +| `/control/ptz/preset` | `[id]` | Move to PTZ preset | +| `/control/ptz/assign` | `[id] [name]` | Save current position as preset | +| `/control/zoom` | `[amount]` | Zoom factor (1.0 = normal, 3.5 = 3.5x) | +| `/control/pir` | `on\|off` | Toggle PIR sensor | +| `/control/floodlight` | `on\|off` | Toggle floodlight | +| `/control/floodlight_tasks` | `on\|off` | Toggle automatic floodlight triggers (motion, night) | +| `/control/wakeup` | `[mins]` | Force wakeup for given minutes (for `idle_disconnect` cameras) | +| `/control/siren` | `on` | Activate siren (no "off" signal exists) | + +### Status Messages + +| Topic | Description | +|---|---| +| `/status` | `disconnected` when camera goes offline | +| `/status/battery` | XML battery status (reply to `/query/battery`) | +| `/status/battery_level` | Battery % (requires `enable_battery = true`) | +| `/status/pir` | XML PIR status (reply to `/query/pir`) | +| `/status/motion` | `on` for motion, `off` for still (requires `enable_motion = true`) | +| `/status/ptz/preset` | XML PTZ presets (reply to `/query/ptz/preset`) | +| `/status/preview` | Base64 JPEG, updated every 2s (requires `enable_preview = true`) | +| `/status/floodlight_tasks` | Floodlight task status, updated every 2s | + +### Query Messages + +| Topic | Description | +|---|---| +| `/query/battery` | Request battery level report | +| `/query/pir` | Request PIR status report | +| `/query/ptz/preset` | Request PTZ preset list | +| `/query/preview` | Request immediate preview image | + +## Controlling RTSP from MQTT + +When started with `mqtt-rtsp`, publish to `/neolink/config` to change RTSP +settings at runtime: + +```toml +# Change available users +[[users]] +name = "me" +pass = "mepass" + +# Change permitted users on a camera +[[cameras]] +permitted_users = ["me"] + +# Change stream (set to "None" to disable) +[[cameras]] +stream = "Main" + +# Disable a camera entirely +[[cameras]] +enabled = false +``` + +## Per-Camera MQTT Options + +Disable features to conserve battery or reduce traffic: + +```toml +[cameras.mqtt] +enable_motion = false # motion detection (passive, low battery drain) +enable_light = false # floodlight status (passive, low battery drain) +enable_battery = false # battery updates in /status/battery_level +enable_preview = false # preview image in /status/preview +enable_floodlight = false # floodlight task status +battery_update = 2000 # ms between battery updates +preview_update = 2000 # ms between preview updates +floodlight_update = 2000 # ms between floodlight task updates +``` + +## Home Assistant Discovery + +[MQTT Discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) +is partially supported. Discovery is opt-in and features must be manually +specified: + +```toml +[cameras.mqtt.discovery] +topic = "homeassistant" +features = ["floodlight", "camera", "led", "ir", "motion", "reboot", "pt", "battery", "siren"] +``` + +| Feature | Description | +|---|---| +| `floodlight` | Light control | +| `camera` | Preview image (updated ~every 0.5s over MQTT, not RTSP). Not all cameras support the snapshot command. | +| `led` | Status LED on/off switch | +| `ir` | IR light on/off/auto selector | +| `motion` | Motion detection binary sensor | +| `reboot` | Reboot button | +| `pt` | Pan and tilt buttons | +| `battery` | Battery level sensor | +| `siren` | Siren button |