Rebroadcaster Mode¶
Rebroadcaster is a receiver mode with extra outputs. It accepts position packets from a network source (a paired Sender, a flight simulator's output plugin, a custom script), synthesizes NMEA-0183 locally, and re-emits the result to as many downstream targets as you've enabled - a Bad Elf over USB, one or more EFBs over XGPS, a Fleet Dashboard listener over UDP, or any combination.
This is the canonical mode for a "central hub" station: one rebroadcaster fans out to a serial-connected avionics box, several EFB iPads, and a fleet-monitoring container, all at once.
Where this fits
Rebroadcaster is the most-configured of the four modes. If you only need to drive a single output - just an EFB, or just a serial device, with no upstream source - use Stand-Alone Mode instead. If you have an upstream source but only one downstream consumer, plain Receiver Mode is simpler.
What Rebroadcaster Mode does¶
At a high level:
flowchart LR
Source["Upstream source<br/>(Sender, sim plugin, custom script)"]
Engine["NMEA engine<br/>synthesize sentences"]
USB["USB Serial<br/>(Bad Elf, generic)"]
EFB1["ForeFlight (broadcast)"]
EFB2["ForeFlight / Garmin Pilot<br/>(unicast IP)"]
UDP["UDP Retransmit<br/>(downstream consumer<br/>or Fleet Dashboard)"]
Source -->|UDP or TCP :LISTEN_PORT<br/>JSON or CYGNUS| Engine
Engine -->|NMEA-0183| USB
Engine -->|XGPS line| EFB1
Engine -->|XGPS line| EFB2
Engine -->|original position packet<br/>+ 1Hz heartbeat| UDP
Three things to keep straight about that picture:
| Thing | Detail |
|---|---|
| The receiver always synthesizes NMEA locally. | Even though the upstream source could in theory ship full NMEA, the protocol is intentionally just position. NMEA sentence selection is a property of this rebroadcaster, not of the source. |
| UDP Retransmit forwards the original position packet, not synthesized NMEA. | The downstream consumer (typically a Fleet Dashboard) gets the same JSON the rebroadcaster received. This keeps the dashboard's view of every simulator schema-consistent. |
| The heartbeat is a separate UDP packet on the same port as UDP Retransmit. | Distinguished by "type": "heartbeat". The Fleet Dashboard uses it for the health-chain view (see Health Chain). |
Reaching Rebroadcaster mode in the UI¶
| Step | Action |
|---|---|
| 1 | Log in if BYPASS_AUTH=false. |
| 2 | In the mode selector at the top of the dashboard, choose Rebroadcaster. |
| 3 | Two configuration panels appear in the main column - the Network panel (listen port + protocol) and the Rebroadcaster Settings panel (outputs). |
Rebroadcaster is not selectable while the emulator is running. Press Stop first if you need to switch into or out of it.
Listen configuration¶
Rebroadcaster shares its listen settings with plain Receiver mode. They are controlled by the Network panel.
| Control | Default | Valid values | What it does | When to change |
|---|---|---|---|---|
| Listen Port | 12000 |
1024-65535 | The UDP/TCP port this rebroadcaster binds to and waits for incoming position packets. | Whenever you need to coexist with another rebroadcaster on the same host, or whenever the upstream source is configured to send to a different port. |
| Protocol | udp |
udp, tcp |
Transport for position packets. UDP is fire-and-forget at 1 Hz; TCP is reliable but adds a connection-state per source. | Pick tcp only when you need guaranteed delivery (slow or lossy network) and you control the source. For Wi-Fi / wired LAN, udp is the right answer. |
Listen Port and UDP Retransmit Port must be different
If you enable UDP Retransmit (below) and point it at the same port the rebroadcaster is listening on, you'll create a feedback loop that hammers the host. The UI rejects identical ports, but be aware of this when configuring auto-start via env vars - the env-var path does not validate.
Persistent state for listen configuration¶
| Live (in-memory) | Boot-time (env var) |
|---|---|
| Listen port set in the UI | AUTO_START_LISTEN_PORT (default 12000) |
| Protocol set in the UI | AUTO_START_PROTOCOL (udp or tcp, default udp) |
In-memory changes do not survive a container restart. To pin a configuration across restarts, set the env vars in docker-compose.yml and use Auto-Start.
Output options¶
At least one output must be enabled before Start activates. The Rebroadcaster Settings panel groups the outputs into three independent blocks.
EFB Output (UDP 49002 - ForeFlight and Garmin Pilot)¶
| Control | Default | Valid values | What it does |
|---|---|---|---|
| EFB Output (Port 49002) master toggle | off | on / off | Enables the EFB block. With this off, neither broadcast nor unicast EFB output is sent regardless of sub-options. |
| Broadcast | off | on / off | Sends XGPS frames to the local broadcast address on UDP 49002. ForeFlight uses this to auto-discover the source. |
| Garmin Pilot / ForeFlight (IP Address) | off | on / off | Sends XGPS frames to one or more unicast targets on UDP 49002. Required for Garmin Pilot; optional alternative to broadcast for ForeFlight. |
| EFB target IPs | (empty) | Comma-separated list. See IP Range Parsing. | Individual IPs (10.200.50.5), multiple IPs (10.200.50.5, 10.200.50.6), ranges (10.200.50.10-10.200.50.20), or mixed. |
| Simulator Name | (empty) | Free text, typically aircraft type (CL350, Ultra, CJ3) |
The string that appears in the EFB app's GPS-source list. Required when EFB output is enabled. |
When to use Broadcast vs IP targeting¶
| Situation | Use Broadcast | Use IP targeting |
|---|---|---|
| ForeFlight on the same Wi-Fi, no AP isolation | Yes | Optional |
| Garmin Pilot on the same Wi-Fi | No (Garmin Pilot doesn't accept broadcast) | Yes |
| Multiple iPads, mixed apps | Both - broadcast covers ForeFlight, IPs cover Garmin Pilot | Both |
| The EFBs are on a different VLAN with a router in between | Broadcast won't cross routers | Yes - IP unicast works |
Test EFB discovery before everything else
With EFB enabled and an iPad on the same Wi-Fi, the Simulator Name should appear in the EFB's GPS-source list within a few seconds of pressing Start. If it doesn't, the issue is almost always either AP isolation on the Wi-Fi or a host firewall blocking outbound UDP 49002. Resolve that first; nothing else in the chain matters until EFB discovery works.
UDP Retransmit (typically to a Fleet Dashboard)¶
| Control | Default | Valid values | What it does |
|---|---|---|---|
| UDP Retransmit toggle | off | on / off | Enables the block. With this off, no UDP packets and no heartbeats are sent downstream. |
| Target IP | (empty) | A single IPv4 address | Where the position packets and heartbeats are sent. Typically the IP of the Fleet Dashboard host. |
| Port | 12001 |
1024-65535 | UDP port on the target. Must differ from the Listen Port. When sending to the Fleet Dashboard, must match the dashboard's SIM_N_PORT for the simulator's slot. |
The retransmit stream actually contains two packet types on the same port:
| Packet | Cadence | Trigger |
|---|---|---|
| Position | Once per incoming source packet (typically 1 Hz) | An inbound JSON or CYGNUS packet was received and parsed. |
| Heartbeat | Strictly 1 Hz, regardless of source activity | The rebroadcaster's heartbeat thread. Sent even when no source data is arriving, so the dashboard can distinguish "rebroadcaster is up but source is silent" from "rebroadcaster is down." |
The heartbeat carries:
| Field | Type | Meaning |
|---|---|---|
type |
string | Always "heartbeat" (vs position packets which omit type). |
sim_ip |
string | The configured SIMULATOR_IP, or empty string if none. |
sim_reachable |
bool | Result of an ICMP ping (ping -c 1 -W 1) to sim_ip. false if no sim_ip is configured. |
receiving_udp |
bool | true if a position packet has been received in the last 5 seconds. |
uptime_seconds |
int | Seconds since this rebroadcaster started. |
Ping is part of the container image
iputils-ping is included in the base image, so the sim_reachable check works inside the container with no extra setup. The container still needs network reachability to the simulator host for ping to succeed; if you've isolated networks, expect sim_reachable: false even on a healthy deployment.
USB Serial Output (Bad Elf and others)¶
| Control | Default | Valid values | What it does |
|---|---|---|---|
| USB Serial Output toggle | off | on / off | Enables NMEA-0183 output to a USB-serial device. |
| Device | (none) | Any path the host's /dev exposes that matches the supported patterns |
The serial device to open. The Refresh button next to the panel header rescans the device list. |
| Baud Rate | 115200 |
1200 - 115200 (see table) | Symbol rate on the serial line. Must match the receiving device's expectation. |
The serial format is fixed at 8N1 (8 data bits, no parity, 1 stop bit) - the standard for NMEA-0183 and what every Bad Elf product expects.
Supported device patterns:
| Platform | Pattern |
|---|---|
| Linux | /dev/ttyUSB*, /dev/ttyACM* |
| macOS | /dev/tty.usbserial-*, /dev/cu.usbserial-*, /dev/tty.usbmodem-*, /dev/cu.usbmodem-* |
Hot-plugging doesn't add devices to a running container by default
-v /dev:/dev shares the current /dev snapshot - it doesn't subscribe to new device events. If you plug in a USB-serial adapter after docker compose up -d, restart the container with docker compose restart gps-emulator so the new device shows up in the picker.
Baud rate selection¶
| Baud | Typical consumer |
|---|---|
| 1200 | Legacy modems |
| 2400 | Low-speed printers |
| 4800 | GPS modules (de facto NMEA default) |
| 9600 | Common embedded default and many EFBs |
| 19200 | Industrial / PLC |
| 38400 | Instrumentation |
| 57600 | Higher-speed embedded |
| 115200 | USB-serial bridges - the default |
For the Bad Elf SBK-2500 specifically, see USB Serial (Bad Elf) - the cable, baud, and parity choice are documented there end-to-end.
NMEA sentence selection¶
Rebroadcaster mode honors the sentence selections from the NMEA panel - the same panel used by every mode. GPGGA and GPRMC are always emitted; everything else is opt-in. See NMEA Sentences for the per-sentence detail.
Send only what the consumer needs
If the rebroadcaster is feeding a single piece of avionics that only parses GPGGA + GPRMC, leave the others off. NMEA over RS-232 is bandwidth-constrained at low baud rates, and adding GPGSV plus GPGSA can occasionally cause the receiving end to drop the next GPGGA if its buffer fills.
Starting and stopping¶
| Action | What happens |
|---|---|
| Start | The receiver thread binds the listen port, the NMEA engine arms, each enabled output opens its socket / serial port, and (if UDP Retransmit is enabled) the heartbeat thread begins firing at 1 Hz. |
| Stop | All output sockets close, the serial port releases, the receiver thread unbinds, and the heartbeat stops. In-memory configuration stays untouched - you can press Start again without re-entering anything. |
| Container restart | All in-memory configuration is discarded. Auto-start (if configured via env vars) re-arms the rebroadcaster with the env-var values on the next boot. |
While running:
- The Status Display shows whether packets are arriving (
receiving_udp). - The Output Viewer scrolls live NMEA-0183, the same bytes being sent over USB and (if you toggle the viewer to "EFB") the XGPS frames being sent over UDP 49002.
Health monitoring against a connected simulator¶
If the rebroadcaster's role is to feed a specific flight simulator (an X-Plane host, an MSFS host, a Cygnus box), set SIMULATOR_IP in docker-compose.yml to the simulator's IP. The rebroadcaster will:
- Ping that IP once per second from inside the container.
- Include the result (
sim_reachable: true | false) in every heartbeat. - The Fleet Dashboard's per-card Health Chain uses this to distinguish a "dashboard <-> emulator" failure from an "emulator <-> simulator" failure.
SIMULATOR_IP is only read at container start. Changing it requires a restart. See Health Chain for what the dashboard does with the value.
Why this lives in the env var and not the UI
The simulator IP is a deployment-time decision (it's tied to the physical setup), not a per-session one. Keeping it in docker-compose.yml next to AUTO_START_* makes the entire rebroadcaster topology reproducible from one file.
Persistent state: where every setting lives¶
| Setting | UI control | Env var | Survives restart? |
|---|---|---|---|
| Listen port | Network panel | AUTO_START_LISTEN_PORT |
Only if env var set |
| Protocol (UDP/TCP) | Network panel | AUTO_START_PROTOCOL |
Only if env var set |
| EFB enabled | Rebroadcaster panel | AUTO_START_EFB_ENABLED |
Only if env var set |
| EFB broadcast | Rebroadcaster panel | AUTO_START_EFB_BROADCAST |
Only if env var set |
| EFB target IPs | Rebroadcaster panel | AUTO_START_EFB_TARGET_IPS |
Only if env var set |
| EFB simulator name | Rebroadcaster panel | AUTO_START_EFB_SIM_NAME |
Only if env var set |
| USB enabled | Rebroadcaster panel | AUTO_START_USB_ENABLED |
Only if env var set |
| USB device path | Serial picker | AUTO_START_USB_DEVICE |
Only if env var set |
| Serial baud | Serial picker | (host-wide default SERIAL_BAUDRATE) |
Yes (env var) |
| UDP retransmit enabled | Rebroadcaster panel | AUTO_START_UDP_RETRANSMIT |
Only if env var set |
| UDP retransmit target IP | Rebroadcaster panel | AUTO_START_UDP_RETRANSMIT_IP |
Only if env var set |
| UDP retransmit target port | Rebroadcaster panel | AUTO_START_UDP_RETRANSMIT_PORT |
Only if env var set |
| Simulator IP (for ping) | (env var only) | SIMULATOR_IP |
Yes (env var) |
For the complete env-var reference with defaults and validation rules, see Environment Variables.
Worked scenarios¶
Scenario 1 - Single rebroadcaster driving one EFB + the Fleet Dashboard¶
Setup: a flight training facility with one CL350 simulator, one iPad running Garmin Pilot, one Fleet Dashboard server at 10.200.40.3.
| Setting | Value |
|---|---|
| Mode | Rebroadcaster |
| Listen port | 12000 (default) |
| Protocol | udp |
| EFB enabled | on |
| EFB broadcast | off |
| EFB IP targeting | on, target 10.200.40.198 (the iPad) |
| EFB simulator name | CL350 |
| UDP retransmit | on, target 10.200.40.3 port 12001 |
| USB | off (no serial avionics on this station) |
SIMULATOR_IP (env var) |
10.200.50.10 (the X-Plane host) |
On the Fleet Dashboard side, the matching env vars are SIM_1_NAME=CL350, SIM_1_PORT=12001, SIM_1_GPS_SYSTEM=Avionics. Within seconds of pressing Start, the CL350 card on the dashboard goes green and Garmin Pilot's GPS source picker shows "CL350".
Scenario 2 - Two iPads, one ForeFlight + one Garmin Pilot¶
Same hardware as Scenario 1, but a second iPad joins running ForeFlight at 10.200.40.199.
| Setting | Value |
|---|---|
| EFB broadcast | on (so ForeFlight discovers the source) |
| EFB IP targeting | on, target 10.200.40.198 (the Garmin Pilot iPad - still required because Garmin Pilot doesn't accept broadcast) |
| EFB simulator name | CL350 |
ForeFlight discovers CL350 via broadcast. Garmin Pilot receives the same data via unicast. Both see the same position.
Scenario 3 - Fleet of six simulators, one Fleet Dashboard¶
Six rebroadcasters, each on a separate host with a separate simulator. Each retransmits to a different port on a single Fleet Dashboard.
| Rebroadcaster | UDP Retransmit Target IP | UDP Retransmit Port | SIMULATOR_IP |
|---|---|---|---|
| CJ3 | 10.200.40.3 |
12001 | 10.200.50.11 |
| Ultra | 10.200.40.3 |
12002 | 10.200.50.12 |
| CJ1 | 10.200.40.3 |
12003 | 10.200.50.13 |
| CE560XL | 10.200.40.3 |
12004 | 10.200.50.14 |
| Classic CJ1 | 10.200.40.3 |
12005 | 10.200.50.15 |
| CL350 | 10.200.40.3 |
12006 | 10.200.50.16 |
On the dashboard, SIM_N_NAME / SIM_N_PORT / SIM_N_GPS_SYSTEM are defined for each row. Six green cards on a single instructor screen.
What's next¶
- Receiver Mode - the simpler one-source-one-output version of this same flow.
- Sender Mode - what goes on the other end of the network protocol.
- NMEA Sentences - per-sentence reference for the sentences this mode can emit.
- Auto-Start - boot directly into Rebroadcaster from
docker-compose.ymlwith no UI interaction. - Fleet Monitoring - end-to-end multi-simulator setup including the dashboard side.
- TX Checksum Offload Fix - if
tcpdumpon the dashboard shows "bad udp cksum" warnings. - Environment Variables - authoritative env-var reference for every value in the persistence table above.