@totemsdk/act-mavlink 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +60 -0
- package/README.md +108 -2
- package/dist/adapter.d.ts +22 -0
- package/dist/adapter.js +403 -0
- package/dist/cjs/adapter.d.ts +22 -0
- package/dist/cjs/adapter.js +406 -0
- package/dist/cjs/conformance.d.ts +28 -0
- package/dist/cjs/conformance.js +122 -0
- package/dist/cjs/effects.d.ts +16 -0
- package/dist/cjs/effects.js +19 -0
- package/dist/cjs/envelope.d.ts +38 -0
- package/dist/cjs/envelope.js +98 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/profile.d.ts +36 -0
- package/dist/cjs/profile.js +167 -0
- package/dist/cjs/transport.d.ts +56 -0
- package/dist/cjs/transport.js +33 -0
- package/dist/conformance.d.ts +28 -0
- package/dist/conformance.js +119 -0
- package/dist/effects.d.ts +16 -0
- package/dist/effects.js +16 -0
- package/dist/envelope.d.ts +38 -0
- package/dist/envelope.js +92 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +13 -0
- package/dist/profile.d.ts +36 -0
- package/dist/profile.js +159 -0
- package/dist/transport.d.ts +56 -0
- package/dist/transport.js +28 -0
- package/package.json +72 -3
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-025 §5.3 — governed MAVLink operations.
|
|
3
|
+
*
|
|
4
|
+
* `startMission` carries the authorized envelope digest; a start whose
|
|
5
|
+
* recomputed digest differs from the authorized one fails closed before
|
|
6
|
+
* dispatch. Acceptance is never completion: `startMission` reconciles through
|
|
7
|
+
* vehicle telemetry and only reports `confirmed` on observed arrival.
|
|
8
|
+
*/
|
|
9
|
+
import { defaultUnitRegistry, resourceIdKey, } from '@totemsdk/act-core';
|
|
10
|
+
import { deriveFlightEffects } from './effects.js';
|
|
11
|
+
import { assertValidMissionEnvelope, distanceMetres, envelopeDigest, } from './envelope.js';
|
|
12
|
+
import { MAVLINK_PROTOCOL, mavlinkErrorTaxonomy, vehicleResourceId, } from './profile.js';
|
|
13
|
+
import { assertEnvelopeBinding, MavlinkTransportError, } from './transport.js';
|
|
14
|
+
function decimal(value) {
|
|
15
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(9)));
|
|
16
|
+
}
|
|
17
|
+
function quantityValue(value, unit, units) {
|
|
18
|
+
if (value === undefined || value === null)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
21
|
+
return value;
|
|
22
|
+
if (typeof value === 'object' && 'value' in value && 'unit' in value) {
|
|
23
|
+
return units.convert(value, unit).value;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function numberParameter(value) {
|
|
28
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
29
|
+
return value;
|
|
30
|
+
throw new MavlinkTransportError('Invalid', `expected a number, got '${String(value)}'`);
|
|
31
|
+
}
|
|
32
|
+
function stringParameter(value) {
|
|
33
|
+
if (typeof value === 'string' && value.length > 0)
|
|
34
|
+
return value;
|
|
35
|
+
throw new MavlinkTransportError('Invalid', `expected a non-empty string, got '${String(value)}'`);
|
|
36
|
+
}
|
|
37
|
+
function waypointIssues(value) {
|
|
38
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
39
|
+
return 'at least one waypoint is required';
|
|
40
|
+
if (value.length > 100)
|
|
41
|
+
return 'mission exceeds the 100-waypoint limit';
|
|
42
|
+
for (const [index, entry] of value.entries()) {
|
|
43
|
+
if (!entry || typeof entry !== 'object')
|
|
44
|
+
return `waypoint ${index} must be an object`;
|
|
45
|
+
const waypoint = entry;
|
|
46
|
+
if (typeof waypoint.latitudeDeg !== 'number')
|
|
47
|
+
return `waypoint ${index} latitudeDeg must be a number`;
|
|
48
|
+
if (typeof waypoint.longitudeDeg !== 'number')
|
|
49
|
+
return `waypoint ${index} longitudeDeg must be a number`;
|
|
50
|
+
if (typeof waypoint.altitudeMetres !== 'number')
|
|
51
|
+
return `waypoint ${index} altitudeMetres must be a number`;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export function createMavlinkDefinitions(options) {
|
|
56
|
+
const units = options.units ?? defaultUnitRegistry();
|
|
57
|
+
const taxonomy = mavlinkErrorTaxonomy();
|
|
58
|
+
const transport = options.transport;
|
|
59
|
+
const arrivalTolerance = options.arrivalToleranceMetres ?? 15;
|
|
60
|
+
function vehicleOf(vehicleId) {
|
|
61
|
+
const vehicle = options.vehicles.find((candidate) => candidate.vehicleId === vehicleId);
|
|
62
|
+
if (!vehicle) {
|
|
63
|
+
throw new MavlinkTransportError('Invalid', `vehicle '${vehicleId}' is not registered`);
|
|
64
|
+
}
|
|
65
|
+
return vehicle;
|
|
66
|
+
}
|
|
67
|
+
function resourceFor(vehicleId) {
|
|
68
|
+
return resourceIdKey(vehicleResourceId(options.site, vehicleId));
|
|
69
|
+
}
|
|
70
|
+
function envelopeFrom(params) {
|
|
71
|
+
const vehicleId = stringParameter(params.vehicle);
|
|
72
|
+
vehicleOf(vehicleId);
|
|
73
|
+
const altitude = quantityValue(params.maxAltitudeMetres, 'm', units);
|
|
74
|
+
const speed = quantityValue(params.maxSpeedMetresPerSecond, 'm/s', units);
|
|
75
|
+
const duration = quantityValue(params.maxDurationSeconds, 's', units);
|
|
76
|
+
if (altitude === undefined)
|
|
77
|
+
throw new MavlinkTransportError('Invalid', 'maxAltitudeMetres is required');
|
|
78
|
+
if (speed === undefined)
|
|
79
|
+
throw new MavlinkTransportError('Invalid', 'maxSpeedMetresPerSecond is required');
|
|
80
|
+
if (duration === undefined)
|
|
81
|
+
throw new MavlinkTransportError('Invalid', 'maxDurationSeconds is required');
|
|
82
|
+
const geofenceLatitude = params.geofenceLatitudeDeg;
|
|
83
|
+
const geofenceLongitude = params.geofenceLongitudeDeg;
|
|
84
|
+
const geofenceRadius = params.geofenceRadiusMetres;
|
|
85
|
+
const hasGeofence = geofenceLatitude !== undefined || geofenceLongitude !== undefined || geofenceRadius !== undefined;
|
|
86
|
+
if (hasGeofence && (geofenceLatitude === undefined || geofenceLongitude === undefined || geofenceRadius === undefined)) {
|
|
87
|
+
throw new MavlinkTransportError('Invalid', 'geofence requires latitude, longitude and radius');
|
|
88
|
+
}
|
|
89
|
+
const envelope = {
|
|
90
|
+
envelopeId: stringParameter(params.envelopeId),
|
|
91
|
+
vehicle: vehicleId,
|
|
92
|
+
waypoints: params.waypoints,
|
|
93
|
+
maxAltitudeMetres: decimal(altitude),
|
|
94
|
+
maxSpeedMetresPerSecond: decimal(speed),
|
|
95
|
+
maxDurationSeconds: decimal(duration),
|
|
96
|
+
...(hasGeofence
|
|
97
|
+
? {
|
|
98
|
+
geofence: {
|
|
99
|
+
latitudeDeg: numberParameter(geofenceLatitude),
|
|
100
|
+
longitudeDeg: numberParameter(geofenceLongitude),
|
|
101
|
+
radiusMetres: numberParameter(geofenceRadius),
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
: {}),
|
|
105
|
+
};
|
|
106
|
+
assertValidMissionEnvelope(envelope);
|
|
107
|
+
return envelope;
|
|
108
|
+
}
|
|
109
|
+
function envelopeParameters() {
|
|
110
|
+
return [
|
|
111
|
+
{ name: 'envelopeId', type: 'string', required: true, description: 'Authorized plan id.' },
|
|
112
|
+
{ name: 'vehicle', type: 'string', required: true, description: 'Vehicle id.' },
|
|
113
|
+
{
|
|
114
|
+
name: 'waypoints', type: 'array', required: true,
|
|
115
|
+
description: 'Ordered WGS-84 waypoints (latitudeDeg, longitudeDeg, altitudeMetres).',
|
|
116
|
+
validation: waypointIssues,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'maxAltitudeMetres', type: 'quantity', required: true,
|
|
120
|
+
dimension: 'length', unit: 'm', description: 'Altitude ceiling.',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'maxSpeedMetresPerSecond', type: 'quantity', required: true,
|
|
124
|
+
dimension: 'linearSpeed', unit: 'm/s', description: 'Ground-speed ceiling.',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'maxDurationSeconds', type: 'quantity', required: true,
|
|
128
|
+
dimension: 'time', unit: 's', description: 'Mission duration ceiling.',
|
|
129
|
+
},
|
|
130
|
+
{ name: 'geofenceLatitudeDeg', type: 'number', required: false },
|
|
131
|
+
{ name: 'geofenceLongitudeDeg', type: 'number', required: false },
|
|
132
|
+
{ name: 'geofenceRadiusMetres', type: 'number', required: false },
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
function telemetryConfirmed(telemetry, check) {
|
|
136
|
+
return check(telemetry);
|
|
137
|
+
}
|
|
138
|
+
const definitions = [
|
|
139
|
+
{
|
|
140
|
+
kind: 'flight:uploadMission',
|
|
141
|
+
description: 'Upload an authorized mission plan to the autopilot (no dispatch of motion).',
|
|
142
|
+
version: 1,
|
|
143
|
+
capability: 'industrial:action',
|
|
144
|
+
effect: 'write',
|
|
145
|
+
protocol: MAVLINK_PROTOCOL,
|
|
146
|
+
errorTaxonomy: taxonomy,
|
|
147
|
+
schema: { parameters: envelopeParameters(), context: [] },
|
|
148
|
+
// Uploading a plan moves nothing; a failed upload is simply aborted.
|
|
149
|
+
policy: { failureMode: 'abort', timeoutMs: 30000 },
|
|
150
|
+
async prepare(params) {
|
|
151
|
+
const envelope = envelopeFrom(params);
|
|
152
|
+
const digest = envelopeDigest(envelope);
|
|
153
|
+
return {
|
|
154
|
+
resourceId: resourceFor(envelope.vehicle),
|
|
155
|
+
command: { action: 'uploadMission', envelope, envelopeDigest: digest },
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
deriveEffects(op) {
|
|
159
|
+
const command = op.command;
|
|
160
|
+
return deriveFlightEffects({
|
|
161
|
+
resource: resourceFor(command.envelope.vehicle),
|
|
162
|
+
maxSpeedMetresPerSecond: command.envelope.maxSpeedMetresPerSecond,
|
|
163
|
+
maxDurationSeconds: command.envelope.maxDurationSeconds,
|
|
164
|
+
envelopeId: command.envelope.envelopeId,
|
|
165
|
+
envelopeDigest: command.envelopeDigest,
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
async actuate(op) {
|
|
169
|
+
const command = op.command;
|
|
170
|
+
const result = await transport.uploadMission({
|
|
171
|
+
envelopeId: command.envelope.envelopeId,
|
|
172
|
+
envelopeDigest: command.envelopeDigest,
|
|
173
|
+
waypoints: command.envelope.waypoints,
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
ok: true,
|
|
177
|
+
data: { missionId: result.missionId, envelopeDigest: command.envelopeDigest },
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
async: { confirmation: 'completed' },
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
kind: 'flight:startMission',
|
|
184
|
+
description: 'Start an uploaded mission that matches the authorized envelope digest.',
|
|
185
|
+
version: 1,
|
|
186
|
+
capability: 'industrial:action',
|
|
187
|
+
effect: 'write',
|
|
188
|
+
protocol: MAVLINK_PROTOCOL,
|
|
189
|
+
errorTaxonomy: taxonomy,
|
|
190
|
+
schema: {
|
|
191
|
+
parameters: [
|
|
192
|
+
{ name: 'missionId', type: 'string', required: true, description: 'Autopilot mission id.' },
|
|
193
|
+
{ name: 'envelopeDigest', type: 'string', required: true, description: 'Authorized envelope digest.' },
|
|
194
|
+
...envelopeParameters(),
|
|
195
|
+
],
|
|
196
|
+
context: [],
|
|
197
|
+
},
|
|
198
|
+
policy: { failureMode: 'fail-safe', timeoutMs: 30000 },
|
|
199
|
+
safeState: async () => {
|
|
200
|
+
await transport.returnToLaunch();
|
|
201
|
+
return { ok: true };
|
|
202
|
+
},
|
|
203
|
+
async prepare(params) {
|
|
204
|
+
const envelope = envelopeFrom(params);
|
|
205
|
+
const digest = envelopeDigest(envelope);
|
|
206
|
+
assertEnvelopeBinding(envelope, stringParameter(params.envelopeDigest));
|
|
207
|
+
return {
|
|
208
|
+
resourceId: resourceFor(envelope.vehicle),
|
|
209
|
+
command: {
|
|
210
|
+
action: 'startMission',
|
|
211
|
+
missionId: stringParameter(params.missionId),
|
|
212
|
+
envelopeDigest: digest,
|
|
213
|
+
envelope,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
deriveEffects(op) {
|
|
218
|
+
const command = op.command;
|
|
219
|
+
return deriveFlightEffects({
|
|
220
|
+
resource: resourceFor(command.envelope.vehicle),
|
|
221
|
+
maxSpeedMetresPerSecond: command.envelope.maxSpeedMetresPerSecond,
|
|
222
|
+
maxDurationSeconds: command.envelope.maxDurationSeconds,
|
|
223
|
+
envelopeId: command.envelope.envelopeId,
|
|
224
|
+
envelopeDigest: command.envelopeDigest,
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
async actuate(op) {
|
|
228
|
+
const command = op.command;
|
|
229
|
+
await transport.startMission({
|
|
230
|
+
missionId: command.missionId,
|
|
231
|
+
envelopeDigest: command.envelopeDigest,
|
|
232
|
+
});
|
|
233
|
+
return { ok: true, data: { missionId: command.missionId, envelopeDigest: command.envelopeDigest } };
|
|
234
|
+
},
|
|
235
|
+
async: {
|
|
236
|
+
confirmation: 'accepted',
|
|
237
|
+
async reconcile(op) {
|
|
238
|
+
const command = op.command;
|
|
239
|
+
const telemetry = await transport.readTelemetry();
|
|
240
|
+
const final = command.envelope.waypoints.at(-1);
|
|
241
|
+
const handle = { missionId: command.missionId };
|
|
242
|
+
if (final && distanceMetres({ latitudeDeg: telemetry.latitudeDeg, longitudeDeg: telemetry.longitudeDeg, altitudeMetres: telemetry.altitudeMetres }, final) <= arrivalTolerance) {
|
|
243
|
+
return {
|
|
244
|
+
status: 'confirmed',
|
|
245
|
+
handle,
|
|
246
|
+
progress: { fraction: 1, detail: 'arrived at final waypoint', at: Date.now() },
|
|
247
|
+
result: { telemetry },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const fraction = typeof telemetry.waypointIndex === 'number' && command.envelope.waypoints.length > 0
|
|
251
|
+
? Math.min(1, telemetry.waypointIndex / command.envelope.waypoints.length)
|
|
252
|
+
: undefined;
|
|
253
|
+
return {
|
|
254
|
+
status: 'running',
|
|
255
|
+
handle,
|
|
256
|
+
progress: {
|
|
257
|
+
...(fraction !== undefined ? { fraction } : {}),
|
|
258
|
+
detail: `en route${typeof telemetry.waypointIndex === 'number' ? ` (waypoint ${telemetry.waypointIndex})` : ''}`,
|
|
259
|
+
at: Date.now(),
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
async cancel() {
|
|
264
|
+
await transport.hold();
|
|
265
|
+
await transport.returnToLaunch();
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
kind: 'flight:hold',
|
|
271
|
+
description: 'Hold position; the autopilot remains in control.',
|
|
272
|
+
version: 1,
|
|
273
|
+
capability: 'industrial:action',
|
|
274
|
+
effect: 'write',
|
|
275
|
+
protocol: MAVLINK_PROTOCOL,
|
|
276
|
+
errorTaxonomy: taxonomy,
|
|
277
|
+
schema: { parameters: [], context: [] },
|
|
278
|
+
policy: { failureMode: 'fail-safe', timeoutMs: 15000 },
|
|
279
|
+
safeState: async () => {
|
|
280
|
+
await transport.returnToLaunch();
|
|
281
|
+
return { ok: true };
|
|
282
|
+
},
|
|
283
|
+
async prepare() {
|
|
284
|
+
return { command: { action: 'hold' } };
|
|
285
|
+
},
|
|
286
|
+
deriveEffects() {
|
|
287
|
+
return { spends: [], physical: { speed: [] } };
|
|
288
|
+
},
|
|
289
|
+
async actuate() {
|
|
290
|
+
await transport.hold();
|
|
291
|
+
return { ok: true };
|
|
292
|
+
},
|
|
293
|
+
async: {
|
|
294
|
+
confirmation: 'accepted',
|
|
295
|
+
async reconcile() {
|
|
296
|
+
const telemetry = await transport.readTelemetry();
|
|
297
|
+
const stopped = telemetryConfirmed(telemetry, (snapshot) => snapshot.groundspeedMetresPerSecond <= 0.2);
|
|
298
|
+
return stopped
|
|
299
|
+
? { status: 'confirmed', progress: { fraction: 1, detail: 'vehicle holding', at: Date.now() } }
|
|
300
|
+
: { status: 'running', progress: { detail: 'slowing to hold', at: Date.now() } };
|
|
301
|
+
},
|
|
302
|
+
async cancel() {
|
|
303
|
+
await transport.returnToLaunch();
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
kind: 'flight:returnToLaunch',
|
|
309
|
+
description: 'Return to launch; completion is observed from telemetry.',
|
|
310
|
+
version: 1,
|
|
311
|
+
capability: 'industrial:action',
|
|
312
|
+
effect: 'write',
|
|
313
|
+
protocol: MAVLINK_PROTOCOL,
|
|
314
|
+
errorTaxonomy: taxonomy,
|
|
315
|
+
schema: { parameters: [], context: [] },
|
|
316
|
+
policy: { failureMode: 'fail-safe', timeoutMs: 15000 },
|
|
317
|
+
safeState: async () => {
|
|
318
|
+
await transport.land();
|
|
319
|
+
return { ok: true };
|
|
320
|
+
},
|
|
321
|
+
async prepare() {
|
|
322
|
+
return { command: { action: 'returnToLaunch' } };
|
|
323
|
+
},
|
|
324
|
+
deriveEffects() {
|
|
325
|
+
return { spends: [] };
|
|
326
|
+
},
|
|
327
|
+
async actuate() {
|
|
328
|
+
await transport.returnToLaunch();
|
|
329
|
+
return { ok: true };
|
|
330
|
+
},
|
|
331
|
+
async: {
|
|
332
|
+
confirmation: 'accepted',
|
|
333
|
+
async reconcile() {
|
|
334
|
+
const telemetry = await transport.readTelemetry();
|
|
335
|
+
const home = telemetry.missionFinished === true && telemetry.groundspeedMetresPerSecond <= 0.5;
|
|
336
|
+
return home
|
|
337
|
+
? { status: 'confirmed', progress: { fraction: 1, detail: 'returned to launch', at: Date.now() } }
|
|
338
|
+
: { status: 'running', progress: { detail: 'returning to launch', at: Date.now() } };
|
|
339
|
+
},
|
|
340
|
+
async cancel() {
|
|
341
|
+
await transport.land();
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
kind: 'flight:land',
|
|
347
|
+
description: 'Land at the current position.',
|
|
348
|
+
version: 1,
|
|
349
|
+
capability: 'industrial:action',
|
|
350
|
+
effect: 'write',
|
|
351
|
+
protocol: MAVLINK_PROTOCOL,
|
|
352
|
+
errorTaxonomy: taxonomy,
|
|
353
|
+
schema: { parameters: [], context: [] },
|
|
354
|
+
policy: { failureMode: 'fail-safe', timeoutMs: 15000 },
|
|
355
|
+
safeState: async () => {
|
|
356
|
+
await transport.land();
|
|
357
|
+
return { ok: true };
|
|
358
|
+
},
|
|
359
|
+
async prepare() {
|
|
360
|
+
return { command: { action: 'land' } };
|
|
361
|
+
},
|
|
362
|
+
deriveEffects() {
|
|
363
|
+
return { spends: [] };
|
|
364
|
+
},
|
|
365
|
+
async actuate() {
|
|
366
|
+
await transport.land();
|
|
367
|
+
return { ok: true };
|
|
368
|
+
},
|
|
369
|
+
async: {
|
|
370
|
+
confirmation: 'accepted',
|
|
371
|
+
async reconcile() {
|
|
372
|
+
const telemetry = await transport.readTelemetry();
|
|
373
|
+
return telemetry.armed === false
|
|
374
|
+
? { status: 'confirmed', progress: { fraction: 1, detail: 'landed and disarmed', at: Date.now() } }
|
|
375
|
+
: { status: 'running', progress: { detail: 'landing', at: Date.now() } };
|
|
376
|
+
},
|
|
377
|
+
async cancel() {
|
|
378
|
+
throw new MavlinkTransportError('Unsupported', 'a landing cannot be cancelled; command return-to-launch from the operator console');
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
kind: 'flight:readTelemetry',
|
|
384
|
+
description: 'Read the vehicle telemetry snapshot used for reconciliation.',
|
|
385
|
+
version: 1,
|
|
386
|
+
capability: 'industrial:read',
|
|
387
|
+
effect: 'read',
|
|
388
|
+
protocol: MAVLINK_PROTOCOL,
|
|
389
|
+
errorTaxonomy: taxonomy,
|
|
390
|
+
schema: { parameters: [], context: [] },
|
|
391
|
+
async prepare() {
|
|
392
|
+
return { command: { action: 'readTelemetry' } };
|
|
393
|
+
},
|
|
394
|
+
deriveEffects() {
|
|
395
|
+
return { spends: [] };
|
|
396
|
+
},
|
|
397
|
+
async actuate() {
|
|
398
|
+
return { ok: true, data: await transport.readTelemetry() };
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
return definitions;
|
|
403
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-025 §5.3 — governed MAVLink operations.
|
|
3
|
+
*
|
|
4
|
+
* `startMission` carries the authorized envelope digest; a start whose
|
|
5
|
+
* recomputed digest differs from the authorized one fails closed before
|
|
6
|
+
* dispatch. Acceptance is never completion: `startMission` reconciles through
|
|
7
|
+
* vehicle telemetry and only reports `confirmed` on observed arrival.
|
|
8
|
+
*/
|
|
9
|
+
import { type IndustrialActionDefinition, type UnitRegistry } from '@totemsdk/act-core';
|
|
10
|
+
import { type MissionEnvelope } from './envelope.js';
|
|
11
|
+
import { type FlightVehicle } from './profile.js';
|
|
12
|
+
import { type MavlinkTransportPort } from './transport.js';
|
|
13
|
+
export interface MavlinkAdapterOptions {
|
|
14
|
+
transport: MavlinkTransportPort;
|
|
15
|
+
site: string;
|
|
16
|
+
vehicles: readonly FlightVehicle[];
|
|
17
|
+
units?: UnitRegistry;
|
|
18
|
+
/** Metres from the final waypoint that count as observed arrival. */
|
|
19
|
+
arrivalToleranceMetres?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function createMavlinkDefinitions(options: MavlinkAdapterOptions): IndustrialActionDefinition[];
|
|
22
|
+
export type { MissionEnvelope };
|