Skip to content

Commit bfbd53e

Browse files
Add /llm endpoint
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent c7a1526 commit bfbd53e

4 files changed

Lines changed: 371 additions & 0 deletions

File tree

src/endpoints.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ pub async fn info() -> Json<Info> {
9797
Json(info)
9898
}
9999

100+
#[api_v2_operation]
101+
/// Provide instructions on how to use this API
102+
pub async fn llm() -> actix_web::Result<HttpResponse> {
103+
let content = load_html_file("llm").unwrap();
104+
HttpResponse::Ok()
105+
.content_type("text/plain")
106+
.body(content)
107+
.await
108+
}
109+
100110
#[api_v2_operation]
101111
/// Provides an object containing all MAVLink messages received by the service
102112
pub async fn mavlink(req: HttpRequest) -> actix_web::Result<HttpResponse> {

src/html/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
<a href="watcher.html?path=mavlink/vehicles/1/components/1/messages/HEARTBEAT/message/mavtype/type">
2525
watcher.html?path=mavlink/vehicles/1/components/1/messages/HEARTBEAT/message/mavtype/type
2626
</a><br/><br/>
27+
28+
For LLM usage, check <a href="/llm">/llm</a>.<br/>
29+
30+
For further endpoints documentaiton, check <a href="/docs">/docs</a>.<br/><br/>
2731
</div>
2832
</div>
2933

src/html/llm

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# mavlink2rest API Reference
2+
3+
mavlink2rest exposes a RESTful API over the MAVLink protocol.
4+
Supports the ardupilotmega MAVLink dialect (includes common, icarous, uavionix).
5+
All JSON responses are pretty-printed.
6+
7+
You are reading this file from the /llm endpoint.
8+
The base URL for all API requests is the same origin as this endpoint.
9+
For example, if you accessed this file at http://192.168.0.10:8088/llm,
10+
then the base URL for all examples below is http://192.168.0.10:8088.
11+
Replace http://0.0.0.0:8088 in the examples with your actual address.
12+
13+
## Data Model
14+
15+
Messages are organized hierarchically:
16+
vehicles/{system_id}/components/{component_id}/messages/{MESSAGE_NAME}
17+
18+
Each message entry contains:
19+
- message: the MAVLink message fields
20+
- status.time.first_update: ISO 8601 timestamp of first reception
21+
- status.time.last_update: ISO 8601 timestamp of last reception
22+
- status.time.counter: number of times received
23+
- status.time.frequency: reception rate in Hz
24+
25+
## Endpoints
26+
27+
### GET /v1/mavlink
28+
Returns all MAVLink messages from all vehicles and components.
29+
30+
Example:
31+
curl http://0.0.0.0:8088/v1/mavlink
32+
33+
### GET /v1/mavlink/{path}
34+
Returns a specific nested value using JSON pointer-style path traversal.
35+
36+
Examples:
37+
# Full ATTITUDE message with status
38+
curl http://0.0.0.0:8088/v1/mavlink/vehicles/1/components/1/messages/ATTITUDE
39+
40+
# Single field (note: fields are under "message/")
41+
curl http://0.0.0.0:8088/v1/mavlink/vehicles/1/components/1/messages/ATTITUDE/message/roll
42+
Returns: 0.14598171412944794
43+
44+
# Status metadata
45+
curl http://0.0.0.0:8088/v1/mavlink/vehicles/1/components/1/messages/ATTITUDE/status/time/frequency
46+
Returns: 10.047618865966797
47+
48+
# Last update timestamp
49+
curl http://0.0.0.0:8088/v1/mavlink/vehicles/1/components/1/messages/ATTITUDE/status/time/last_update
50+
Returns: "2026-03-29T07:14:29.700718126-03:00"
51+
52+
Response for a full message (GET .../messages/ATTITUDE):
53+
{
54+
"message": {
55+
"type": "ATTITUDE",
56+
"time_boot_ms": 49624892,
57+
"roll": 0.14528612792491913,
58+
"pitch": 0.012282482348382473,
59+
"yaw": -2.570211410522461,
60+
"rollspeed": -0.00008160166908055544,
61+
"pitchspeed": -0.0007044915109872818,
62+
"yawspeed": -0.00017313705757260323
63+
},
64+
"status": {
65+
"time": {
66+
"first_update": "2026-03-29T07:10:52.701061519-03:00",
67+
"last_update": "2026-03-29T07:14:19.980848695-03:00",
68+
"counter": 2075,
69+
"frequency": 10.024154663085938
70+
}
71+
}
72+
}
73+
74+
Invalid paths return the string "None" with HTTP 200.
75+
76+
### GET /v1/helper/mavlink?name={MESSAGE_NAME}
77+
Returns a JSON template for any MAVLink message with default values.
78+
Use this to discover the correct JSON structure before POSTing.
79+
80+
Examples:
81+
curl "http://0.0.0.0:8088/v1/helper/mavlink?name=HEARTBEAT"
82+
curl "http://0.0.0.0:8088/v1/helper/mavlink?name=COMMAND_LONG"
83+
curl "http://0.0.0.0:8088/v1/helper/mavlink?name=COMMAND_INT"
84+
curl "http://0.0.0.0:8088/v1/helper/mavlink?name=PARAM_SET"
85+
curl "http://0.0.0.0:8088/v1/helper/mavlink?name=MANUAL_CONTROL"
86+
87+
Response for COMMAND_LONG:
88+
{
89+
"header": {
90+
"system_id": 255,
91+
"component_id": 0,
92+
"sequence": 0
93+
},
94+
"message": {
95+
"type": "COMMAND_LONG",
96+
"param1": 0.0,
97+
"param2": 0.0,
98+
"param3": 0.0,
99+
"param4": 0.0,
100+
"param5": 0.0,
101+
"param6": 0.0,
102+
"param7": 0.0,
103+
"command": {
104+
"type": "MAV_CMD_NAV_WAYPOINT"
105+
},
106+
"target_system": 0,
107+
"target_component": 0,
108+
"confirmation": 0
109+
}
110+
}
111+
112+
Invalid message names return HTTP 404 with "Invalid message name."
113+
114+
### POST /v1/mavlink
115+
Sends a MAVLink message to the vehicle. The body must be a JSON object with
116+
"header" and "message" fields matching the structure from the helper endpoint.
117+
118+
The "header" fields:
119+
- system_id: sender system ID (typically 255 for a GCS)
120+
- component_id: sender component ID (typically 0 or 240 for a GCS)
121+
- sequence: message sequence number (typically 0)
122+
123+
The "message" must include:
124+
- type: the MAVLink message type name (e.g. "COMMAND_LONG")
125+
- All required fields for that message type
126+
127+
Returns HTTP 200 on success, HTTP 404 with error details on failure.
128+
129+
#### Example: Arm the vehicle
130+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
131+
-H "Content-Type: application/json" \
132+
-d '{
133+
"header": {
134+
"system_id": 255,
135+
"component_id": 0,
136+
"sequence": 0
137+
},
138+
"message": {
139+
"type": "COMMAND_LONG",
140+
"param1": 1.0,
141+
"param2": 0.0,
142+
"param3": 0.0,
143+
"param4": 0.0,
144+
"param5": 0.0,
145+
"param6": 0.0,
146+
"param7": 0.0,
147+
"command": {
148+
"type": "MAV_CMD_COMPONENT_ARM_DISARM"
149+
},
150+
"target_system": 1,
151+
"target_component": 1,
152+
"confirmation": 0
153+
}
154+
}'
155+
156+
#### Example: Disarm the vehicle
157+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
158+
-H "Content-Type: application/json" \
159+
-d '{
160+
"header": {
161+
"system_id": 255,
162+
"component_id": 0,
163+
"sequence": 0
164+
},
165+
"message": {
166+
"type": "COMMAND_LONG",
167+
"param1": 0.0,
168+
"param2": 0.0,
169+
"param3": 0.0,
170+
"param4": 0.0,
171+
"param5": 0.0,
172+
"param6": 0.0,
173+
"param7": 0.0,
174+
"command": {
175+
"type": "MAV_CMD_COMPONENT_ARM_DISARM"
176+
},
177+
"target_system": 1,
178+
"target_component": 1,
179+
"confirmation": 0
180+
}
181+
}'
182+
183+
#### Example: Set flight mode (e.g. MANUAL mode = custom_mode 19 for ArduSub)
184+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
185+
-H "Content-Type: application/json" \
186+
-d '{
187+
"header": {
188+
"system_id": 255,
189+
"component_id": 0,
190+
"sequence": 0
191+
},
192+
"message": {
193+
"type": "COMMAND_LONG",
194+
"param1": 19.0,
195+
"param2": 0.0,
196+
"param3": 0.0,
197+
"param4": 0.0,
198+
"param5": 0.0,
199+
"param6": 0.0,
200+
"param7": 0.0,
201+
"command": {
202+
"type": "MAV_CMD_DO_SET_MODE"
203+
},
204+
"target_system": 1,
205+
"target_component": 1,
206+
"confirmation": 0
207+
}
208+
}'
209+
210+
#### Example: Set a parameter
211+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
212+
-H "Content-Type: application/json" \
213+
-d '{
214+
"header": {
215+
"system_id": 255,
216+
"component_id": 0,
217+
"sequence": 0
218+
},
219+
"message": {
220+
"type": "PARAM_SET",
221+
"param_value": 1.0,
222+
"target_system": 1,
223+
"target_component": 1,
224+
"param_id": "SURFACE_DEPTH",
225+
"param_type": {
226+
"type": "MAV_PARAM_TYPE_REAL32"
227+
}
228+
}
229+
}'
230+
231+
#### Example: Send manual control input
232+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
233+
-H "Content-Type: application/json" \
234+
-d '{
235+
"header": {
236+
"system_id": 255,
237+
"component_id": 0,
238+
"sequence": 0
239+
},
240+
"message": {
241+
"type": "MANUAL_CONTROL",
242+
"x": 0,
243+
"y": 0,
244+
"z": 500,
245+
"r": 0,
246+
"buttons": 0,
247+
"target": 1,
248+
"buttons2": 0,
249+
"enabled_extensions": 0,
250+
"s": 0,
251+
"t": 0,
252+
"aux1": 0,
253+
"aux2": 0,
254+
"aux3": 0,
255+
"aux4": 0,
256+
"aux5": 0,
257+
"aux6": 0
258+
}
259+
}'
260+
261+
#### Example: Request all parameters from the vehicle
262+
curl -X POST http://0.0.0.0:8088/v1/mavlink \
263+
-H "Content-Type: application/json" \
264+
-d '{
265+
"header": {
266+
"system_id": 255,
267+
"component_id": 0,
268+
"sequence": 0
269+
},
270+
"message": {
271+
"type": "PARAM_REQUEST_LIST",
272+
"target_system": 1,
273+
"target_component": 1
274+
}
275+
}'
276+
277+
### GET /info
278+
Returns service version information.
279+
280+
Example:
281+
curl http://0.0.0.0:8088/info
282+
283+
Response:
284+
{
285+
"version": 0,
286+
"service": {
287+
"name": "mavlink2rest",
288+
"version": "1.0.0",
289+
"sha": "8c5df007c2726af9db3ef98f6e12d5400ba2e718",
290+
"build_date": "2026-03-29T10:05:39.95884241Z",
291+
"authors": "Patrick José Pereira <patrickelectric@gmail.com>"
292+
}
293+
}
294+
295+
### WebSocket: /v1/ws/mavlink
296+
Streams MAVLink messages in real-time over WebSocket.
297+
Accepts an optional "filter" query parameter with a regex to match message names.
298+
299+
ws://0.0.0.0:8088/v1/ws/mavlink # all messages
300+
ws://0.0.0.0:8088/v1/ws/mavlink?filter=.* # all messages (explicit)
301+
ws://0.0.0.0:8088/v1/ws/mavlink?filter=ATTITUDE # only ATTITUDE
302+
ws://0.0.0.0:8088/v1/ws/mavlink?filter=RC_.* # RC_CHANNELS and RC_CHANNELS_RAW
303+
304+
Each WebSocket frame is a JSON object with "header" and "message":
305+
{
306+
"header": {
307+
"system_id": 1,
308+
"component_id": 1,
309+
"sequence": 37
310+
},
311+
"message": {
312+
"type": "RC_CHANNELS",
313+
"chan1_raw": 1500,
314+
"chan2_raw": 1500,
315+
...
316+
}
317+
}
318+
319+
You can also send MAVLink messages through the WebSocket by writing a JSON
320+
string in the same header+message format. The server will forward it to the
321+
vehicle and reply with the result.
322+
323+
### GET /docs
324+
Interactive Swagger UI for exploring the API.
325+
326+
### GET /docs.json
327+
Raw OpenAPI/Swagger 2.0 specification in JSON format.
328+
329+
## Common Patterns
330+
331+
### Discovering available vehicles and messages
332+
1. GET /v1/mavlink to see all vehicles, components, and messages.
333+
2. Navigate the hierarchy: vehicles/{id}/components/{id}/messages/{name}
334+
335+
### Sending a command to the vehicle
336+
1. GET /v1/helper/mavlink?name=COMMAND_LONG to get the template.
337+
2. Fill in: command.type, param1-param7, target_system, target_component.
338+
3. POST /v1/mavlink with the filled-in JSON.
339+
340+
### Monitoring a value in real-time
341+
Option A (polling): GET /v1/mavlink/vehicles/1/components/1/messages/{MSG}/message/{field}
342+
Option B (streaming): Connect a WebSocket to /v1/ws/mavlink?filter={MSG}
343+
344+
### Getting a message template for any MAVLink message
345+
GET /v1/helper/mavlink?name={MESSAGE_NAME}
346+
Replace {MESSAGE_NAME} with any valid ardupilotmega message name, e.g.:
347+
HEARTBEAT, ATTITUDE, COMMAND_LONG, COMMAND_INT, PARAM_SET,
348+
MANUAL_CONTROL, PARAM_REQUEST_LIST, SET_MODE, GPS_RAW_INT,
349+
GLOBAL_POSITION_INT, BATTERY_STATUS, SYS_STATUS, RC_CHANNELS,
350+
MISSION_ITEM, MISSION_COUNT, MISSION_REQUEST_LIST, etc.
351+
352+
## Notes
353+
- Enum fields use {"type": "ENUM_VALUE_NAME"} format (e.g. {"type": "MAV_CMD_COMPONENT_ARM_DISARM"}).
354+
- Bitfield fields use pipe-separated strings (e.g. "MAV_MODE_FLAG_SAFETY_ARMED | MAV_MODE_FLAG_CUSTOM_MODE_ENABLED").
355+
- The POST endpoint accepts JSON5 (allows comments and trailing commas).
356+
- All endpoints under /v1/ are also available without the prefix (e.g. /mavlink works the same as /v1/mavlink).

src/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fn add_v1_paths(scope: Scope) -> Scope {
2727
.route("/mavlink", web::get().to(endpoints::mavlink))
2828
.route("/mavlink", web::post().to(endpoints::mavlink_post))
2929
.route(r"/mavlink/{path:.*}", web::get().to(endpoints::mavlink))
30+
.route("/llm", web::get().to(endpoints::llm))
3031
.service(web::resource("/ws/mavlink").route(web::get().to(endpoints::websocket)))
3132
}
3233

0 commit comments

Comments
 (0)