# 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_group and description.latitude/longitude describe 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-started
  • relay-connecting
  • relay-connected (NTRIP request accepted, may have no data yet)
  • relay-streaming (first bytes published)
  • relay-connect-failed (no accepted connection; no source session)
  • relay-disconnected
  • relay-retry-scheduled
  • relay-stopped
  • relay-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.