@aircast-4g/mavlink
Version:
TypeScript type generator for MAVLink dialects
793 lines (620 loc) • 22.9 kB
Markdown
# Aircast MAVLink
A comprehensive TypeScript/JavaScript library for working with MAVLink protocols, providing both TypeScript type generation from XML dialect files and robust real-time MAVLink message parsing with built-in frame parsing and buffering.
## Features
### Type Generation
- Generate TypeScript interfaces from MAVLink XML dialects
- Support for all MAVLink data types and enums
- Type-safe enum definitions with numeric values (not string literals)
- Batch processing of multiple dialects
- CLI interface for easy integration
### MAVLink Parser & Decoder
- **Complete message parsing** - Built-in frame parsing with `parseBytes()` API
- **Protocol support** - MAVLink v1 and v2 message parsing
- **Robust decoding** - Fixed array handling, proper field ordering, graceful error handling
- **Buffering** - Automatic buffer management for partial messages
- **Browser & Node.js** - Universal compatibility with Web Workers
- **Dialect-specific parsers** - Generated parsers for each dialect (Common, ArduPilot, etc.)
## Installation
```bash
# Install the package
npm install @aircast-4g/mavlink
# Or with yarn
yarn add @aircast-4g/mavlink
```
### CDN Usage (Browser)
```html
<!-- ES modules -->
<script type="module">
import { CommonParser } from 'https://esm.sh/@aircast-4g/mavlink@1.1.6/dist/dialects/common/index.js';
</script>
<!-- Or for specific dialects -->
<script type="module">
import { ArdupilotmegaParser } from 'https://esm.sh/@aircast-4g/mavlink@1.1.6/dist/dialects/ardupilotmega/index.js';
</script>
```
### Local Development
```bash
git clone <repository>
cd aircast-mavlink
npm install
npm run build
```
## Usage
### CLI Usage
#### Generate Single Dialect
```bash
# Using the global CLI
aircast-mavlink generate -i https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/common.xml -o ./types
# Using local installation
node dist/cli.js generate -i https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/common.xml -o ./types
# Generate from local file
aircast-mavlink generate -i ./dialect.xml -o ./types
# Specify dialect name and format
aircast-mavlink generate -i common.xml -o ./types -n common -f separate
```
#### Batch Generate Multiple Dialects
```bash
# Generate all available dialects
aircast-mavlink batch -o ./mavlink-types
# Generate specific dialects
aircast-mavlink batch -d "common,minimal,ardupilotmega" -o ./mavlink-types
# Generate with package.json
aircast-mavlink batch -o ./mavlink-types --package
```
#### List Available Dialects
```bash
aircast-mavlink list
```
#### CLI Options
**Generate Command:**
- `-i, --input <path>` - Input XML file path or URL
- `-o, --output <path>` - Output directory (default: "./types")
- `-n, --name <name>` - Dialect name (auto-detected if not provided)
- `-f, --format <format>` - Output format: "single" or "separate" (default: "separate")
- `--no-enums` - Skip enum generation
- `--no-type-guards` - Skip type guard generation
**Batch Command:**
- `-o, --output <path>` - Output directory (default: "./mavlink-types")
- `-d, --dialects <dialects>` - Comma-separated dialect names
- `-f, --format <format>` - Output format: "single" or "separate" (default: "separate")
- `--no-enums` - Skip enum generation
- `--no-type-guards` - Skip type guard generation
- `--package` - Generate package.json and tsconfig.json
### Local Development Examples
```bash
# Build the project
npm run build
# Generate common dialect types
node dist/cli.js generate -i https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/common.xml -o ./output/common
# Generate minimal dialect types
node dist/cli.js generate -i https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/minimal.xml -o ./output/minimal
# Generate all dialects
node dist/cli.js batch -o ./output/all-dialects
# List available dialects
node dist/cli.js list
# Generate from local XML file
node dist/cli.js generate -i ./my-dialect.xml -o ./output/custom
```
## Quick Start
### Basic Message Parsing
```typescript
import { CommonParser } from '@aircast-4g/mavlink/dialects/common';
// Create parser instance
const parser = new CommonParser();
// Parse raw MAVLink bytes
const rawBytes = new Uint8Array([/* MAVLink frame data */]);
const messages = parser.parseBytes(rawBytes);
// Process messages
messages.forEach(message => {
console.log(`Received ${message.message_name} from ${message.system_id}:${message.component_id}`);
console.log('Payload:', message.payload);
});
```
### Web Worker Integration
```typescript
// worker.js
import { ArdupilotmegaParser } from '@aircast-4g/mavlink/dialects/ardupilotmega';
const parser = new ArdupilotmegaParser();
self.onmessage = (event) => {
if (event.data.type === 'PARSE_MAVLINK') {
const messages = parser.parseBytes(event.data.data);
self.postMessage({ type: 'MESSAGES', messages });
}
};
```
### Real-time Stream Processing
```typescript
import { CommonParser } from '@aircast-4g/mavlink/dialects/common';
const parser = new CommonParser();
// WebSocket example
websocket.onmessage = (event) => {
const data = new Uint8Array(event.data);
const messages = parser.parseBytes(data);
messages.forEach(msg => {
switch (msg.message_name) {
case 'HEARTBEAT':
updateSystemStatus(msg.payload);
break;
case 'GPS_RAW_INT':
updatePosition(msg.payload);
break;
}
});
};
```
## API Reference
### Dialect Parsers
Each dialect has its own parser class with the same interface:
```typescript
// Available parsers
import { CommonParser } from '@aircast-4g/mavlink/dialects/common';
import { ArdupilotmegaParser } from '@aircast-4g/mavlink/dialects/ardupilotmega';
import { MinimalParser } from '@aircast-4g/mavlink/dialects/minimal';
import { StandardParser } from '@aircast-4g/mavlink/dialects/standard';
import { TestParser } from '@aircast-4g/mavlink/dialects/test';
// Parser interface
class DialectParser {
// Parse raw bytes into messages (handles buffering internally)
parseBytes(data: Uint8Array): ParsedMAVLinkMessage[];
// Decode a single frame
decode(frame: MAVLinkFrame): ParsedMAVLinkMessage;
// Get supported message IDs
getSupportedMessageIds(): number[];
// Check if message ID is supported
supportsMessage(messageId: number): boolean;
// Get message definition
getMessageDefinition(id: number): MessageDefinition | undefined;
// Get dialect name
getDialectName(): string;
// Reset internal buffer
resetBuffer(): void;
}
```
### ParsedMAVLinkMessage
```typescript
interface ParsedMAVLinkMessage {
timestamp: number; // Parse timestamp
system_id: number; // MAVLink system ID
component_id: number; // MAVLink component ID
message_id: number; // Message type ID
message_name: string; // Human-readable message name
sequence: number; // MAVLink sequence number
payload: Record<string, any>; // Decoded message fields
protocol_version: 1 | 2; // MAVLink protocol version
checksum: number; // Frame checksum
crc_ok: boolean; // CRC validation result
signature?: Uint8Array; // MAVLink v2 signature (if present)
dialect?: string; // Dialect name
}
```
### Key Benefits of parseBytes()
1. **Automatic Buffering** - Handles partial frames across multiple calls
2. **Frame Synchronization** - Finds valid MAVLink frames in noisy data
3. **Protocol Detection** - Automatically detects MAVLink v1 vs v2
4. **Robust Parsing** - Gracefully handles malformed data
5. **Zero Configuration** - No setup required, just call parseBytes()
```typescript
const parser = new CommonParser();
// Handle partial data - parser buffers automatically
const part1 = new Uint8Array([0xFE, 0x09, 0x00]); // Partial frame
const part2 = new Uint8Array([0x01, 0x01, 0x00, /* rest of frame */]);
const messages1 = parser.parseBytes(part1); // [] - no complete messages
const messages2 = parser.parseBytes(part2); // [message] - complete message
```
### Programmatic Usage
#### Type Generation
```typescript
import { MAVLinkGenerator, generateTypesFromXML } from '@aircast-4g/mavlink';
// Generate from XML string
const files = await generateTypesFromXML(xmlContent, {
dialectName: 'common',
outputFormat: 'separate', // or 'single'
includeEnums: true,
includeTypeGuards: true
});
// files is an object with filename -> content mappings
console.log(files['types.ts']);
console.log(files['messages.ts']);
console.log(files['index.ts']);
// Generate from URL or file
const generator = new MAVLinkGenerator();
// From URL
await generator.generateFromURL(
'https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/common.xml',
'./output',
{
dialectName: 'common',
outputFormat: 'separate',
includeEnums: true,
includeTypeGuards: true
}
);
```
## Examples & Integration Patterns
### Real-time Telemetry Processing
```typescript
import { CommonParser } from '@aircast-4g/mavlink/dialects/common';
const parser = new CommonParser();
// WebRTC data channel
dataChannel.onmessage = (event) => {
const messages = parser.parseBytes(new Uint8Array(event.data));
messages.forEach(processMessage);
};
// WebSocket connection
websocket.onmessage = (event) => {
const messages = parser.parseBytes(new Uint8Array(event.data));
messages.forEach(processMessage);
};
// TCP/UDP streams (Node.js)
socket.on('data', (data) => {
const messages = parser.parseBytes(data);
messages.forEach(processMessage);
});
```
### Message Filtering and Routing
```typescript
function processMessage(msg: ParsedMAVLinkMessage) {
switch (msg.message_name) {
case 'HEARTBEAT':
updateSystemStatus(msg.system_id, msg.payload);
break;
case 'GPS_RAW_INT':
updatePosition(msg.payload.lat / 1e7, msg.payload.lon / 1e7);
break;
case 'ATTITUDE':
updateAttitude(msg.payload.roll, msg.payload.pitch, msg.payload.yaw);
break;
case 'VFR_HUD':
updateHUD(msg.payload);
break;
case 'STATUSTEXT':
displayStatusMessage(msg.payload.text, msg.payload.severity);
break;
}
}
```
### Error Handling and Recovery
```typescript
try {
const messages = parser.parseBytes(incomingData);
messages.forEach(processMessage);
} catch (error) {
console.error('Parse error:', error.message);
// Reset parser buffer if needed
parser.resetBuffer();
// Implement reconnection logic
scheduleReconnect();
}
```
## Robustness & Features
### Parser Robustness
- **Fixed array decoding** - Proper handling of `uint8_t[8]` arrays without double nesting
- **Graceful degradation** - Missing fields get sensible default values
- **Protocol version detection** - Automatic MAVLink v1/v2 detection
- **Frame synchronization** - Finds valid frames in noisy data streams
- **Buffer management** - Handles partial frames across data chunks
- **Memory efficient** - Reuses buffers, minimal allocations
### Decoder Features
- **All MAVLink types** - Support for `uint8_t`, `int32_t`, `float`, `double`, `char[N]`, arrays
- **Little-endian parsing** - Correct byte order handling
- **Unknown message handling** - Gracefully processes unsupported message types
- **Field validation** - Bounds checking and safe defaults
- **CRC validation** - Optional checksum verification (simplified implementation)
### Testing & Quality
- **Comprehensive tests** - 20+ test cases covering edge cases
- **Generated decoder tests** - Validates HEARTBEAT, PROTOCOL_VERSION, arrays, partial payloads
- **Frame parsing tests** - Tests v1/v2 protocols, multi-message buffers, invalid data
- **CI/CD integration** - Automated testing on builds
## Pre-generated Types
The package includes pre-generated TypeScript types for common MAVLink dialects:
### Available Dialects
- **CommonTypes** - Standard MAVLink common dialect (most widely used)
- **MinimalTypes** - Minimal MAVLink dialect for basic functionality
- **ArduPilotMegaTypes** - ArduPilot-specific extensions
- **StandardTypes** - Full standard MAVLink dialect
### Usage Examples
```typescript
import * as CommonTypes from 'aircast-mavlink/types/common';
import * as ArduPilotMegaTypes from 'aircast-mavlink/types/ardupilotmega';
// Type-safe message handling
function handleHeartbeat(msg: CommonTypes.MessageHeartbeat) {
if (msg.autopilot === CommonTypes.MAV_AUTOPILOTEnum.MAV_AUTOPILOT_ARDUPILOTMEGA) {
console.log('ArduPilot detected');
}
}
// Type guards
function isHeartbeat(msg: any): msg is CommonTypes.MessageHeartbeat {
return CommonTypes.isHeartbeat(msg);
}
// Enum usage
const systemType: CommonTypes.MAV_TYPE = CommonTypes.MAV_TYPEEnum.MAV_TYPE_QUADROTOR;
const flightMode: ArduPilotMegaTypes.COPTER_MODE = ArduPilotMegaTypes.COPTER_MODEEnum.COPTER_MODE_STABILIZE;
```
### Type Structure
Each dialect export includes:
- **Message interfaces** - Typed message payload structures
- **Enum types** - Union types with numeric values
- **Enum objects** - Runtime enum values
- **Type guards** - Runtime type checking functions
- **Type maps** - Message name to type mappings
```typescript
// Example from CommonTypes
interface MessageHeartbeat {
type: MAV_TYPE;
autopilot: MAV_AUTOPILOT;
base_mode: MAV_MODE_FLAG;
system_status: MAV_STATE;
mavlink_version: number;
}
type MAV_TYPE = 0 | 1 | 2 | 3 | number; // Union type
enum MAV_TYPEEnum { // Runtime enum
MAV_TYPE_GENERIC = 0,
MAV_TYPE_FIXED_WING = 1,
MAV_TYPE_QUADROTOR = 2,
MAV_TYPE_COAXIAL = 3,
}
function isHeartbeat(msg: any): msg is MessageHeartbeat; // Type guard
```
## Parser API Reference
### MAVLinkParser
Main parser class for processing MAVLink data streams.
```typescript
interface ParserOptions {
validateCRC?: boolean; // Enable CRC validation (default: true)
bufferSize?: number; // Internal buffer size (default: 4096)
resetOnError?: boolean; // Reset parser state on errors (default: true)
}
class MAVLinkParser {
constructor(options?: ParserOptions);
// Initialize dialect parsers (call before parsing)
initialize(): Promise<void>;
// Parse bytes and return complete messages
parseBytes(data: Buffer | Uint8Array): Promise<MAVLinkMessage[]>;
// Parse single message
parseMessage(data: Uint8Array): Promise<MAVLinkMessage | null>;
// Reset parser state
reset(): void;
// Get parser statistics
getStats(): ParserStats;
}
```
### MAVLinkFrameParser
Low-level frame parser with event-based API.
```typescript
class MAVLinkFrameParser extends EventEmitter {
constructor(options?: ParserOptions);
// Process incoming data
process(data: Buffer | Uint8Array): void;
// Events:
// 'frame' - Complete frame received
// 'error' - Parse error occurred
}
```
### DialectParserFactory
Creates and manages dialect-specific parsers.
```typescript
class DialectParserFactory {
// Create single dialect parser
static createParser(dialectName: SupportedDialects): Promise<DialectParser>;
// Create multi-dialect parser
static createMultipleDialectParser(dialectNames: SupportedDialects[]): Promise<MultiDialectParser>;
// Get list of supported dialects
static getSupportedDialects(): SupportedDialects[];
}
```
### Message Structure
```typescript
interface MAVLinkMessage {
sequence: number;
system_id: number;
component_id: number;
message_id: number;
message_name: string;
payload: Record<string, any>;
timestamp: number;
crc_ok: boolean;
}
```
## Examples
The `examples/` directory contains comprehensive usage examples:
### Browser Examples
- **WebRTC Integration** (`webrtc-integration.html`) - Complete web demo with real-time parsing
- **Web Worker** (`web-worker-example.js`) - Non-blocking parsing in web workers
- **Basic Web Usage** (`test-web.html`) - Simple browser integration
### Node.js Examples
- **Basic Parser** (`basic-parser.js`) - Fundamental parsing concepts
- **Streaming Data** (`nodejs-stream.js`) - TCP/UDP connections and file replay
- **Production Usage** - Advanced patterns for real applications
### Running Examples
```bash
# Build the project first
npm run build
# Run basic Node.js example
node examples/basic-parser.js
# Run streaming example with different modes
node examples/nodejs-stream.js test # Run tests
node examples/nodejs-stream.js tcp localhost 5760 # Connect to TCP source
node examples/nodejs-stream.js udp 14550 # Start UDP server
node examples/nodejs-stream.js file data.mavlink # Replay file
# Serve web examples
python3 -m http.server 8000
# Then open http://localhost:8000/examples/
```
### Common Integration Patterns
#### Real-time Telemetry Processing
```typescript
const parser = new MAVLinkParser({ validateCRC: true });
// WebRTC data channel
dataChannel.onmessage = async (event) => {
const messages = await parser.parseBytes(new Uint8Array(event.data));
messages.forEach(processMessage);
};
// WebSocket connection
websocket.onmessage = async (event) => {
const messages = await parser.parseBytes(new Uint8Array(event.data));
messages.forEach(processMessage);
};
// TCP/UDP streams (Node.js)
socket.on('data', async (data) => {
const messages = await parser.parseBytes(data);
messages.forEach(processMessage);
});
```
#### Message Filtering and Routing
```typescript
function processMessage(msg: MAVLinkMessage) {
switch (msg.message_name) {
case 'HEARTBEAT':
updateSystemStatus(msg.system_id, msg.payload);
break;
case 'GPS_RAW_INT':
updatePosition(msg.payload.lat / 1e7, msg.payload.lon / 1e7);
break;
case 'ATTITUDE':
updateAttitude(msg.payload.roll, msg.payload.pitch, msg.payload.yaw);
break;
case 'VFR_HUD':
updateHUD(msg.payload);
break;
case 'STATUSTEXT':
displayStatusMessage(msg.payload.text, msg.payload.severity);
break;
}
}
```
#### Error Handling and Recovery
```typescript
try {
const messages = await parser.parseBytes(incomingData);
messages.forEach(processMessage);
} catch (error) {
console.error('Parse error:', error.message);
// Reset parser state if needed
if (error.code === 'INVALID_CHECKSUM') {
parser.reset();
}
// Implement reconnection logic
scheduleReconnect();
}
```
## Generated Types
The tool generates TypeScript files with the following structure:
### File Structure
When using `separate` format, the generator creates:
- `types.ts` - Base interfaces and type definitions
- `enums.ts` - Enum object definitions
- `messages.ts` - Message interfaces, type maps, and type guards
- `index.ts` - Main export file
### Example Output
**types.ts**
```typescript
export interface MAVLinkMessage<Content = unknown> {
timestamp: number;
system_id: number;
component_id: number;
type: string;
content: Content;
}
export type MAV_STATE =
| 0 // MAV_STATE_UNINIT - Uninitialized system
| 1 // MAV_STATE_BOOT - System is booting up
| 2 // MAV_STATE_STANDBY - System is standby
| 3 // MAV_STATE_ACTIVE - System is active
| number;
export type MAV_TYPE =
| 0 // MAV_TYPE_GENERIC - Generic micro air vehicle
| 1 // MAV_TYPE_FIXED_WING - Fixed wing aircraft
| 2 // MAV_TYPE_QUADROTOR - Quadrotor
| number;
```
**enums.ts**
```typescript
export enum MAV_STATEEnum {
MAV_STATE_UNINIT = 0,
MAV_STATE_BOOT = 1,
MAV_STATE_STANDBY = 2,
MAV_STATE_ACTIVE = 3,
}
```
**messages.ts**
```typescript
export interface MessageHeartbeat {
type: MAV_TYPE;
autopilot: MAV_AUTOPILOT;
base_mode: MAV_MODE_FLAG;
system_status: MAV_STATE;
mavlink_version: number;
}
export interface MessageTypeMap {
HEARTBEAT: MessageHeartbeat;
// ... other messages
}
export type AnyMessage =
| MAVLinkMessage<MessageHeartbeat>
| MAVLinkMessage<MessageSysStatus>;
// Type guard functions
export function isHeartbeat(msg: MAVLinkMessage): msg is MAVLinkMessage<MessageHeartbeat> {
return msg.type === 'HEARTBEAT';
}
```
## Output Formats
### Separate Files (Default)
- `types.ts` - Type definitions and interfaces
- `enums.ts` - Enum object definitions
- `messages.ts` - Message interfaces and utilities
- `index.ts` - Main export file
### Single File
All definitions combined into a single `index.ts` file.
## Development
```bash
# Setup
npm install
npm run build
# Development workflow
npm run dev # Run in development mode with tsx
npm run build # Build TypeScript to JavaScript
npm run test # Run Jest tests
npm run lint # Run ESLint
npm run clean # Clean build artifacts
# Test the CLI locally
node dist/cli.js --help
# Generate test types
node dist/cli.js generate -i https://raw.githubusercontent.com/mavlink/mavlink/master/message_definitions/v1.0/minimal.xml -o ./test-output
```
## Project Structure
```
├── src/
│ ├── cli.ts # Command line interface
│ ├── index.ts # Main export file
│ ├── types.ts # Shared type definitions
│ ├── generator/ # Type generation components
│ │ ├── generator.ts # Main generator class
│ │ ├── template-engine.ts # Handlebars template engine with decoder
│ │ ├── type-converter.ts # XML to TypeScript conversion
│ │ ├── xml-parser.ts # MAVLink XML parser
│ │ └── batch-processor.ts # Batch processing utilities
│ └── generated/ # Generated dialect parsers
│ └── dialects/ # Generated parsers for each dialect
│ ├── common/ # Common dialect parser + types
│ ├── ardupilotmega/ # ArduPilot dialect parser + types
│ ├── minimal/ # Minimal dialect parser + types
│ ├── standard/ # Standard dialect parser + types
│ └── test/ # Test dialect parser + types
├── tests/ # Jest test files
│ ├── decoder.test.ts # Decoder functionality tests
│ ├── template-engine.test.ts # Template generation tests
│ ├── xml-parser.test.ts # XML parsing tests
│ └── generator.test.ts # End-to-end generation tests
├── dist/ # Compiled JavaScript output
│ └── dialects/ # Built dialect parsers for distribution
└── examples/ # Usage examples and demos
```
### Generated Files Per Dialect
Each dialect directory contains:
- `decoder.ts` - Parser class with parseBytes() and decode() methods
- `types.ts` - Base types and ParsedMAVLinkMessage interface
- `enums.ts` - Enum definitions and type aliases
- `messages.ts` - Message interfaces and type guards
- `index.ts` - Main export file
## License
MIT