Tracking & Rotor API
The Tracking & Rotor API provides full control over antenna rotors through the Hamlib rotctld TCP protocol. You can configure multiple rotors, point them at celestial objects, run continuous tracking sessions, and perform automated sky scans with RSSI measurement.
Rotor Configuration
Section titled “Rotor Configuration”List Rotors
Section titled “List Rotors”GET /api/rotorsResponse
Section titled “Response”[ { "id": 1, "name": "AZ-EL Main", "rotor_type": "azel", "host": "127.0.0.1", "port": 4533, "min_elevation": 5.0, "park_az": 0.0, "park_el": 0.0 }]Create Rotor
Section titled “Create Rotor”Register a new rotor configuration.
POST /api/rotorsRequest Body
Section titled “Request Body”{ "name": "AZ-EL Main", "rotor_type": "azel", "host": "127.0.0.1", "port": 4533, "min_elevation": 5.0, "park_az": 0.0, "park_el": 0.0}| Field | Type | Default | Description |
|---|---|---|---|
name | string | — | Unique rotor name |
rotor_type | string | azel | Rotor type |
host | string | 127.0.0.1 | rotctld host address |
port | int | 4533 | rotctld TCP port |
min_elevation | float | 5.0 | Minimum elevation limit (degrees) |
park_az | float | 0.0 | Park azimuth (degrees) |
park_el | float | 0.0 | Park elevation (degrees) |
Error Responses
Section titled “Error Responses”| Status | Description |
|---|---|
409 | Rotor with that name already exists |
Update Rotor
Section titled “Update Rotor”Update an existing rotor configuration. All fields are optional — only provided fields are updated.
PUT /api/rotors/{rotor_id}| Status | Description |
|---|---|
404 | Rotor not found |
409 | Cannot update rotor while tracking is active |
Delete Rotor
Section titled “Delete Rotor”DELETE /api/rotors/{rotor_id}| Status | Description |
|---|---|
404 | Rotor not found |
409 | Cannot delete rotor while tracking is active |
Test Connection
Section titled “Test Connection”Test the TCP connection to the rotor’s rotctld daemon.
GET /api/rotors/{rotor_id}/testResponse
Section titled “Response”{ "connected": true, "model": "Yaesu GS-232B"}If the connection fails:
{ "connected": false}Position & Pointing
Section titled “Position & Pointing”Get Current Position
Section titled “Get Current Position”Read the rotor’s current azimuth and elevation.
GET /api/rotors/{rotor_id}/positionResponse
Section titled “Response”{ "azimuth": 145.2, "elevation": 32.8, "rotor_name": "AZ-EL Main", "timestamp": "2026-02-14T22:30:00Z"}| Status | Description |
|---|---|
503 | Cannot connect to rotctld |
Point at Target
Section titled “Point at Target”Compute the current position of a target and slew the rotor to it.
POST /api/rotors/{rotor_id}/pointRequest Body
Section titled “Request Body”{ "target_type": "satellite", "target_id": "25544"}Response
Section titled “Response”{ "status": "ok", "target": "ISS (ZARYA)", "azimuth": 185.3, "elevation": 42.1}Error Responses
Section titled “Error Responses”| Status | Description |
|---|---|
400 | Target is below the horizon |
400 | Target is below minimum elevation limit |
404 | Rotor not found |
Stop Rotor
Section titled “Stop Rotor”Immediately stop rotor movement. Also stops any active tracking session.
POST /api/rotors/{rotor_id}/stopResponse
Section titled “Response”{ "status": "stopped", "rotor": "AZ-EL Main"}Park Rotor
Section titled “Park Rotor”Move the rotor to its configured park position. Stops any active tracking session first.
POST /api/rotors/{rotor_id}/parkResponse
Section titled “Response”{ "status": "parked", "rotor": "AZ-EL Main", "azimuth": 0.0, "elevation": 0.0}Continuous Tracking
Section titled “Continuous Tracking”Continuous tracking keeps the rotor pointed at a moving target (typically a satellite) by recomputing the position and sending updates to rotctld in a loop.
Start Tracking
Section titled “Start Tracking”POST /api/rotors/{rotor_id}/trackRequest Body
Section titled “Request Body”{ "target_type": "satellite", "target_id": "25544"}Response
Section titled “Response”{ "id": 17, "rotor_id": 1, "target_type": "satellite", "target_id": "25544", "started_at": "2026-02-14T22:30:00Z", "ended_at": null, "notes": null}| Status | Description |
|---|---|
409 | Rotor is already tracking a target |
Stop Tracking
Section titled “Stop Tracking”DELETE /api/rotors/{rotor_id}/trackResponse
Section titled “Response”{ "status": "stopped", "rotor": "AZ-EL Main", "session_id": 17}Get Tracking Status
Section titled “Get Tracking Status”Check whether a rotor is actively tracking and what target it is following.
GET /api/rotors/{rotor_id}/trackingResponse (active)
Section titled “Response (active)”{ "tracking": true, "target_type": "satellite", "target_id": "25544", "session_id": 17}Response (idle)
Section titled “Response (idle)”{ "tracking": false}Tracking Session History
Section titled “Tracking Session History”List previous tracking sessions for a rotor.
GET /api/rotors/{rotor_id}/sessionsQuery Parameters
Section titled “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 20 | Max results (max 100) |
Response
Section titled “Response”[ { "id": 17, "rotor_id": 1, "target_type": "satellite", "target_id": "25544", "started_at": "2026-02-14T22:30:00Z", "ended_at": "2026-02-14T22:42:00Z", "notes": null }]WebSocket: Rotor Status
Section titled “WebSocket: Rotor Status”Real-time rotor position updates at 2 Hz.
ws://host:port/ws/rotor/{rotor_id}Messages (JSON)
Section titled “Messages (JSON)”Each message includes the current rotor state:
{ "rotor": "AZ-EL Main", "azimuth": 145.2, "elevation": 32.8, "connected": true, "tracking": true, "target": "satellite", "target_id": "25544", "session_id": 17, "timestamp": "2026-02-14T22:30:00.500Z"}When the rotor is disconnected:
{ "rotor": "AZ-EL Main", "connected": false, "tracking": false, "timestamp": "2026-02-14T22:30:01Z"}Examples
Section titled “Examples”const ws = new WebSocket("ws://localhost:8000/ws/rotor/1");
ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log(`Az: ${data.azimuth}, El: ${data.elevation}`);
if (data.tracking) { console.log(`Tracking: ${data.target} ${data.target_id}`); }};import asyncioimport websocketsimport json
async def monitor_rotor(): async with websockets.connect("ws://localhost:8000/ws/rotor/1") as ws: async for message in ws: data = json.loads(message) print(f"Az: {data['azimuth']:.1f} El: {data['elevation']:.1f}")
asyncio.run(monitor_rotor())WebSocket: Target Tracking
Section titled “WebSocket: Target Tracking”Real-time target position updates at 1 Hz. Computes the current altitude, azimuth, RA/Dec, and distance for any trackable object.
ws://host:port/ws/tracking/{target_type}/{target_id}Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
target_type | string | satellite, planet, star, dso, comet, sun, moon |
target_id | string | Object identifier |
Messages (JSON)
Section titled “Messages (JSON)”{ "name": "ISS (ZARYA)", "target_type": "satellite", "target_id": "25544", "altitude_deg": 42.3, "azimuth_deg": 185.7, "distance_km": 420.5, "ra_hours": 14.23, "dec_deg": -12.45, "is_above_horizon": true, "timestamp": "2026-02-14T22:30:01Z"}Examples
Section titled “Examples”const ws = new WebSocket("ws://localhost:8000/ws/tracking/satellite/25544");
ws.onmessage = (event) => { const pos = JSON.parse(event.data); if (pos.is_above_horizon) { console.log(`${pos.name}: Alt ${pos.altitude_deg.toFixed(1)} Az ${pos.azimuth_deg.toFixed(1)}`); }};Sky Scanning
Section titled “Sky Scanning”The sky scan system performs automated az/el grid scans with RSSI (signal strength) measurement at each point. This is used for RF signal mapping and antenna pattern measurement.
Start Scan
Section titled “Start Scan”POST /api/skyscan/startRequest Body
Section titled “Request Body”{ "rotor_id": 1, "az_start": 0.0, "az_end": 360.0, "el_start": 18.0, "el_end": 65.0, "step_deg": 2.0, "rssi_iterations": 10}| Field | Type | Default | Description |
|---|---|---|---|
rotor_id | int | — | Rotor to use for the scan |
az_start | float | 0.0 | Start azimuth (degrees) |
az_end | float | 360.0 | End azimuth (degrees) |
el_start | float | 18.0 | Start elevation (degrees) |
el_end | float | 65.0 | End elevation (degrees) |
step_deg | float | 2.0 | Step size in degrees |
rssi_iterations | int | 10 | Number of RSSI samples at each grid point |
Response
Section titled “Response”{ "id": 5, "rotor_id": 1, "status": "pending", "az_start": 0.0, "az_end": 360.0, "el_start": 18.0, "el_end": 65.0, "step_deg": 2.0, "rssi_iterations": 10, "total_points": 4320, "completed_points": 0, "progress_pct": 0.0, "started_at": null, "finished_at": null, "notes": null}List Scan Sessions
Section titled “List Scan Sessions”GET /api/skyscan/sessionsGet Scan Status
Section titled “Get Scan Status”GET /api/skyscan/{session_id}Get Scan Data
Section titled “Get Scan Data”Retrieve all collected data points for a completed (or in-progress) scan.
GET /api/skyscan/{session_id}/dataResponse
Section titled “Response”[ { "azimuth": 0.0, "elevation": 18.0, "rssi_reads": 10, "rssi_average": -85, "rssi_current": -83, "measured_at": "2026-02-14T22:35:00Z" }]Scan Control
Section titled “Scan Control”| Endpoint | Description |
|---|---|
POST /api/skyscan/{session_id}/pause | Pause a running scan |
POST /api/skyscan/{session_id}/resume | Resume a paused scan |
POST /api/skyscan/{session_id}/cancel | Cancel a running, paused, or pending scan |
WebSocket: Scan Progress
Section titled “WebSocket: Scan Progress”Real-time scan progress updates. The connection closes automatically when the scan finishes, is cancelled, or fails.
ws://host:port/ws/skyscan/{session_id}Messages (JSON)
Section titled “Messages (JSON)”{ "session_id": 5, "status": "running", "point": { "azimuth": 45.0, "elevation": 22.0, "rssi_reads": 10, "rssi_average": -82, "rssi_current": -80, "measured_at": "2026-02-14T22:36:00Z" }, "progress_pct": 12.5, "completed": 540, "total": 4320}The status field progresses through: pending -> running -> completed | cancelled | failed.