# Pull relays
Available in YCCaster 2.1.0 and later. Download YCCaster or follow the Quick Start.
YCCaster can fetch one NTRIP stream and publish it to a local mountpoint. Relay settings
are managed locally through relays.yml or the YCCaster HTTP API. Source metadata,
upstream credentials and the local mountpoint are owned by the relay configuration.
Only direction: pull is implemented. Push, VRS/per-rover positioning, upstream
failover and on-demand connections are outside this implementation.
# Configuration and restart
By default YCCaster reads relays.yml next to caster.yml. Override the path in
caster.yml:
configuration:
relays-file: relays.yml
api:
address: 127.0.0.1:8080
api-keys:
- replace-with-a-secret-key
A missing file means an empty list. An invalid file fails startup. Each entry must have a unique canonical UUID. Enabled relays start automatically, including when the external application API is unavailable. Disabled entries stay disabled. Edit the YAML manually only while YCCaster is stopped: there is no file watcher.
HTTP mutations serialize and save the complete configuration through a temporary
file, file sync and atomic rename, before changing the runtime. On Linux/macOS
both the temporary file and the resulting file have mode 0600; the service
user needs write access to the containing directory. Settings contain plaintext
upstream passwords, so protect backups too. Runtime state and traffic are never
written to this file. A no-op PUT and reconnect do not rewrite it.
Example relays.yml (demo coordinates and credentials):
- id: 9a132980-b753-4f64-89af-412216f34c40
name: External base
direction: pull
enabled: true
mountpoint: LOCAL_BASE
host: caster.example.com
port: 2101
tls: false
source_mountpoint: EXTERNAL_BASE
username: demo
password: replace-me
ntrip_version: 2
nearest_group: NEAREST
description:
identifier: External fixed base
format: RTCM 3
latitude: 35.17
longitude: 33.36
authentication: B
gga:
enabled: false
latitude: 35.17
longitude: 33.36
height: 150
height_reference: orthometric
geoid_separation: 30
quality: 1
satellites: 12
hdop: 1
interval: 10
Mountpoint/group names use ASCII letters, digits, _, -, ., ~; omit the
leading slash. host is a DNS name or unbracketed IP, without scheme or port.
Default upstream port is 2101 (443 with TLS); default NTRIP version is 2.
TLS verifies the certificate and hostname against system trust, with TLS 1.2
minimum. NTRIP 1 ICY and NTRIP 2 HTTP/chunked responses are supported. Redirects
are not followed; response headers are bounded and never logged.
# HTTP API
All routes use the existing X-API-Key authorization. The admin application must
reach YCCaster through a trusted backend/connection; the YCCaster HTTP API itself
does not add TLS. Successful JSON responses retain the existing {"data": ...}
envelope. Configuration responses omit password and include password_set.
Responses are marked Cache-Control: no-store.
| Method | Route | Behavior |
|---|---|---|
| POST | /relays | Create a relay, generate its UUID; returns 201 |
| GET | /relays | List redacted configuration and status |
| GET | /relays/{id} | Read one relay |
| PUT | /relays/{id} | Replace configuration, or create using a canonical UUID |
| DELETE | /relays/{id} | Persist deletion, stop and release the name; returns 204 |
| POST | /relays/{id}/reconnect | Restart an enabled relay without changing the file |
POST/PUT take the configuration object from the example above as JSON. POST
omits id; PUT can omit it or specify the URL ID. PUT is a full replacement,
not a patch. Omitted password preserves an existing password; "password":""
clears it. To disable a relay, PUT its configuration with enabled: false.
Do not send response-only fields status or password_set back in PUT.
Unknown input fields are rejected; request bodies are limited to 64 KiB.
Example creation body (send with your API client and X-API-Key):
{
"direction": "pull",
"enabled": true,
"mountpoint": "LOCAL_BASE",
"host": "caster.example.com",
"port": 2101,
"source_mountpoint": "EXTERNAL_BASE",
"ntrip_version": 2,
"username": "demo",
"password": "replace-me"
}
Changes to configuration restart that relay only. An unchanged PUT preserves the connection. UUID stays unchanged; reconnect/configuration changes start a new run, and each connection attempt gets a new connection ID. Deleting and creating again produces a new relay UUID. A successful save acknowledges the desired configuration, not successful upstream authentication or data reception. New credentials are not rolled back when an upstream rejects them.
Errors: 400 invalid input, 401/403 API authorization, 404 missing ID, 409 occupied mountpoint or reconnect of a disabled relay, 503 unavailable/stopped manager, 500 persistence failure (runtime unchanged). Upstream errors are asynchronous status/events. If a client loses the HTTP response, read the relay list/status before retrying creation: POST does not have an idempotency key.
# Sources, rovers and limits
A relay defines its own local source; a matching entry in mountpoints.yml is not required. Existing client authorization still applies to rovers connecting to its stream. Incoming-base passwords and upstream relay passwords are separate.
- An enabled relay reserves the local mountpoint, including during retries. Direct bases receive "mountpoint taken". Conflicting enabled relays are rejected. Disabling/deleting releases the name; direct sources are never evicted.
- There is no separate relay count limit. Established relay source sessions use the same source license hook as direct bases. An offline reservation does not consume a source connection slot. License refusal is retried.
- A source becomes available to clients, sourcetable and nearest selection on its first received bytes. It disappears when the upstream connection ends or its read timeout expires. This is byte-level liveness, not RTCM validation.
nearest_groupanddescription.latitude/longitudedescribe the fixed base, independently of optional GGA coordinates. Supply the actual base coordinates; a missing numeric coordinate defaults to zero. Zero latitude/longitude are valid.- Existing rovers keep their name-based subscription across a brief upstream interruption. The existing 30-second no-data timeout still closes them if data does not resume. Existing clients remain visible and count toward the per-name client limit after upstream reconnection.
- Nearest-group selection continues to use the existing rover GGA behavior. This change does not implement immediate switching on source loss without new GGA.
- No additional upstream GGA is generated from rover messages.
DNS/connect/TLS establishment has a five-second budget. NTRIP request/response and GGA writes have five-second deadlines. First data and subsequent reads use 30 seconds. Retry delay doubles from 1 to 30 seconds, plus 0–25% jitter; receipt of data resets the retry delay. These values are fixed in this first version.
All detected connections to this caster's own NTRIP endpoint are rejected, even to a different local mountpoint, to avoid local chains/cycles. Checks include configured caster host/address, interface/loopback IPs, and resolved upstream IPs before every dial. Private networks are otherwise allowed. This is protection against local loops, not distributed cycle detection across external casters; unknown NAT/port-forward aliases still need to be avoided by the operator.
# Fixed GGA
GGA is opt-in and sent immediately after NTRIP acceptance, then every interval
seconds (1–3600, default 10). Latitude/longitude are decimal degrees. Height and
geoid separation are metres. With height_reference: ellipsoidal, the emitted
orthometric altitude is height - geoid_separation; with orthometric, height
is emitted directly. UTC and checksum are generated by YCCaster.
quality (0–8), satellites (0–99), and hdop (0–99.9) are explicitly supplied
synthetic values, not receiver observations; numeric defaults are zero. Choose
values appropriate to the upstream. GGA altitude/geoid fields follow the
receiver GGA field definition (opens new window).
A failed GGA write closes the connection and triggers reconnection. Status
reports last_gga_sent_at and gga_sent; individual sends are not lifecycle events.
# Events and status
See event sender configuration for file and HTTP examples.
The existing event envelope (name, UNIX-seconds timestamp, data) and delivery
mechanism are reused. Add relay to a sender's types to subscribe to all relay
events (or leave types empty to receive all events). relay-status is emitted
every 10 seconds, including disabled relays.
Lifecycle events:
relay-startedrelay-connectingrelay-connected(NTRIP request accepted, may have no data yet)relay-streaming(first bytes published)relay-connect-failed(no accepted connection; no source session)relay-disconnectedrelay-retry-scheduledrelay-stoppedrelay-status
Payload contains relay_id, direction, mountpoint, run_id, sequence,
connection_id, state, reason, last_error, attempt number, connection/last-data
and retry times, bytes received and GGA counters. Sequence increases within a run;
traffic counters are cumulative within that run. A new attempt retains last-data
history; connected_at resets. Current state determines availability, not a
historical last-data timestamp. Status contains no upstream credentials or raw
server response text. The current snapshot is also available through GET.
Established source sessions additionally generate existing ntrip-session-started,
ntrip-session-status, ntrip-session-server-data, ntrip-session-ended, with
relay_id, direction: pull, source_kind: relay, type: server. Lifecycle and
source session events share the connection ID. Existing rover sessions retain
their original source-session link during a transparent upstream reconnect.
Reasons include dns_error, connect_timeout, connection_refused, tls_error,
request_write_error, response_timeout, invalid_response, unauthorized,
mountpoint_not_found, upstream_rejected, source_limit, relay_loop,
local_mountpoint_conflict, first_data_timeout, read_timeout, read_error,
remote_closed, gga_write_error, and deliberate stops: disabled, deleted,
config_changed, manual_reconnect, caster_shutdown.
Event delivery retains the existing best-effort behavior. No durable retry queue or event deduplication was added; history can have gaps during delivery failures.