An OcuSync-style frequency-hopping link on any band from 70 MHz to 6 GHz, for drones and robots, on off-the-shelf SDR boards.
NyxHop is the kind of link DJI builds into its drones: the video hops, the control has its own hopping channel, the two ends are paired, the rate follows the channel and the link comes back by itself after a drop. The difference is where it goes: your own channel table anywhere from 70 MHz to 6 GHz, several bands in one table, on off-the-shelf SDR boards (an ADRV9364-Z7020, an ANTSDR E200 or a PlutoSDR). The link moves blocks of bytes; what you put in them is your business: video, telemetry, files, your own protocol, all at once if you like.
- Hard to jam and hard to find: no fixed, well-known frequencies to aim at, only the channels you choose; it moves off a jammed channel by itself and keeps its power to what the link needs.
- Distance without GPS: the link measures the distance between its two ends from its own radio signal, steady to about 0.3 m, so it keeps working where GPS is jammed or spoofed.
- Fully non-Chinese if you need it: Analog Devices radios, AMD (Xilinx) FPGAs, our own software from end to end.
- Made for your system: we fit the link to your band, your hardware, your application and your product (below).
- Free on a PlutoSDR: two ADALM-Plutos make a complete NyxHop link with no licence, no time limit and no sign-up. One command sets them up (below).
It carries an H.264 camera stream at 30 fps, 28 ms from camera to screen (61 ms with the two-layer simulcast switched on, which keeps a picture through fades).
nyxhop-demo.mp4
Two boards and the NyxHop app at each end: pairing, a fixed channel, a jump to another band, Auto, your own channel tables, the distance between the boards measured by the link itself (no GPS) and messages both ways.
| DJI OcuSync | NyxHop | |
|---|---|---|
| bands | the licence-free bands around 2.4 and 5 GHz | any channels you list from 70 MHz to 6 GHz; one table can span several bands and the link hops across them |
| jamming and detection | the well-known 2.4 and 5 GHz bands, the ones drone detectors and jammers cover first | frequencies only you know, across bands; moves off a jammed channel by itself; transmit power held to what the link needs |
| hardware | built into DJI aircraft, goggles and controllers | two off-the-shelf SDR boards, your camera, your computer or phone |
| what it carries | DJI video and flight control | any bytes: video, MAVLink, files, your own protocol |
| software | closed | apps and SDK in Rust, public domain |
| origin | DJI, China | developed in Vietnam; can be built with no Chinese parts at all: Analog Devices radios, AMD (Xilinx) FPGAs, our own software |
OcuSync is a trademark of SZ DJI Technology Co., Ltd.; NyxHop is not affiliated with or endorsed by DJI.
This repository is where we start, not a box we hand you. Write to contact@tacitek.com with what you are building and we fit the link to it:
- your band: a channel plan anywhere from 70 MHz to 6 GHz, across several bands if you need them, with hopping and power set for your rules and the spectrum where you operate;
- your hardware: another AD936x board or your own board design, your power amplifier, low-noise amplifier and antennas;
- your application: video with the codec and latency you need, MAVLink and telemetry, an IP bridge, several streams at once, or your own protocol over the link;
- your product: NyxHop inside what you sell, your branding on the apps, boards in production numbers, a support contract;
- range and robustness: tuned for your distance, your airframe or robot and the interference where you fly;
- fully non-Chinese when your programme needs it;
- engineers with you: the people who built the link sit with your integration until it does what you need.
To try it yourself first, everything below runs on two boards you can buy today, free for the first ten boards (Try it free).
The source code in this repository needs no licence: it is public domain
(Unlicense), do what you like with it. The radio itself is a different matter: the
board images in deploy/ (FPGA design, radio daemon) are binaries under an EULA,
and the boards need a licence file to run - the Licensing table at the bottom
says exactly which is which.
flowchart LR
CAM["Camera"] --> TX["nyx-tx<br/>PC or small SBC"]
FC["Flight controller"] <-.-> TX
TX -- "Ethernet" --> BA["SDR board"]
BA == "radio link" ==> BB["SDR board"]
BB -- "Ethernet" --> RX["nyx-rx<br/>PC or Android"]
RX <-.-> GCS["Ground control station"]
IPC["IP camera"] -. "Ethernet, no computer" .-> BA
Two boards, one at each end, each on Ethernet with the computer that runs its app. Video and data go one way over the radio link, control and telemetry the other. Both boards run the same image; which end a board plays is a flag when you flash it. The computers never touch the radio: they exchange bytes with a board over Ethernet, and everything about the air interface lives on the board. The aircraft end does not need a computer at all: with an IP camera on its Ethernet port the board sends the camera by itself (below).
Four steps: an OS on each board, NyxHop and an address on each board, the apps, a licence. Budget an hour the first time.
You need
- Two boards, ADRV9364-Z7020, ANTSDR E200 or PlutoSDR in any mix, with antennas for the band you will use. For a first test, 1–3 m apart is fine.
- A PC on the same Ethernet as the boards, with Python 3 (
pip install paramiko) for the flashing tools (a PlutoSDR plugs into its computer's USB instead; its tool needs nothing but Python). A USB camera on the transmitting side. - This repository: it carries the prebuilt board images in
deploy/. The apps you build yourself, with Rust 1.85 or newer:cargo build --releaseat the repository root, once, for all of them.
ADRV9364-Z7020: write ADI's stock Kuiper Linux 2023_r2 image to a microSD card (balenaEtcher,
or ADI's Kuiper Imager with the adrv9364z7020_lvds configuration), put it in the board, power up
with Ethernet connected. It takes an address by DHCP; user root, password analog. Find the
address on your router. Want a fixed one: give it a DHCP reservation on your router, or set a
static address on the board as on any Linux box.
ANTSDR E200: nothing to install. The stock firmware in its flash stays as it is; NyxHop boots
from a microSD card and the board is stock again the moment you take the card out. Put an empty
FAT32 card in and power up: a stock board comes up at 192.168.1.10. Another address is one
command on the board, once: ssh root@192.168.1.10, then fw_setenv ipaddr_eth 192.168.0.12
and reboot.
PlutoSDR: nothing to install here; step 2 does it. Plugged into USB, a Pluto shows a drive
named PlutoSDR and is a small network of its own: the Pluto at 192.168.2.1 (user root,
password analog), this computer at 192.168.2.10. On Windows, if that address does not
answer, install ADI's PlutoSDR USB drivers. A Pluto at the aircraft end still needs a computer
on its USB cable, the one with the camera: a laptop, or a small board running
nyx-tx --headless (step 3).
One command per board, from the repository root. --host is the board's address, --role is
which end it plays.
set BOARD_PW=analog # the board's ssh password (PowerShell: $env:BOARD_PW = "analog")
# ADRV9364 as the transmitting end (camera side)
python link/scripts/nyx_flash.py --host 192.168.0.10 --role tx
# E200 as the receiving end (ground side)
python link/scripts/nyx_flash.py --host 192.168.0.12 --role rx --board e200Any board can take either role. The tool copies the FPGA design, the radio daemon and its
configuration to the board (on the ADRV9364 over the Kuiper boot files, keeping the originals as
*.nyx-orig; on the E200 onto the card), reboots the board and checks that it came back. From
then on the board starts NyxHop by itself on every power-up. The addresses above are the ones
used throughout this page; yours are whatever your boards have.
Run the same command again to update a board; tables, pairing and licence are kept.
--verify-only only checks a running board. --scan lists every board it finds on the network,
with role and address, no password needed.
The quick way. Plug the Pluto in (or both, if one computer holds the two ends), wait until its drive shows up, then from the repository root:
python link/scripts/nyx_pluto.py # NyxHop onto every Pluto plugged in here
python link/scripts/nyx_pluto.py --pair # two Plutos here: one ground, one aircraft, pairedIt lists the Pluto(s) it found and what it will do, and asks before writing (--list only
shows). It writes the firmware through the Pluto's own drive, gives each Pluto a network of its
own when it needs one (below), waits until NyxHop answers and prints the command that opens the
app on it. Nothing but Python 3 is needed, and nothing is left to do on the Pluto. Leave the
Pluto plugged in while its LED blinks fast: that is it writing its flash (a minute or two).
By hand, the same thing, one Pluto at a time:
-
Firmware. Copy
deploy/pluto/pluto.frmonto the Pluto's drive and eject the drive (the Eject in Windows Explorer, the Finder or your Linux file manager: it is the eject that tells the Pluto to act). The LED blinks fast while it writes the firmware, then the Pluto restarts; when its drive shows up again it runs NyxHop, as the receiving end until the app says otherwise. -
Network, when needed. Every Pluto comes as
192.168.2.1, so a Pluto needs another network when a second Pluto is plugged into the same computer, or when this computer already has something on192.168.2.x: look atipconfig(Windows),ip addr(Linux) orifconfig(macOS) with the Pluto unplugged. Virtual machine software often sits there,192.168.2.1included. Openconfig.txton the Pluto's drive in a text editor and change three lines:[NETWORK] ipaddr = 192.168.3.1 ipaddr_host = 192.168.3.10 [ACTIONS] reset = 1
ipaddris the Pluto (the address you give the app),ipaddr_hostthis computer, and both must share the first three numbers. A second Pluto takes192.168.4.1/192.168.4.10, a third192.168.5.x, and so on.reset = 1makes the Pluto restart with the new address as soon as you save and eject; without it the address waits for the next power-up. When the drive shows up again,config.txtshows the address in use (andreset = 0again), andping 192.168.3.1answers. Both steps fit in one eject: copypluto.frm, editconfig.txt, then eject.
Either way, an update later is the same copy and keeps role, pairing and tables; ADI's own
pluto.frm copied the same way puts the stock firmware back. NyxHop keeps ADI's firmware
underneath, so the Pluto still works with other SDR software once NyxHop is off:
ssh root@192.168.2.1 (its address), then touch /mnt/jffs2/nyxhop.off and reboot;
rm /mnt/jffs2/nyxhop.off and reboot bring NyxHop back, pairing and tables as they were. The
Pluto tunes its frequency to the board at the other end by itself, so two Plutos, or a Pluto and
another board, meet without calibration.
A PlutoSDR starts on its own channel tables, video 2500, 3300, 3600 MHz and control 433,
922 MHz; an ADRV9364 or an E200 starts on video 5.8 GHz and control 2.4 GHz. Two Plutos pair
as they are. A Pluto and one of the other boards need tables that suit both, set on both ends
before pairing (Channel, then Apply, in the app connected to each board): video
2500,3300,3600 and control 2412,2432,2452,2472 work for every board. A Pluto at the aircraft
end receives the control at 40 dB of gain, right for the sub-GHz pool; with the control on
2.4 GHz raise it under Radio to about 62 dB.
cargo build --release at the repository root puts them in target/release/. One app serves
either end:
nyxhopIt asks which end this computer is (Ground station: this computer shows the video;
Aircraft: this computer has the camera) and which board it is plugged into (its address; a
Pluto's is 192.168.2.1 unless you changed it), puts the board into the matching role (the
board restarts, about ten seconds), and opens the ground or the
aircraft screen. It remembers the choice; nyxhop --mode rx --board 192.168.0.12 or
--mode tx --board 192.168.0.10 skips the question. So a pair of boards can change ends from
the apps alone: choose the other end on each computer, and both boards follow.
The settings drawer of every screen (the gear, or H) has a Board role switch too:
Aircraft (tx) / Ground (rx). It sends the same command; the board restarts in its new
role, and a nyxhop window follows by opening the other screen on its own. Change both boards
and the link comes back the other way round, tables, pairing and licence untouched.
The two screens are also programs of their own, for scripts and headless boxes:
nyx-rx --channel 192.168.0.12:7011 # ground: the receiving board's address
nyx-tx --channel 192.168.0.10:7010 # aircraft: the transmitting board's address
nyx-tx --channel 192.168.0.10:7010 --headless # no window, e.g. on an SBC next to the cameraThe aircraft screen starts on the USB camera; with none plugged in it sends a test pattern.
Source in its settings also takes an IP camera: choose IP camera (RTSP) and type the
camera's URL, rtsp://user:password@192.168.1.64:554/stream1 for instance (the address and
path are in the camera's manual; a 640x480 sub-stream is the right size). The camera's picture
is decoded and sent on like the webcam's, so the link's rate control applies to it.
Two settings underneath change how an IP camera is carried. Send the camera's H.264 as it is passes the camera's own pictures on untouched: nothing is decoded and re-encoded, which takes about 30 ms off the delay and leaves the picture exactly as the camera made it. Nothing on this side can then make the stream smaller, so Camera follows the link (ONVIF) does it at the source: over ONVIF, with the user and password of the RTSP URL, the app moves the camera's own bitrate limit up and down with the link and tells you what it asked for. Leave ONVIF address empty unless the camera answers on another address or port, and set Ceiling to hold it below a bitrate of your choosing (0 keeps the limit the camera already had). A camera that does not answer ONVIF simply keeps its own bitrate, and the reading says so.
The Android app is the same thing on a phone: it starts on the same choice, Ground station
or Aircraft, with the board's address, puts the board into the role and shows that end's
screen. As the ground end it shows the video; as the aircraft end it sends the phone's own
camera (the app asks for the camera permission once) or an IP camera (Source in the
settings, then the URL). The phone talks to the board over USB-C Ethernet or Wi-Fi. It
remembers the choice in Android/data/com.nyxhop.mobile/files/nyxhop.cfg, a plain text file
adb can edit (board 192.168.0.12, mode tx, source phone|rtsp, rtsp <url>).
The Android app is its own build: python apps/video/build_apk.py writes a signed APK to
apps/video/android/out/, and needs the Android SDK and NDK, cargo-ndk and the
aarch64-linux-android Rust target. You do not need it: the apps on a PC do the same job.
An IP camera needs no computer on the aircraft. nyx-ipcam is the transmitting app built for
the board's own ARM: it runs beside the radio, pulls the camera's RTSP stream over the board's
Ethernet port and puts the camera's H.264 on air as it is. Nothing is decoded or encoded on the
board, so the two ARM cores stay with the radio. What flies is a board, a camera and a cable.
It runs on the ADRV9364 and the E200, the boards with an Ethernet port for the camera.
python link/scripts/nyx_ipcam_install.py 192.168.0.10 # ADRV9364 or E200, once NyxHop runs on itThat writes the prebuilt program from deploy/ipcam/ onto the board and starts it on every
power-up (a systemd service on the ADRV9364, the SD card's start-up hook on the E200). Give the
camera an address in the board's subnet and plug it into the board, directly or through a
switch. Then tell the board where the camera is, from any PC on that network:
nyx-ipcam-ctl --board 192.168.0.10Type the camera's URL, press Apply, then Save on board: from then on the board finds its camera by itself after a power cycle, and the PC can go. The window also shows what the camera sends against what the air carries, holds the same ONVIF settings as the aircraft app (the board moves the camera's bitrate with the link), and carries the link, role, radio and licence sections of the other apps. No video passes through that PC; the picture is on the ground screen.
The program follows the board's role by itself, so it can sit on both boards of a pair: on the
receiving end it waits, and when a board becomes the aircraft it starts sending. If the camera
falls silent it lets go of the radio after five seconds, so a nyxhop Aircraft window on a PC
can take the board over without anything being switched off. Its console is TCP 7201 on the
board, the same commands as the headless app below plus save; a flight controller on the same
Ethernet sends MAVLink to the board's UDP 14557 and it goes up the link.
No camera at hand? nyx-fakecam is one on your PC: it serves your webcam (or a test pattern) as
an RTSP camera with an ONVIF service, at the bitrate and key-frame interval you give it.
nyx-fakecam --kbps 2000 --gop 60 --user admin --pass secret prints the URL to give the board.
To build the board program yourself instead of using the prebuilt one:
cargo build --release -p nyx-ipcam --target armv7-unknown-linux-gnueabihf (the Rust target and
an arm-linux-gnueabihf linker named in .cargo/config.toml); the installer takes your build
when it finds one.
Then, once in the life of a pair of boards, pair them: press Link aircraft in the ground app. A board that has never been paired accepts on its own, both pills turn linked, and the video is on the ground screen. The boards remember the pairing across power cycles.
Accept link in the transmitting app is for the other case: an aircraft that is already paired ignores a new key until someone at the aircraft presses it, which opens a 60-second window. That is what stops a stranger from taking over your aircraft. Unlink forgets the key on either end.
Everything else is in the apps' settings drawer (tap the video or press H):
- Video (aircraft): USB camera, IP camera (RTSP, a URL) or test pattern, resolution, fps, quality, codec, the simulcast base layer for reach through fades. An IP camera adds the pass-through and ONVIF settings above.
- Channel: your tables. Video MHz and Control MHz take any channels from 70 to 6000 MHz, comma separated; Apply sends them to the board, which passes them to the other end over the air. Auto holds the best channel and re-scans when it degrades; Off pins one channel. The tables are yours: check your local rules, fit antennas for the band.
- Radio: gains, antenna ports (the E200 has two per direction), AGC. Leave them alone unless you know why.
- Messages: short text between the ends. Connection: the board address.
- Readouts in the drawer header (or the
Okey) turns the figures over the video off, leaving the picture and the status pills.Hopens and closes the drawer.
The headless transmitting app takes the same settings on its console (TCP 7201 by default):
set source webcam, set source rtsp with set rtsp rtsp://..., set pass 1,
set camadapt 1, set onvif auto, set cammax 2000, set fps 20, set quality 60, stats; a nyx-tx.cfg next to the program holds them across starts.
Telemetry and data: the link also carries a two-way UDP pipe. Send datagrams into nyx-rx
UDP 14555 and they come out of nyx-tx UDP 14556 (commands, RC); send into nyx-tx UDP 14557
and they come out of nyx-rx UDP 14558 (telemetry). MAVLink is the usual payload: point your
ground control station at 14558 and your flight controller's MAVLink router at 14557. Details
and the SDK crates in docs/telemetry-sdk.md.
PlutoSDR: free, nothing to do. NyxHop on a PlutoSDR needs no licence, has no time limit and never asks for one, commercial use included.
ADRV9364-Z7020 and ANTSDR E200: every board starts with a 20-hour grace period, counted by the board itself, so you can do all of the above first. Each board needs its own licence, and the simplest way is to do each one in the app that is connected to it:
- Open Licence in the drawer and press Copy next to the board's DNA.
- Ask for a licence with it at nyxhop.com/licence.html: free for the first ten boards per email address, commercial use included (see docs/license.md). The file comes back on the page at once.
- Paste it into the box under the DNA and press Apply here. The pill turns licensed.
Do that in nyx-rx for the ground board and in nyx-tx for the aircraft board. Once video is
flowing, the ground app also shows the aircraft's DNA and a Send to aircraft button, which
saves the walk to the aircraft next time; it needs the video link, so it is not there while the
aircraft is still locked. A licence is bound to its board, works offline and never expires.
- The app cannot connect: can you ping the board? Is the PC on the same network? Forgot
the address:
python link/scripts/nyx_flash.py --scan. A Pluto's address isipaddrinconfig.txton its drive (python link/scripts/nyx_pluto.py --listshows every Pluto plugged in, and whether another network card of this computer is in its way). - Connected but no video: both pills must say linked; if not, press Link aircraft in the ground app again (and Accept link in the aircraft app if that board is already paired). A pill saying locked is a board past its grace period: licence it from the app connected to that board. Both ends must show the same tables: press Apply in Channel again. In the transmitting app, does the preview move? Try the test pattern to rule out the camera.
- Video stutters: SNR under 15 dB is antennas, distance or the wrong band; the pill shows scan while the receiver looks for a better channel.
- Looking deeper: the board console answers on TCP 7202 (
nc 192.168.0.12 7202, thenhop status,license,stats), the apps write logs next to the executable inlogs/, and the daemon log is/home/root/radio.out(ADRV9364),/tmp/nyxhop/radio.out(E200) or/tmp/nyxhop/node.log(PlutoSDR).
| board | transceiver | status |
|---|---|---|
| ADRV9364-Z7020 | AD9364 | ready |
| ANTSDR E200 | AD9361 | ready |
| PlutoSDR | AD9363 | ready, free |
Any board can be either end, in any mix, and two of the same kind work. Ethernet between an ADRV9364 or an E200 and its computer, with a 12 V supply; a PlutoSDR runs from its USB cable, which is its network too. Antennas of your choice.
A pair of boards: ADRV9364-Z7020 on the left, ANTSDR E200 on the right.
The boards transmit at the power their own front end gives, which is enough for a short-range test and not much more. For distance, add the RF yourself: an antenna with gain at both ends, a power amplifier on the transmitting side, a low-noise amplifier on the receiving side. That part is outside NyxHop, and the power you end up radiating is yours to keep within your local rules.
Another board, or one you would like to see on this list? Write to contact@tacitek.com.
The link itself knows nothing about video. It carries blocks and tells you which ones arrived. Applications sit on top of it and each lives in its own folder:
| folder | what it does |
|---|---|
apps/video/ |
the apps: H.264 from a USB, IP or phone camera one way, a ground app, a transmitting app and an Android app for either end. They also carry the two-way UDP pipe (MAVLink or any datagrams) |
apps/nyxhop/ |
the one app for either end: asks Ground or Aircraft, sets the board's role, then hosts the screen |
apps/data/ |
mav_sim.py, which measures that pipe |
Write your own the same way: take nyx-proto and nyx-link from link/, hand the link your
bytes, read them out at the far end. It is Rust with nothing platform-specific in the core:
Windows and Linux on x86-64, Linux on ARM boards, Android 12 or later. Nothing on the PC or the
phone needs a radio driver: the modem runs on the board.
A PlutoSDR is free outright: no licence, no time limit, no sign-up. Any other board runs 20 hours before it needs a licence at all, and the first ten boards per email address are free, commercial use included, covering the current feature generation with all its bug fixes, for ever. Licences are per board, not per pair. More boards, or a link made for your system: contact@tacitek.com (above). Details in docs/license.md. Questions about using it as it is belong in Issues and Discussions, where everyone can read the answer.
link/ the transmission system: protocol, block framing, shared app support,
board console, the flashing tools (nyx_flash.py; nyx_pluto.py for PlutoSDRs)
apps/video/ the apps (ground, transmitting, Android, the camera app for the board's ARM
with its PC window and a test camera) - video and the UDP data pipe
apps/data/ the tool that measures the pipe
deploy/ prebuilt board images you flash (deploy/adrv9364, deploy/e200, deploy/pluto) and
the camera app built for the boards (deploy/ipcam)
docs/ licence terms, SDK notes, legal
| what | licence |
|---|---|
Source code in this repository: link/, apps/, docs/ |
public domain (Unlicense): no licence needed |
Binaries in deploy/adrv9364, deploy/e200 and deploy/pluto: FPGA design, radio daemon, board images |
EULA |
deploy/ipcam/: a build of apps/video/nyx-ipcam for the boards |
public domain, like its source |
| Third-party components inside the board images (U-Boot, Linux, BusyBox and others) | their own licences, see THIRD-PARTY.md |
Radio use is subject to the laws of your country: you are responsible for the frequencies and the power you transmit.
