@aircast-4g/mavlink 1.1.6 → 1.1.8
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/README.md +261 -111
- package/dist/dialects/ardupilotmega/index.d.ts +1370 -1346
- package/dist/dialects/ardupilotmega/index.js +2355 -1608
- package/dist/dialects/ardupilotmega/index.js.map +1 -1
- package/dist/dialects/common/index.d.ts +1122 -1098
- package/dist/dialects/common/index.js +1941 -1284
- package/dist/dialects/common/index.js.map +1 -1
- package/dist/dialects/minimal/index.d.ts +39 -15
- package/dist/dialects/minimal/index.js +450 -20
- package/dist/dialects/minimal/index.js.map +1 -1
- package/dist/dialects/paparazzi/index.d.ts +1130 -1106
- package/dist/dialects/paparazzi/index.js +1955 -1293
- package/dist/dialects/paparazzi/index.js.map +1 -1
- package/dist/dialects/python_array_test/index.d.ts +1149 -1125
- package/dist/dialects/python_array_test/index.js +2005 -1340
- package/dist/dialects/python_array_test/index.js.map +1 -1
- package/dist/dialects/standard/index.d.ts +44 -20
- package/dist/dialects/standard/index.js +455 -25
- package/dist/dialects/standard/index.js.map +1 -1
- package/dist/dialects/test/index.d.ts +41 -17
- package/dist/dialects/test/index.js +460 -31
- package/dist/dialects/test/index.js.map +1 -1
- package/package.json +11 -3
|
@@ -5,7 +5,7 @@ interface ParsedMAVLinkMessage$1 {
|
|
|
5
5
|
message_id: number;
|
|
6
6
|
message_name: string;
|
|
7
7
|
sequence: number;
|
|
8
|
-
payload: Record<string,
|
|
8
|
+
payload: Record<string, unknown>;
|
|
9
9
|
protocol_version: 1 | 2;
|
|
10
10
|
checksum: number;
|
|
11
11
|
crc_ok: boolean;
|
|
@@ -266,17 +266,17 @@ type MAV_COMPONENT = MAV_COMPONENTEnum;
|
|
|
266
266
|
interface MessageHeartbeat {
|
|
267
267
|
type: MAV_TYPE;
|
|
268
268
|
autopilot: MAV_AUTOPILOT;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
base_mode: MAV_MODE_FLAG;
|
|
270
|
+
custom_mode: number;
|
|
271
|
+
system_status: MAV_STATE;
|
|
272
|
+
mavlink_version: number;
|
|
273
273
|
}
|
|
274
274
|
interface MessageProtocolVersion {
|
|
275
275
|
version: number;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
min_version: number;
|
|
277
|
+
max_version: number;
|
|
278
|
+
spec_version_hash: number[];
|
|
279
|
+
library_version_hash: number[];
|
|
280
280
|
}
|
|
281
281
|
interface MessageTypeMap {
|
|
282
282
|
HEARTBEAT: MessageHeartbeat;
|
|
@@ -297,7 +297,7 @@ interface ParsedMAVLinkMessage {
|
|
|
297
297
|
message_id: number;
|
|
298
298
|
message_name: string;
|
|
299
299
|
sequence: number;
|
|
300
|
-
payload: Record<string,
|
|
300
|
+
payload: Record<string, unknown>;
|
|
301
301
|
protocol_version: 1 | 2;
|
|
302
302
|
checksum: number;
|
|
303
303
|
crc_ok: boolean;
|
|
@@ -316,6 +316,8 @@ interface MAVLinkFrame {
|
|
|
316
316
|
payload: Uint8Array;
|
|
317
317
|
checksum: number;
|
|
318
318
|
signature?: Uint8Array;
|
|
319
|
+
crc_ok?: boolean;
|
|
320
|
+
protocol_version?: 1 | 2;
|
|
319
321
|
}
|
|
320
322
|
interface MessageDefinition {
|
|
321
323
|
id: number;
|
|
@@ -337,10 +339,7 @@ declare abstract class DialectParser {
|
|
|
337
339
|
parseBytes(data: Uint8Array): ParsedMAVLinkMessage[];
|
|
338
340
|
private tryParseFrame;
|
|
339
341
|
resetBuffer(): void;
|
|
340
|
-
decode(frame: MAVLinkFrame
|
|
341
|
-
crc_ok?: boolean;
|
|
342
|
-
protocol_version?: 1 | 2;
|
|
343
|
-
}): ParsedMAVLinkMessage;
|
|
342
|
+
decode(frame: MAVLinkFrame): ParsedMAVLinkMessage;
|
|
344
343
|
private decodePayload;
|
|
345
344
|
private getDefaultValue;
|
|
346
345
|
private decodeField;
|
|
@@ -349,12 +348,37 @@ declare abstract class DialectParser {
|
|
|
349
348
|
getSupportedMessageIds(): number[];
|
|
350
349
|
getDialectName(): string;
|
|
351
350
|
supportsMessage(messageId: number): boolean;
|
|
351
|
+
serializeMessage(message: Record<string, unknown> & {
|
|
352
|
+
message_name: string;
|
|
353
|
+
}): Uint8Array;
|
|
354
|
+
private extractMessageFields;
|
|
355
|
+
private completeMessageWithDefaults;
|
|
356
|
+
private getDefaultValueForField;
|
|
357
|
+
private serializePayload;
|
|
358
|
+
private serializeField;
|
|
359
|
+
private serializeSingleValue;
|
|
360
|
+
private getFieldSize;
|
|
361
|
+
private getSingleFieldSize;
|
|
362
|
+
private getDefaultValueForType;
|
|
363
|
+
private createMAVLinkFrame;
|
|
352
364
|
}
|
|
353
365
|
declare class MinimalParser extends DialectParser {
|
|
354
366
|
constructor();
|
|
355
367
|
loadDefinitions(): Promise<void>;
|
|
356
368
|
private loadDefinitionsSync;
|
|
357
369
|
}
|
|
370
|
+
declare class MinimalSerializer {
|
|
371
|
+
private parser;
|
|
372
|
+
constructor();
|
|
373
|
+
serialize(message: Record<string, unknown> & {
|
|
374
|
+
message_name: string;
|
|
375
|
+
}): Uint8Array;
|
|
376
|
+
completeMessage(message: Record<string, unknown> & {
|
|
377
|
+
message_name: string;
|
|
378
|
+
}): Record<string, unknown>;
|
|
379
|
+
getSupportedMessages(): string[];
|
|
380
|
+
supportsMessage(messageName: string): boolean;
|
|
381
|
+
}
|
|
358
382
|
|
|
359
|
-
export { MAV_AUTOPILOTEnum, MAV_COMPONENTEnum, MAV_MODE_FLAGEnum, MAV_MODE_FLAG_DECODE_POSITIONEnum, MAV_STATEEnum, MAV_TYPEEnum, MinimalParser, isHeartbeat, isProtocolVersion };
|
|
383
|
+
export { MAV_AUTOPILOTEnum, MAV_COMPONENTEnum, MAV_MODE_FLAGEnum, MAV_MODE_FLAG_DECODE_POSITIONEnum, MAV_STATEEnum, MAV_TYPEEnum, MinimalParser, MinimalSerializer, isHeartbeat, isProtocolVersion };
|
|
360
384
|
export type { AnyMessage, MAV_AUTOPILOT, MAV_COMPONENT, MAV_MODE_FLAG, MAV_MODE_FLAG_DECODE_POSITION, MAV_STATE, MAV_TYPE, MessageHeartbeat, MessageProtocolVersion, MessageTypeMap, ParsedMAVLinkMessage$1 as ParsedMAVLinkMessage };
|
|
@@ -505,6 +505,35 @@ function isProtocolVersion(msg) {
|
|
|
505
505
|
|
|
506
506
|
// Auto-generated decoder and parser for minimal dialect
|
|
507
507
|
// Generated from MAVLink XML definitions
|
|
508
|
+
// Embedded MAVLink CRC implementation
|
|
509
|
+
const CRC_EXTRA = {
|
|
510
|
+
0: 50,
|
|
511
|
+
300: 0
|
|
512
|
+
};
|
|
513
|
+
class MAVLinkCRC {
|
|
514
|
+
static calculate(data, crcExtra) {
|
|
515
|
+
let crc = 0xffff;
|
|
516
|
+
// Process all message bytes using MCRF4XX algorithm
|
|
517
|
+
for (let i = 0; i < data.length; i++) {
|
|
518
|
+
let tmp = data[i] ^ (crc & 0xff);
|
|
519
|
+
tmp = (tmp ^ (tmp << 4)) & 0xff;
|
|
520
|
+
crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xffff;
|
|
521
|
+
}
|
|
522
|
+
// Add CRC_EXTRA byte using the same algorithm
|
|
523
|
+
let tmp = crcExtra ^ (crc & 0xff);
|
|
524
|
+
tmp = (tmp ^ (tmp << 4)) & 0xff;
|
|
525
|
+
crc = ((crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4)) & 0xffff;
|
|
526
|
+
return crc;
|
|
527
|
+
}
|
|
528
|
+
static validate(data, messageId, receivedChecksum) {
|
|
529
|
+
const crcExtra = CRC_EXTRA[messageId];
|
|
530
|
+
if (crcExtra === undefined) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const calculatedChecksum = this.calculate(data, crcExtra);
|
|
534
|
+
return calculatedChecksum === receivedChecksum;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
508
537
|
// Base dialect parser class
|
|
509
538
|
class DialectParser {
|
|
510
539
|
constructor(dialectName) {
|
|
@@ -592,9 +621,11 @@ class DialectParser {
|
|
|
592
621
|
frameOffset += 13;
|
|
593
622
|
}
|
|
594
623
|
}
|
|
595
|
-
|
|
624
|
+
// Validate CRC using proper MAVLink algorithm
|
|
625
|
+
const headerAndPayload = data.slice(offset + 1, offset + frameOffset - offset - 2);
|
|
626
|
+
frame.crc_ok = MAVLinkCRC.validate(headerAndPayload, frame.message_id, frame.checksum);
|
|
596
627
|
frame.protocol_version = isV2 ? 2 : 1;
|
|
597
|
-
return { frame, bytesConsumed: frameOffset - offset };
|
|
628
|
+
return { frame: frame, bytesConsumed: frameOffset - offset };
|
|
598
629
|
}
|
|
599
630
|
resetBuffer() {
|
|
600
631
|
this.buffer = new Uint8Array(0);
|
|
@@ -615,7 +646,7 @@ class DialectParser {
|
|
|
615
646
|
},
|
|
616
647
|
protocol_version: protocolVersion,
|
|
617
648
|
checksum: frame.checksum,
|
|
618
|
-
crc_ok: frame.crc_ok
|
|
649
|
+
crc_ok: frame.crc_ok ?? true,
|
|
619
650
|
signature: frame.signature,
|
|
620
651
|
dialect: this.dialectName
|
|
621
652
|
};
|
|
@@ -631,7 +662,7 @@ class DialectParser {
|
|
|
631
662
|
payload,
|
|
632
663
|
protocol_version: protocolVersion,
|
|
633
664
|
checksum: frame.checksum,
|
|
634
|
-
crc_ok: frame.crc_ok
|
|
665
|
+
crc_ok: frame.crc_ok ?? true,
|
|
635
666
|
signature: frame.signature,
|
|
636
667
|
dialect: this.dialectName
|
|
637
668
|
};
|
|
@@ -683,18 +714,37 @@ class DialectParser {
|
|
|
683
714
|
const isArray = field.arrayLength !== undefined;
|
|
684
715
|
const arrayLength = field.arrayLength || 1;
|
|
685
716
|
if (isArray && arrayLength > 1) {
|
|
686
|
-
const values = [];
|
|
687
|
-
let totalBytes = 0;
|
|
688
717
|
// Strip array notation from type to avoid double processing
|
|
689
718
|
let baseType = field.type;
|
|
690
719
|
if (baseType.includes('[') && baseType.includes(']')) {
|
|
691
720
|
baseType = baseType.substring(0, baseType.indexOf('['));
|
|
692
721
|
}
|
|
722
|
+
// Special handling for char arrays - return as string
|
|
723
|
+
if (baseType === 'char') {
|
|
724
|
+
const chars = [];
|
|
725
|
+
let totalBytes = 0;
|
|
726
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
727
|
+
if (offset + totalBytes >= view.byteLength)
|
|
728
|
+
break;
|
|
729
|
+
const charCode = view.getUint8(offset + totalBytes);
|
|
730
|
+
if (charCode === 0)
|
|
731
|
+
break; // Null terminator
|
|
732
|
+
chars.push(String.fromCharCode(charCode));
|
|
733
|
+
totalBytes += 1;
|
|
734
|
+
}
|
|
735
|
+
// Return string value and total bytes for the array
|
|
736
|
+
return { value: chars.join(''), bytesRead: arrayLength }; // Always consume full array size
|
|
737
|
+
}
|
|
738
|
+
// Handle other array types
|
|
739
|
+
const values = [];
|
|
740
|
+
let totalBytes = 0;
|
|
693
741
|
for (let i = 0; i < arrayLength; i++) {
|
|
694
742
|
if (offset + totalBytes >= view.byteLength)
|
|
695
743
|
break;
|
|
696
744
|
const { value, bytesRead } = this.decodeSingleValue(view, offset + totalBytes, baseType);
|
|
697
|
-
|
|
745
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {
|
|
746
|
+
values.push(value);
|
|
747
|
+
}
|
|
698
748
|
totalBytes += bytesRead;
|
|
699
749
|
}
|
|
700
750
|
return { value: values, bytesRead: totalBytes };
|
|
@@ -711,7 +761,7 @@ class DialectParser {
|
|
|
711
761
|
case 'int8_t':
|
|
712
762
|
return { value: view.getInt8(offset), bytesRead: 1 };
|
|
713
763
|
case 'uint16_t':
|
|
714
|
-
return { value: view.getUint16(offset,
|
|
764
|
+
return { value: view.getUint16(offset, false), bytesRead: 2 };
|
|
715
765
|
case 'int16_t':
|
|
716
766
|
return { value: view.getInt16(offset, true), bytesRead: 2 };
|
|
717
767
|
case 'uint32_t':
|
|
@@ -751,7 +801,9 @@ class DialectParser {
|
|
|
751
801
|
if (offset + totalBytes >= view.byteLength)
|
|
752
802
|
break;
|
|
753
803
|
const { value, bytesRead } = this.decodeSingleValue(view, offset + totalBytes, baseType);
|
|
754
|
-
|
|
804
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {
|
|
805
|
+
values.push(value);
|
|
806
|
+
}
|
|
755
807
|
totalBytes += bytesRead;
|
|
756
808
|
}
|
|
757
809
|
return { value: values, bytesRead: totalBytes };
|
|
@@ -775,6 +827,347 @@ class DialectParser {
|
|
|
775
827
|
supportsMessage(messageId) {
|
|
776
828
|
return this.messageDefinitions.has(messageId);
|
|
777
829
|
}
|
|
830
|
+
// Serialization methods for outgoing commands
|
|
831
|
+
serializeMessage(message) {
|
|
832
|
+
const messageDef = Array.from(this.messageDefinitions.values())
|
|
833
|
+
.find(def => def.name === message.message_name);
|
|
834
|
+
if (!messageDef) {
|
|
835
|
+
throw new Error(`Unknown message type: ${message.message_name}`);
|
|
836
|
+
}
|
|
837
|
+
// Extract fields from either flat structure or payload structure
|
|
838
|
+
const messageFields = this.extractMessageFields(message, messageDef.fields);
|
|
839
|
+
// Complete the message with all defined fields (including extension fields with defaults)
|
|
840
|
+
const completeMessage = this.completeMessageWithDefaults(messageFields, messageDef.fields);
|
|
841
|
+
const payload = this.serializePayload(completeMessage, messageDef.fields);
|
|
842
|
+
return this.createMAVLinkFrame(message, messageDef.id, payload);
|
|
843
|
+
}
|
|
844
|
+
// Extract message fields from payload structure (payload format required)
|
|
845
|
+
extractMessageFields(message, fieldDefinitions) {
|
|
846
|
+
// Require payload structure
|
|
847
|
+
if (!message.payload || typeof message.payload !== 'object') {
|
|
848
|
+
throw new Error(`Message must have a 'payload' object containing the message fields. Expected format: { message_name: '...', system_id: 1, component_id: 1, sequence: 0, payload: { ...fields } }`);
|
|
849
|
+
}
|
|
850
|
+
return message.payload;
|
|
851
|
+
}
|
|
852
|
+
// Helper method to complete message with all defined fields
|
|
853
|
+
completeMessageWithDefaults(message, fields) {
|
|
854
|
+
const completeMessage = { ...message };
|
|
855
|
+
for (const field of fields) {
|
|
856
|
+
if (completeMessage[field.name] === undefined) {
|
|
857
|
+
completeMessage[field.name] = this.getDefaultValueForField(field);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return completeMessage;
|
|
861
|
+
}
|
|
862
|
+
// Get default value for a field based on its definition
|
|
863
|
+
getDefaultValueForField(field) {
|
|
864
|
+
const isArray = field.arrayLength !== undefined && field.arrayLength > 1;
|
|
865
|
+
if (isArray) {
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
let baseType = field.type;
|
|
869
|
+
if (baseType.includes('[') && baseType.includes(']')) {
|
|
870
|
+
baseType = baseType.substring(0, baseType.indexOf('['));
|
|
871
|
+
}
|
|
872
|
+
switch (baseType) {
|
|
873
|
+
case 'uint8_t':
|
|
874
|
+
case 'int8_t':
|
|
875
|
+
case 'uint16_t':
|
|
876
|
+
case 'int16_t':
|
|
877
|
+
case 'uint32_t':
|
|
878
|
+
case 'int32_t':
|
|
879
|
+
case 'float':
|
|
880
|
+
case 'double':
|
|
881
|
+
return 0;
|
|
882
|
+
case 'uint64_t':
|
|
883
|
+
case 'int64_t':
|
|
884
|
+
return 0n;
|
|
885
|
+
case 'char':
|
|
886
|
+
return field.type.includes('[') ? '' : '\0';
|
|
887
|
+
default:
|
|
888
|
+
return 0;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
serializePayload(message, fields) {
|
|
892
|
+
// Calculate total payload size
|
|
893
|
+
let totalSize = 0;
|
|
894
|
+
for (const field of fields) {
|
|
895
|
+
totalSize += this.getFieldSize(field);
|
|
896
|
+
}
|
|
897
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
898
|
+
const view = new DataView(buffer);
|
|
899
|
+
let offset = 0;
|
|
900
|
+
for (const field of fields) {
|
|
901
|
+
const value = message[field.name];
|
|
902
|
+
const bytesWritten = this.serializeField(view, offset, field, value);
|
|
903
|
+
offset += bytesWritten;
|
|
904
|
+
}
|
|
905
|
+
// Implement MAVLink payload trimming: remove trailing zero bytes from extension fields only
|
|
906
|
+
// This is required for proper handling of extension fields
|
|
907
|
+
const fullPayload = new Uint8Array(buffer);
|
|
908
|
+
// Calculate minimum payload size (core fields only)
|
|
909
|
+
let corePayloadSize = 0;
|
|
910
|
+
let extensionStartOffset = 0;
|
|
911
|
+
let hasExtensions = false;
|
|
912
|
+
message.message_name;
|
|
913
|
+
for (const field of fields) {
|
|
914
|
+
const fieldSize = this.getFieldSize(field);
|
|
915
|
+
// Check if this is an extension field using proper XML-based detection
|
|
916
|
+
const isExtensionField = field.extension === true;
|
|
917
|
+
if (isExtensionField) {
|
|
918
|
+
if (!hasExtensions) {
|
|
919
|
+
extensionStartOffset = corePayloadSize;
|
|
920
|
+
hasExtensions = true;
|
|
921
|
+
}
|
|
922
|
+
// Don't add extension field sizes to core payload size
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
// This is a core field - always add to core payload size
|
|
926
|
+
corePayloadSize += fieldSize;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// If there are no extension fields, don't trim at all
|
|
930
|
+
if (!hasExtensions) {
|
|
931
|
+
return fullPayload;
|
|
932
|
+
}
|
|
933
|
+
// Find the last non-zero byte in extension fields only
|
|
934
|
+
let trimmedLength = fullPayload.length;
|
|
935
|
+
// If we have extensions, check if they contain any non-zero bytes
|
|
936
|
+
if (hasExtensions && extensionStartOffset < fullPayload.length) {
|
|
937
|
+
// Look for non-zero bytes in the extension section
|
|
938
|
+
let hasNonZeroExtensions = false;
|
|
939
|
+
for (let i = extensionStartOffset; i < fullPayload.length; i++) {
|
|
940
|
+
if (fullPayload[i] !== 0) {
|
|
941
|
+
hasNonZeroExtensions = true;
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (!hasNonZeroExtensions) {
|
|
946
|
+
// All extension fields are zero, trim them all
|
|
947
|
+
trimmedLength = corePayloadSize;
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
// Find the last non-zero byte in extension fields
|
|
951
|
+
for (let i = fullPayload.length - 1; i >= extensionStartOffset; i--) {
|
|
952
|
+
if (fullPayload[i] !== 0) {
|
|
953
|
+
trimmedLength = i + 1;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
// Never trim below the core payload size
|
|
960
|
+
if (trimmedLength < corePayloadSize) {
|
|
961
|
+
trimmedLength = corePayloadSize;
|
|
962
|
+
}
|
|
963
|
+
// Return trimmed payload if it's shorter than the original
|
|
964
|
+
if (trimmedLength < fullPayload.length) {
|
|
965
|
+
return fullPayload.slice(0, trimmedLength);
|
|
966
|
+
}
|
|
967
|
+
return fullPayload;
|
|
968
|
+
}
|
|
969
|
+
serializeField(view, offset, field, value) {
|
|
970
|
+
const isArray = field.arrayLength !== undefined;
|
|
971
|
+
const arrayLength = field.arrayLength || 1;
|
|
972
|
+
if (isArray && arrayLength > 1) {
|
|
973
|
+
let totalBytes = 0;
|
|
974
|
+
let baseType = field.type;
|
|
975
|
+
if (baseType.includes('[') && baseType.includes(']')) {
|
|
976
|
+
baseType = baseType.substring(0, baseType.indexOf('['));
|
|
977
|
+
}
|
|
978
|
+
// Special handling for char arrays - treat string as char array
|
|
979
|
+
if (baseType === 'char' && typeof value === 'string') {
|
|
980
|
+
const str = value;
|
|
981
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
982
|
+
const charCode = i < str.length ? str.charCodeAt(i) : 0;
|
|
983
|
+
view.setUint8(offset + totalBytes, charCode);
|
|
984
|
+
totalBytes += 1;
|
|
985
|
+
}
|
|
986
|
+
return totalBytes;
|
|
987
|
+
}
|
|
988
|
+
// Handle other array types
|
|
989
|
+
const arrayValue = Array.isArray(value) ? value : [value];
|
|
990
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
991
|
+
const itemValue = i < arrayValue.length ? arrayValue[i] : this.getDefaultValueForType(baseType);
|
|
992
|
+
const bytesWritten = this.serializeSingleValue(view, offset + totalBytes, baseType, itemValue);
|
|
993
|
+
totalBytes += bytesWritten;
|
|
994
|
+
}
|
|
995
|
+
return totalBytes;
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
return this.serializeSingleValue(view, offset, field.type, value);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
serializeSingleValue(view, offset, type, value) {
|
|
1002
|
+
const actualValue = value ?? this.getDefaultValueForType(type);
|
|
1003
|
+
switch (type) {
|
|
1004
|
+
case 'uint8_t':
|
|
1005
|
+
view.setUint8(offset, Number(actualValue));
|
|
1006
|
+
return 1;
|
|
1007
|
+
case 'int8_t':
|
|
1008
|
+
view.setInt8(offset, Number(actualValue));
|
|
1009
|
+
return 1;
|
|
1010
|
+
case 'uint16_t':
|
|
1011
|
+
view.setUint16(offset, Number(actualValue), false);
|
|
1012
|
+
return 2;
|
|
1013
|
+
case 'int16_t':
|
|
1014
|
+
view.setInt16(offset, Number(actualValue), true);
|
|
1015
|
+
return 2;
|
|
1016
|
+
case 'uint32_t':
|
|
1017
|
+
view.setUint32(offset, Number(actualValue), true);
|
|
1018
|
+
return 4;
|
|
1019
|
+
case 'int32_t':
|
|
1020
|
+
view.setInt32(offset, Number(actualValue), true);
|
|
1021
|
+
return 4;
|
|
1022
|
+
case 'uint64_t':
|
|
1023
|
+
view.setBigUint64(offset, typeof actualValue === 'bigint' ? actualValue : BigInt(Number(actualValue) || 0), true);
|
|
1024
|
+
return 8;
|
|
1025
|
+
case 'int64_t':
|
|
1026
|
+
view.setBigInt64(offset, typeof actualValue === 'bigint' ? actualValue : BigInt(Number(actualValue) || 0), true);
|
|
1027
|
+
return 8;
|
|
1028
|
+
case 'float':
|
|
1029
|
+
view.setFloat32(offset, Number(actualValue), true);
|
|
1030
|
+
return 4;
|
|
1031
|
+
case 'double':
|
|
1032
|
+
view.setFloat64(offset, Number(actualValue), true);
|
|
1033
|
+
return 8;
|
|
1034
|
+
case 'char':
|
|
1035
|
+
view.setUint8(offset, typeof actualValue === 'string' ? actualValue.charCodeAt(0) : Number(actualValue));
|
|
1036
|
+
return 1;
|
|
1037
|
+
default:
|
|
1038
|
+
if (type.startsWith('char[') && type.endsWith(']')) {
|
|
1039
|
+
const length = parseInt(type.slice(5, -1));
|
|
1040
|
+
const str = String(actualValue);
|
|
1041
|
+
for (let i = 0; i < length; i++) {
|
|
1042
|
+
const charCode = i < str.length ? str.charCodeAt(i) : 0;
|
|
1043
|
+
view.setUint8(offset + i, charCode);
|
|
1044
|
+
}
|
|
1045
|
+
return length;
|
|
1046
|
+
}
|
|
1047
|
+
view.setUint8(offset, Number(actualValue));
|
|
1048
|
+
return 1;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
getFieldSize(field) {
|
|
1052
|
+
if (typeof field === 'string') {
|
|
1053
|
+
// Legacy support for type string
|
|
1054
|
+
if (field.includes('[') && field.includes(']')) {
|
|
1055
|
+
const baseType = field.substring(0, field.indexOf('['));
|
|
1056
|
+
const arrayLength = parseInt(field.substring(field.indexOf('[') + 1, field.indexOf(']')));
|
|
1057
|
+
return this.getSingleFieldSize(baseType) * arrayLength;
|
|
1058
|
+
}
|
|
1059
|
+
return this.getSingleFieldSize(field);
|
|
1060
|
+
}
|
|
1061
|
+
// Handle FieldDefinition object
|
|
1062
|
+
const type = field.type;
|
|
1063
|
+
const arrayLength = field.arrayLength;
|
|
1064
|
+
if (arrayLength && arrayLength > 1) {
|
|
1065
|
+
return this.getSingleFieldSize(type) * arrayLength;
|
|
1066
|
+
}
|
|
1067
|
+
if (type.includes('[') && type.includes(']')) {
|
|
1068
|
+
const baseType = type.substring(0, type.indexOf('['));
|
|
1069
|
+
const parsedArrayLength = parseInt(type.substring(type.indexOf('[') + 1, type.indexOf(']')));
|
|
1070
|
+
return this.getSingleFieldSize(baseType) * parsedArrayLength;
|
|
1071
|
+
}
|
|
1072
|
+
return this.getSingleFieldSize(type);
|
|
1073
|
+
}
|
|
1074
|
+
getSingleFieldSize(type) {
|
|
1075
|
+
switch (type) {
|
|
1076
|
+
case 'uint8_t':
|
|
1077
|
+
case 'int8_t':
|
|
1078
|
+
case 'char':
|
|
1079
|
+
return 1;
|
|
1080
|
+
case 'uint16_t':
|
|
1081
|
+
case 'int16_t':
|
|
1082
|
+
return 2;
|
|
1083
|
+
case 'uint32_t':
|
|
1084
|
+
case 'int32_t':
|
|
1085
|
+
case 'float':
|
|
1086
|
+
return 4;
|
|
1087
|
+
case 'uint64_t':
|
|
1088
|
+
case 'int64_t':
|
|
1089
|
+
case 'double':
|
|
1090
|
+
return 8;
|
|
1091
|
+
default:
|
|
1092
|
+
return 1;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
getDefaultValueForType(type) {
|
|
1096
|
+
switch (type) {
|
|
1097
|
+
case 'uint8_t':
|
|
1098
|
+
case 'int8_t':
|
|
1099
|
+
case 'uint16_t':
|
|
1100
|
+
case 'int16_t':
|
|
1101
|
+
case 'uint32_t':
|
|
1102
|
+
case 'int32_t':
|
|
1103
|
+
case 'float':
|
|
1104
|
+
case 'double':
|
|
1105
|
+
return 0;
|
|
1106
|
+
case 'uint64_t':
|
|
1107
|
+
case 'int64_t':
|
|
1108
|
+
return 0n;
|
|
1109
|
+
case 'char':
|
|
1110
|
+
return 0;
|
|
1111
|
+
default:
|
|
1112
|
+
return 0;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
createMAVLinkFrame(message, messageId, payload) {
|
|
1116
|
+
const systemId = typeof message.system_id === 'number' ? message.system_id : 1;
|
|
1117
|
+
const componentId = typeof message.component_id === 'number' ? message.component_id : 1;
|
|
1118
|
+
const sequence = typeof message.sequence === 'number' ? message.sequence : 0;
|
|
1119
|
+
// Automatically determine protocol version based on message requirements
|
|
1120
|
+
const needsV2 = messageId > 255; // v1 only supports 8-bit message IDs (0-255)
|
|
1121
|
+
const userSpecifiedVersion = typeof message.protocol_version === 'number' ? message.protocol_version : null;
|
|
1122
|
+
// Use user-specified version if provided, otherwise auto-detect
|
|
1123
|
+
const protocolVersion = userSpecifiedVersion !== null ? userSpecifiedVersion : (needsV2 ? 2 : 1);
|
|
1124
|
+
const isV2 = protocolVersion === 2;
|
|
1125
|
+
const magic = isV2 ? 0xFD : 0xFE;
|
|
1126
|
+
// Calculate frame size based on protocol version
|
|
1127
|
+
const headerSize = isV2 ? 10 : 6; // v2 has extra fields
|
|
1128
|
+
const frameSize = headerSize + payload.length + 2;
|
|
1129
|
+
const buffer = new ArrayBuffer(frameSize);
|
|
1130
|
+
const view = new DataView(buffer);
|
|
1131
|
+
let offset = 0;
|
|
1132
|
+
// Header
|
|
1133
|
+
view.setUint8(offset++, magic);
|
|
1134
|
+
view.setUint8(offset++, payload.length);
|
|
1135
|
+
if (isV2) {
|
|
1136
|
+
// MAVLink v2: magic(1) + len(1) + incompat_flags(1) + compat_flags(1) + seq(1) + sysid(1) + compid(1) + msgid(3) + payload + checksum(2)
|
|
1137
|
+
view.setUint8(offset++, 0); // incompat_flags
|
|
1138
|
+
view.setUint8(offset++, 0); // compat_flags
|
|
1139
|
+
view.setUint8(offset++, sequence);
|
|
1140
|
+
view.setUint8(offset++, systemId);
|
|
1141
|
+
view.setUint8(offset++, componentId);
|
|
1142
|
+
// 24-bit message ID in v2
|
|
1143
|
+
view.setUint8(offset++, messageId & 0xFF);
|
|
1144
|
+
view.setUint8(offset++, (messageId >> 8) & 0xFF);
|
|
1145
|
+
view.setUint8(offset++, (messageId >> 16) & 0xFF);
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
// MAVLink v1: magic(1) + len(1) + seq(1) + sysid(1) + compid(1) + msgid(1) + payload + checksum(2)
|
|
1149
|
+
view.setUint8(offset++, sequence);
|
|
1150
|
+
view.setUint8(offset++, systemId);
|
|
1151
|
+
view.setUint8(offset++, componentId);
|
|
1152
|
+
view.setUint8(offset++, messageId & 0xFF); // 8-bit message ID in v1
|
|
1153
|
+
}
|
|
1154
|
+
// Payload
|
|
1155
|
+
const payloadView = new Uint8Array(buffer, offset, payload.length);
|
|
1156
|
+
payloadView.set(payload);
|
|
1157
|
+
offset += payload.length;
|
|
1158
|
+
// Calculate proper MAVLink CRC with CRC_EXTRA
|
|
1159
|
+
const crcExtra = CRC_EXTRA[messageId];
|
|
1160
|
+
if (crcExtra === undefined) {
|
|
1161
|
+
throw new Error("No CRC_EXTRA defined for message ID " + messageId);
|
|
1162
|
+
}
|
|
1163
|
+
// Get message data (exclude start byte and checksum)
|
|
1164
|
+
const messageData = new Uint8Array(buffer, 1, offset - 1);
|
|
1165
|
+
const checksum = MAVLinkCRC.calculate(messageData, crcExtra);
|
|
1166
|
+
// Checksum (little endian)
|
|
1167
|
+
view.setUint8(offset++, checksum & 0xFF);
|
|
1168
|
+
view.setUint8(offset++, (checksum >> 8) & 0xFF);
|
|
1169
|
+
return new Uint8Array(buffer);
|
|
1170
|
+
}
|
|
778
1171
|
}
|
|
779
1172
|
const MESSAGE_DEFINITIONS = [
|
|
780
1173
|
{
|
|
@@ -790,19 +1183,19 @@ const MESSAGE_DEFINITIONS = [
|
|
|
790
1183
|
type: 'uint8_t',
|
|
791
1184
|
},
|
|
792
1185
|
{
|
|
793
|
-
name: '
|
|
1186
|
+
name: 'base_mode',
|
|
794
1187
|
type: 'uint8_t',
|
|
795
1188
|
},
|
|
796
1189
|
{
|
|
797
|
-
name: '
|
|
1190
|
+
name: 'custom_mode',
|
|
798
1191
|
type: 'uint32_t',
|
|
799
1192
|
},
|
|
800
1193
|
{
|
|
801
|
-
name: '
|
|
1194
|
+
name: 'system_status',
|
|
802
1195
|
type: 'uint8_t',
|
|
803
1196
|
},
|
|
804
1197
|
{
|
|
805
|
-
name: '
|
|
1198
|
+
name: 'mavlink_version',
|
|
806
1199
|
type: 'uint8_t_mavlink_version',
|
|
807
1200
|
},
|
|
808
1201
|
]
|
|
@@ -816,21 +1209,21 @@ const MESSAGE_DEFINITIONS = [
|
|
|
816
1209
|
type: 'uint16_t',
|
|
817
1210
|
},
|
|
818
1211
|
{
|
|
819
|
-
name: '
|
|
1212
|
+
name: 'min_version',
|
|
820
1213
|
type: 'uint16_t',
|
|
821
1214
|
},
|
|
822
1215
|
{
|
|
823
|
-
name: '
|
|
1216
|
+
name: 'max_version',
|
|
824
1217
|
type: 'uint16_t',
|
|
825
1218
|
},
|
|
826
1219
|
{
|
|
827
|
-
name: '
|
|
828
|
-
type: 'uint8_t
|
|
1220
|
+
name: 'spec_version_hash',
|
|
1221
|
+
type: 'uint8_t',
|
|
829
1222
|
arrayLength: 8,
|
|
830
1223
|
},
|
|
831
1224
|
{
|
|
832
|
-
name: '
|
|
833
|
-
type: 'uint8_t
|
|
1225
|
+
name: 'library_version_hash',
|
|
1226
|
+
type: 'uint8_t',
|
|
834
1227
|
arrayLength: 8,
|
|
835
1228
|
},
|
|
836
1229
|
]
|
|
@@ -854,6 +1247,43 @@ class MinimalParser extends DialectParser {
|
|
|
854
1247
|
}
|
|
855
1248
|
}
|
|
856
1249
|
}
|
|
1250
|
+
// Dialect-specific serializer
|
|
1251
|
+
class MinimalSerializer {
|
|
1252
|
+
constructor() {
|
|
1253
|
+
this.parser = new MinimalParser();
|
|
1254
|
+
}
|
|
1255
|
+
// Serialize a message to MAVLink bytes
|
|
1256
|
+
serialize(message) {
|
|
1257
|
+
return this.parser.serializeMessage(message);
|
|
1258
|
+
}
|
|
1259
|
+
// Complete a message with all defined fields (including extension fields with defaults)
|
|
1260
|
+
// This is useful to see what the serializer would send without actually serializing
|
|
1261
|
+
// Requires payload structure format
|
|
1262
|
+
completeMessage(message) {
|
|
1263
|
+
const messageDef = Array.from(this.parser['messageDefinitions'].values())
|
|
1264
|
+
.find(def => def.name === message.message_name);
|
|
1265
|
+
if (!messageDef) {
|
|
1266
|
+
throw new Error(`Unknown message type: ${message.message_name}`);
|
|
1267
|
+
}
|
|
1268
|
+
// Extract fields from payload structure (throws error if not payload format)
|
|
1269
|
+
const messageFields = this.parser['extractMessageFields'](message, messageDef.fields);
|
|
1270
|
+
// Complete the message with defaults
|
|
1271
|
+
const completedFields = this.parser['completeMessageWithDefaults'](messageFields, messageDef.fields);
|
|
1272
|
+
// Return in the payload structure format
|
|
1273
|
+
return {
|
|
1274
|
+
...message,
|
|
1275
|
+
payload: completedFields
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
// Get supported message names for this dialect
|
|
1279
|
+
getSupportedMessages() {
|
|
1280
|
+
return Array.from(this.parser['messageDefinitions'].values()).map(def => def.name);
|
|
1281
|
+
}
|
|
1282
|
+
// Check if a message is supported by this dialect
|
|
1283
|
+
supportsMessage(messageName) {
|
|
1284
|
+
return Array.from(this.parser['messageDefinitions'].values()).some(def => def.name === messageName);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
857
1287
|
|
|
858
|
-
export { MAV_AUTOPILOTEnum, MAV_COMPONENTEnum, MAV_MODE_FLAGEnum, MAV_MODE_FLAG_DECODE_POSITIONEnum, MAV_STATEEnum, MAV_TYPEEnum, MinimalParser, isHeartbeat, isProtocolVersion };
|
|
1288
|
+
export { MAV_AUTOPILOTEnum, MAV_COMPONENTEnum, MAV_MODE_FLAGEnum, MAV_MODE_FLAG_DECODE_POSITIONEnum, MAV_STATEEnum, MAV_TYPEEnum, MinimalParser, MinimalSerializer, isHeartbeat, isProtocolVersion };
|
|
859
1289
|
//# sourceMappingURL=index.js.map
|