The NMEA-GPS Emulator is a simple script that emulates a GPS receiver (simulates unit's movement). Data generated by the script are sent to clients in NMEA 0183 format. This script can be useful for testing applications or systems that require some unit's GPS position data.
The NMEA-GPS Emulator script can be used in one of the following operating modes:
- NMEA TCP Stream (sends TCP packets to the specified client),
- NMEA UDP Stream (sends UDP packets to the specified client),
- NMEA TCP Server (the server waits for client connections, then sends TCP packets to the connected clients - max 10 connections)
- NMEA Serial (transmit serial data on specified RS port).
- Generates standard NMEA 0183 sentences for a simulated moving GPS receiver.
- Supports four operating paths:
- interactive menu mode for manual setup;
- TCP server mode for serving up to 10 connected clients;
- TCP or UDP stream mode for sending data to a configured target;
- serial mode for transmitting data over a configured serial port.
- Accepts startup parameters for position, speed, heading, altitude, network target, protocol, serial port, and baudrate.
- Allows runtime speed and heading changes after transmission starts, unless
--headlessis enabled. - Supports
--headlessforeground execution for supervisors, containers, and scripts that should not read from stdin after startup. - Provides
--quietand--verboseoutput modes for automation and troubleshooting. - Validates CLI inputs at startup, including compact GPS position format, heading, speed, IPv4 address, port, serial requirements, and baudrate choices.
- Updates position every second using WGS84 geodetic calculations from
pyproj. - Sends a complete NMEA sentence batch every second with checksums and simulated satellite/fix data.
List of NMEA sentences generated by NMEA-GPS Emulator script:
GPGGA - Global Positioning System Fix Data
GPGLL - Position data: position fix, time of position fix, and status
GPRMC - Recommended minimum specific GPS/Transit data
GPGSA - GPS DOP and active satellites
GPGSV - GPS Satellites in view
GPHDT - True Heading
GPVTG - Track made good and ground speed
GPZDA - Date & Time
Output example:
$GPGGA,173124.00,5430.000,N,01921.029,E,1,09,0.92,15.2,M,32.5,M,,*6C
$GPGSA,A,3,22,11,27,01,03,02,10,21,19,,,,1.56,0.92,1.25*02
$GPGSV,4,1,15,26,25,138,53,16,25,091,67,01,51,238,77,02,45,085,41*79
$GPGSV,4,2,15,03,38,312,01,30,68,187,37,11,22,049,44,09,67,076,71*77
$GPGSV,4,3,15,10,14,177,12,19,86,235,37,21,84,343,95,22,77,040,66*79
$GPGSV,4,4,15,08,50,177,60,06,81,336,46,27,63,209,83*4C
$GPGLL,5430.000,N,01921.029,E,173124.000,A,A*59
$GPRMC,173124.000,A,5430.000,N,01921.029,E,10.500,90.0,051121,,,A*65
$GPHDT,90.0,T*0C
$GPVTG,90.0,T,,M,10.5,N,19.4,K*51
$GPZDA,173124.000,05,11,2021,0,0*50
The emulator simulates realistic GPS receiver behavior through several key components:
sequenceDiagram
participant User
participant CLI as __main__.py
participant Menu as Menu Controller
participant GPS as NmeaMsg
participant Worker as Mode Worker
participant Target as Serial Port / Network Peer
User->>CLI: nmea-gps-emulator [args...]
CLI->>CLI: Parse & validate arguments
CLI->>Menu: Menu(cli_config=config)
alt Interactive mode (--mode interactive, default)
Menu->>User: Display menu, select mode (1-4)
Menu->>User: Prompt for position, heading, speed
Note over Menu,User: CLI args seed prompt defaults (Enter to accept)
User->>Menu: Provide parameters
Menu->>User: Prompt for selected mode configuration
Note over Menu,User: Serial port, TCP server bind, or stream target
else Direct-start mode (serial / tcp-server / stream)
Menu->>Menu: Start mode directly from cli_config
end
Menu->>GPS: Create NmeaMsg(position, altitude, speed, heading)
Menu->>Worker: Start serial, stream, or TCP listener thread
alt Headless mode (--headless)
Menu->>Worker: Monitor worker health without stdin prompts
else Runtime input enabled
User->>Menu: Change speed / heading
Menu->>GPS: Update parameters
end
opt TCP server mode
Target->>Worker: Connect as TCP client
Worker->>Worker: Start per-client sender thread
end
loop Every second
Worker->>GPS: Request next sentence batch
GPS->>GPS: Update position (geodetic calc)
GPS->>GPS: Generate NMEA sentences
Worker->>Target: Send NMEA data
end
- Uses pyproj library for geodetic calculations on the WGS84 ellipsoid
- Calculates new positions based on current speed, heading, and time elapsed
- Converts between geographic coordinates (lat/lon) and projected coordinates for accurate distance calculations
- Updates position every second based on the unit's velocity vector
- Generates 8 standard NMEA 0183 sentences with proper formatting
- Calculates XOR checksums for each sentence to ensure data integrity
- Simulates realistic GPS data including:
- Satellite information (GPGSV) with pseudo-random satellite positions and signal strengths
- Dilution of Precision (DOP) values for position accuracy estimation
- Time synchronization with system clock
- Main thread: Handles user input for interactive speed/course changes
- Worker threads: Manage client connections and data transmission
- TCP Server mode: Accepts up to 10 concurrent client connections
- TCP/UDP Stream mode: Maintains persistent connection to specified client
- Serial mode: Transmits data via RS-232 serial port
- Thread-safe communication using events and locks for coordinated shutdown
- Sends complete NMEA sentence sets every second
- Automatically updates position before each transmission
- Supports interactive runtime modifications of speed and heading without interrupting data flow
Below instructions will get you a copy of the project up and running on your local machine.
Python 3.12 or higher is required. If your system has an older Python version, you can use pyenv to install and manage multiple Python versions:
# Install Python 3.12 with pyenv
pyenv install 3.12
pyenv local 3.12 # Set Python 3.12 for this projectDevelopment workflows use uv for dependency syncing and command execution:
curl -LsSf https://astral.sh/uv/install.sh | shPython third party packages:
In order to use NMEA Serial mode correctly, it is necessary to use dedicated serial null modem cable.
On Linux systems you will probably need to change the permissions for the device matching your serial port before running the script.
# Example command for /dev/ttyUSB0 device
sudo chmod a+rw /dev/ttyUSB0Download the wheel file from the releases page and install it:
python -m pip install nmea_gps_emulator-*.whlgit clone https://github.com/luk-kop/nmea-gps-emulator.git
cd nmea-gps-emulator
uv sync --extra devCommon development commands:
| Command | Description |
|---|---|
make sync |
Create or update the uv environment with development dependencies. |
make test |
Run tests with coverage. |
make lint |
Run Ruff lint checks. |
make format |
Format Python files with Ruff. |
make typecheck |
Run mypy. |
make check |
Run lint, format check, type check, and tests. |
make build |
Build source and wheel distributions. |
make run |
Run the emulator from the local uv environment. Pass CLI options with ARGS="...". |
Development run examples:
# Show CLI help
make run ARGS="--help"
# Start interactive mode with prompt defaults pre-seeded
make run ARGS='--position "4807N 01131E" --speed 12.5 --heading 90'
# Start TCP server mode
make run ARGS='--mode tcp-server --ip 0.0.0.0 --port 3000'
# Start TCP server mode without runtime stdin prompts
make run ARGS='--mode tcp-server --headless --ip 0.0.0.0 --port 3000'
# Send UDP stream data to a remote host
make run ARGS="--mode stream --protocol udp --ip 192.168.1.100 --port 5000"
# Run serial mode
make run ARGS="--mode serial --serial-port /dev/ttyUSB0 --baudrate 9600"nmea-gps-emulatoruv run python -m nmea_gps_emulatorThe emulator can be launched in interactive mode (default) or non-interactive mode by specifying an operating mode and parameters via CLI arguments. All parameters have sensible defaults, so you only need to provide the ones you want to override.
nmea-gps-emulator [OPTIONS]General options:
| Option | Short | Description | Default |
|---|---|---|---|
--help |
-h |
Show help message and exit | |
--quiet |
-q |
Suppress informational messages (only show errors and user prompts) | |
--verbose |
-v |
Enable verbose output with detailed debug information | |
--mode |
-m |
Operating mode: serial, tcp-server, stream, interactive |
interactive |
--headless |
Disable runtime stdin prompts after startup; requires serial, tcp-server, or stream mode |
--quiet and --verbose are mutually exclusive and cannot be used together.
Navigation parameters:
| Option | Short | Description | Default |
|---|---|---|---|
--position |
-p |
GPS position in compact format, e.g. 5430N 01920E |
5430N 01920E |
--speed |
-s |
Speed in knots (0-999) | 0.0 |
--heading |
-c |
Course in degrees (0-359) | 0.0 |
--altitude |
-a |
Altitude in meters | 15.2 |
The position format is DDMMH DDDMMH where DD/DDD are degrees, MM are minutes, and H is the hemisphere (N/S for latitude, E/W for longitude). Latitude ranges from 0000N to 9000N/S, longitude from 00000E to 18000E/W.
Network parameters (for tcp-server and stream modes):
| Option | Description | Default |
|---|---|---|
--ip |
IPv4 address (bind address for tcp-server, target for stream) | 127.0.0.1 |
--port |
Port number (1-65535) | 2020 |
--protocol |
Transport protocol: tcp, udp (stream mode only) |
tcp |
Serial parameters (for serial mode):
| Option | Description | Default |
|---|---|---|
--serial-port |
Serial device path, e.g. /dev/ttyUSB0 or COM1 (required for serial mode) |
|
--baudrate |
Baudrate: 4800, 9600, 19200, 38400, 57600, 115200 |
4800 |
Usage examples:
# Interactive mode (default) - shows menu and prompts for all inputs
nmea-gps-emulator
# Quiet mode - minimal output, useful for scripts or automation
nmea-gps-emulator --quiet
# Verbose mode - detailed diagnostic information for troubleshooting
nmea-gps-emulator --verbose
# TCP Server mode - listen on 0.0.0.0:3000 with custom position and speed
nmea-gps-emulator --mode tcp-server --ip 0.0.0.0 --port 3000 --position "4807N 01131E" --speed 12.5 --heading 90
# TCP Server mode without runtime stdin prompts, suitable for supervisors
nmea-gps-emulator --mode tcp-server --headless --ip 0.0.0.0 --port 3000
# UDP Stream mode - send to a remote host
nmea-gps-emulator --mode stream --ip 192.168.1.100 --port 5000 --protocol udp --speed 5.0
# TCP Stream mode with defaults
nmea-gps-emulator -m stream
# Serial mode - transmit on /dev/ttyUSB0 at 9600 baud
nmea-gps-emulator --mode serial --serial-port /dev/ttyUSB0 --baudrate 9600
# Serial mode on Windows
nmea-gps-emulator --mode serial --serial-port COM3
# Interactive mode pre-seeded with navigation parameters
nmea-gps-emulator --position "4807N 01131E" --speed 12.5 --heading 90In non-interactive modes, the emulator skips the menu and starts transmitting immediately using the provided (or default) parameters. After transmission starts, you can still interactively change speed and heading at runtime.
Use --headless with serial, tcp-server, or stream mode to keep the process in the foreground without reading runtime input from stdin. In headless mode, startup parameters such as --speed and --heading are fixed for the lifetime of the process. Use --quiet separately if you also want reduced output.
In interactive mode (the default, when --mode is omitted or set to interactive), any navigation parameters you pass on the command line (--position, --speed, --heading, --altitude) are used to pre-seed the corresponding prompts: the value you supplied becomes the prompt default, so pressing Enter accepts it. This means CLI navigation parameters are honored in interactive mode too — they are no longer discarded. Parameters you do not supply keep their usual interactive defaults.
Arguments that are irrelevant to the selected mode are silently ignored. For example, --serial-port is ignored when using --mode tcp-server, and --protocol is ignored when using --mode serial.
Argument validation:
The emulator validates all arguments at startup and exits with a clear error message if any are invalid:
--positionmust match the compact GPS format (e.g.5430N 01920E)--speedmust be between 0 and 999--headingmust be between 0 and 359--portmust be between 1 and 65535--ipmust be a valid IPv4 address--serial-portis required when--mode serialis specified--baudratemust be one of: 4800, 9600, 19200, 38400, 57600, 115200--headlesscannot be used with--mode interactive
Output modes:
- Default: Shows informational messages including server status, client connections, and configuration details
- Quiet mode (
-q): Only displays user prompts, errors, and essential status messages. Ideal for automated environments or when you want minimal console output - Verbose mode (
-v): Displays detailed debug information including timing metrics, thread operations, and internal state changes. Useful for debugging and development
When running in interactive mode, the following prompt should appear in the OS console:
..####...#####....####...........######..##...##..##..##..##.......####...######...####...#####..
.##......##..##..##..............##......###.###..##..##..##......##..##....##....##..##..##..##.
.##.###..#####....####...........####....##.#.##..##..##..##......######....##....##..##..#####..
.##..##..##..........##..........##......##...##..##..##..##......##..##....##....##..##..##..##.
..####...##.......####...........######..##...##...####...######..##..##....##.....####...##..##.
.................................................................................................
### Choose emulator option: ###
1 - NMEA Serial
2 - NMEA TCP Server
3 - NMEA TCP or UDP Stream
4 - Quit
>>>Contributions are welcome. Before opening a pull request, run the local checks:
make checkPull requests must have a non-empty description and a semantic title such as fix: validate serial mode or docs: update usage examples. Branch names must follow the configured naming convention:
Valid branch name prefixes:
feature/orfeat/- New featuresfix/orbugfix/- Bug fixeshotfix/- Urgent fixesdocs/- Documentation changeschore/- Maintenance tasksrefactor/- Code refactoringtest/- Test additions or modificationsci/- CI/CD changesrelease/- Release preparationdependabot/- Automated dependency updates
Example: feature/add-nmea-sentence or fix/serial-port-timeout
Pull requests that do not follow these rules will be rejected by CI validation.