@hla4ts/spacefom 0.1.0
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 +73 -0
- package/package.json +27 -0
- package/spacefom/SISO_SpaceFOM_datatypes.xml +377 -0
- package/spacefom/SISO_SpaceFOM_entity.xml +295 -0
- package/spacefom/SISO_SpaceFOM_environment.xml +98 -0
- package/spacefom/SISO_SpaceFOM_management.xml +272 -0
- package/spacefom/SISO_SpaceFOM_switches.xml +67 -0
- package/src/coders.ts +16 -0
- package/src/dashboard.ts +36 -0
- package/src/index.ts +11 -0
- package/src/spacefom-constants.ts +26 -0
- package/src/spacefom-encoding.test.ts +45 -0
- package/src/spacefom-encoding.ts +261 -0
- package/src/spacefom-presets.ts +58 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const FLOAT64_SIZE = 8;
|
|
2
|
+
const INT16_SIZE = 2;
|
|
3
|
+
const HLA_COUNT_SIZE = 4;
|
|
4
|
+
|
|
5
|
+
function assertLength(data: Uint8Array, expected: number, label: string): void {
|
|
6
|
+
if (data.length !== expected) {
|
|
7
|
+
throw new Error(`${label}: expected ${expected} bytes, got ${data.length}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function assertFloat64ArrayLength(data: Uint8Array, expected: number, label: string): void {
|
|
12
|
+
if (data.length !== expected * FLOAT64_SIZE) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`${label}: expected ${expected * FLOAT64_SIZE} bytes (${expected} float64 values), got ${data.length}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function encodeFloat64LE(value: number): Uint8Array {
|
|
20
|
+
const buffer = new ArrayBuffer(FLOAT64_SIZE);
|
|
21
|
+
const view = new DataView(buffer);
|
|
22
|
+
view.setFloat64(0, value, true);
|
|
23
|
+
return new Uint8Array(buffer);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function decodeFloat64LE(data: Uint8Array): number {
|
|
27
|
+
assertLength(data, FLOAT64_SIZE, "HLAfloat64LE");
|
|
28
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
29
|
+
return view.getFloat64(0, true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function encodeScalar(value: number): Uint8Array {
|
|
33
|
+
return encodeFloat64LE(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function decodeScalar(data: Uint8Array): number {
|
|
37
|
+
return decodeFloat64LE(data);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function encodeInt16LE(value: number): Uint8Array {
|
|
41
|
+
const buffer = new ArrayBuffer(INT16_SIZE);
|
|
42
|
+
const view = new DataView(buffer);
|
|
43
|
+
view.setInt16(0, value, true);
|
|
44
|
+
return new Uint8Array(buffer);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function decodeInt16LE(data: Uint8Array): number {
|
|
48
|
+
assertLength(data, INT16_SIZE, "HLAinteger16LE");
|
|
49
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
50
|
+
return view.getInt16(0, true);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function encodeHLAunicodeString(value: string): Uint8Array {
|
|
54
|
+
const length = value.length;
|
|
55
|
+
const buffer = new ArrayBuffer(HLA_COUNT_SIZE + length * 2);
|
|
56
|
+
const view = new DataView(buffer);
|
|
57
|
+
view.setUint32(0, length, false);
|
|
58
|
+
for (let i = 0; i < length; i += 1) {
|
|
59
|
+
view.setUint16(HLA_COUNT_SIZE + i * 2, value.charCodeAt(i), false);
|
|
60
|
+
}
|
|
61
|
+
return new Uint8Array(buffer);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function decodeHLAunicodeString(data: Uint8Array): string {
|
|
65
|
+
if (data.length < HLA_COUNT_SIZE) {
|
|
66
|
+
throw new Error("HLAunicodeString: insufficient data");
|
|
67
|
+
}
|
|
68
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
69
|
+
const length = view.getUint32(0, false);
|
|
70
|
+
const expected = HLA_COUNT_SIZE + length * 2;
|
|
71
|
+
if (data.length !== expected) {
|
|
72
|
+
throw new Error(`HLAunicodeString: expected ${expected} bytes, got ${data.length}`);
|
|
73
|
+
}
|
|
74
|
+
let result = "";
|
|
75
|
+
for (let i = 0; i < length; i += 1) {
|
|
76
|
+
result += String.fromCharCode(view.getUint16(HLA_COUNT_SIZE + i * 2, false));
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
function encodeFloat64Array(values: number[], expectedLength: number, label: string): Uint8Array {
|
|
83
|
+
if (values.length !== expectedLength) {
|
|
84
|
+
throw new Error(`${label}: expected ${expectedLength} values, got ${values.length}`);
|
|
85
|
+
}
|
|
86
|
+
const buffer = new ArrayBuffer(values.length * FLOAT64_SIZE);
|
|
87
|
+
const view = new DataView(buffer);
|
|
88
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
89
|
+
view.setFloat64(i * FLOAT64_SIZE, values[i], true);
|
|
90
|
+
}
|
|
91
|
+
return new Uint8Array(buffer);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function decodeFloat64Array(data: Uint8Array, expectedLength: number, label: string): number[] {
|
|
95
|
+
assertFloat64ArrayLength(data, expectedLength, label);
|
|
96
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
97
|
+
const values = new Array<number>(expectedLength);
|
|
98
|
+
for (let i = 0; i < expectedLength; i += 1) {
|
|
99
|
+
values[i] = view.getFloat64(i * FLOAT64_SIZE, true);
|
|
100
|
+
}
|
|
101
|
+
return values;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type Vector3 = [number, number, number];
|
|
105
|
+
export type Matrix3 = [number, number, number, number, number, number, number, number, number];
|
|
106
|
+
|
|
107
|
+
export interface AttitudeQuaternion {
|
|
108
|
+
scalar: number;
|
|
109
|
+
vector: Vector3;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ReferenceFrameTranslation {
|
|
113
|
+
position: Vector3;
|
|
114
|
+
velocity: Vector3;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ReferenceFrameRotation {
|
|
118
|
+
attitude: AttitudeQuaternion;
|
|
119
|
+
angularVelocity: Vector3;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface SpaceTimeCoordinateState {
|
|
123
|
+
translation: ReferenceFrameTranslation;
|
|
124
|
+
rotation: ReferenceFrameRotation;
|
|
125
|
+
time: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function encodeVector3(values: Vector3): Uint8Array {
|
|
129
|
+
return encodeFloat64Array(values, 3, "Vector3");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function decodeVector3(data: Uint8Array): Vector3 {
|
|
133
|
+
const values = decodeFloat64Array(data, 3, "Vector3");
|
|
134
|
+
return [values[0], values[1], values[2]];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function encodeMatrix3(values: Matrix3): Uint8Array {
|
|
138
|
+
return encodeFloat64Array(values, 9, "Matrix3");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function decodeMatrix3(data: Uint8Array): Matrix3 {
|
|
142
|
+
const values = decodeFloat64Array(data, 9, "Matrix3");
|
|
143
|
+
return [
|
|
144
|
+
values[0],
|
|
145
|
+
values[1],
|
|
146
|
+
values[2],
|
|
147
|
+
values[3],
|
|
148
|
+
values[4],
|
|
149
|
+
values[5],
|
|
150
|
+
values[6],
|
|
151
|
+
values[7],
|
|
152
|
+
values[8],
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function encodeAttitudeQuaternion(value: AttitudeQuaternion): Uint8Array {
|
|
157
|
+
return encodeFloat64Array(
|
|
158
|
+
[value.scalar, value.vector[0], value.vector[1], value.vector[2]],
|
|
159
|
+
4,
|
|
160
|
+
"AttitudeQuaternion"
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function decodeAttitudeQuaternion(data: Uint8Array): AttitudeQuaternion {
|
|
165
|
+
const values = decodeFloat64Array(data, 4, "AttitudeQuaternion");
|
|
166
|
+
return {
|
|
167
|
+
scalar: values[0],
|
|
168
|
+
vector: [values[1], values[2], values[3]],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function encodeReferenceFrameTranslation(value: ReferenceFrameTranslation): Uint8Array {
|
|
173
|
+
return encodeFloat64Array(
|
|
174
|
+
[
|
|
175
|
+
value.position[0],
|
|
176
|
+
value.position[1],
|
|
177
|
+
value.position[2],
|
|
178
|
+
value.velocity[0],
|
|
179
|
+
value.velocity[1],
|
|
180
|
+
value.velocity[2],
|
|
181
|
+
],
|
|
182
|
+
6,
|
|
183
|
+
"ReferenceFrameTranslation"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function decodeReferenceFrameTranslation(data: Uint8Array): ReferenceFrameTranslation {
|
|
188
|
+
const values = decodeFloat64Array(data, 6, "ReferenceFrameTranslation");
|
|
189
|
+
return {
|
|
190
|
+
position: [values[0], values[1], values[2]],
|
|
191
|
+
velocity: [values[3], values[4], values[5]],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function encodeReferenceFrameRotation(value: ReferenceFrameRotation): Uint8Array {
|
|
196
|
+
return encodeFloat64Array(
|
|
197
|
+
[
|
|
198
|
+
value.attitude.scalar,
|
|
199
|
+
value.attitude.vector[0],
|
|
200
|
+
value.attitude.vector[1],
|
|
201
|
+
value.attitude.vector[2],
|
|
202
|
+
value.angularVelocity[0],
|
|
203
|
+
value.angularVelocity[1],
|
|
204
|
+
value.angularVelocity[2],
|
|
205
|
+
],
|
|
206
|
+
7,
|
|
207
|
+
"ReferenceFrameRotation"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function decodeReferenceFrameRotation(data: Uint8Array): ReferenceFrameRotation {
|
|
212
|
+
const values = decodeFloat64Array(data, 7, "ReferenceFrameRotation");
|
|
213
|
+
return {
|
|
214
|
+
attitude: {
|
|
215
|
+
scalar: values[0],
|
|
216
|
+
vector: [values[1], values[2], values[3]],
|
|
217
|
+
},
|
|
218
|
+
angularVelocity: [values[4], values[5], values[6]],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function encodeSpaceTimeCoordinateState(value: SpaceTimeCoordinateState): Uint8Array {
|
|
223
|
+
return encodeFloat64Array(
|
|
224
|
+
[
|
|
225
|
+
value.translation.position[0],
|
|
226
|
+
value.translation.position[1],
|
|
227
|
+
value.translation.position[2],
|
|
228
|
+
value.translation.velocity[0],
|
|
229
|
+
value.translation.velocity[1],
|
|
230
|
+
value.translation.velocity[2],
|
|
231
|
+
value.rotation.attitude.scalar,
|
|
232
|
+
value.rotation.attitude.vector[0],
|
|
233
|
+
value.rotation.attitude.vector[1],
|
|
234
|
+
value.rotation.attitude.vector[2],
|
|
235
|
+
value.rotation.angularVelocity[0],
|
|
236
|
+
value.rotation.angularVelocity[1],
|
|
237
|
+
value.rotation.angularVelocity[2],
|
|
238
|
+
value.time,
|
|
239
|
+
],
|
|
240
|
+
14,
|
|
241
|
+
"SpaceTimeCoordinateState"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function decodeSpaceTimeCoordinateState(data: Uint8Array): SpaceTimeCoordinateState {
|
|
246
|
+
const values = decodeFloat64Array(data, 14, "SpaceTimeCoordinateState");
|
|
247
|
+
return {
|
|
248
|
+
translation: {
|
|
249
|
+
position: [values[0], values[1], values[2]],
|
|
250
|
+
velocity: [values[3], values[4], values[5]],
|
|
251
|
+
},
|
|
252
|
+
rotation: {
|
|
253
|
+
attitude: {
|
|
254
|
+
scalar: values[6],
|
|
255
|
+
vector: [values[7], values[8], values[9]],
|
|
256
|
+
},
|
|
257
|
+
angularVelocity: [values[10], values[11], values[12]],
|
|
258
|
+
},
|
|
259
|
+
time: values[13],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { FomModuleSet } from "@hla4ts/hla-api";
|
|
5
|
+
|
|
6
|
+
export const SpaceFomBaseModules = [
|
|
7
|
+
"datatypes",
|
|
8
|
+
"entity",
|
|
9
|
+
"environment",
|
|
10
|
+
"management",
|
|
11
|
+
"switches",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type SpaceFomBaseModuleName = (typeof SpaceFomBaseModules)[number];
|
|
15
|
+
export type SpaceFomModuleFormat = "inline" | "file";
|
|
16
|
+
|
|
17
|
+
const SPACEFOM_MODULE_FILES: Record<SpaceFomBaseModuleName, string> = {
|
|
18
|
+
datatypes: "SISO_SpaceFOM_datatypes.xml",
|
|
19
|
+
entity: "SISO_SpaceFOM_entity.xml",
|
|
20
|
+
environment: "SISO_SpaceFOM_environment.xml",
|
|
21
|
+
management: "SISO_SpaceFOM_management.xml",
|
|
22
|
+
switches: "SISO_SpaceFOM_switches.xml",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface SpaceFomModuleLoadOptions {
|
|
26
|
+
modules?: SpaceFomBaseModuleName[];
|
|
27
|
+
format?: SpaceFomModuleFormat;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveSpaceFomModulePath(name: SpaceFomBaseModuleName): string {
|
|
31
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
return path.resolve(baseDir, "..", "spacefom", SPACEFOM_MODULE_FILES[name]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function loadSpaceFomModules(
|
|
36
|
+
options: SpaceFomModuleLoadOptions = {}
|
|
37
|
+
): Promise<FomModuleSet> {
|
|
38
|
+
const modules = options.modules ?? [...SpaceFomBaseModules];
|
|
39
|
+
const format = options.format ?? "inline";
|
|
40
|
+
const result: FomModuleSet = [];
|
|
41
|
+
|
|
42
|
+
for (const moduleName of modules) {
|
|
43
|
+
const modulePath = resolveSpaceFomModulePath(moduleName);
|
|
44
|
+
if (format === "file") {
|
|
45
|
+
const content = await fs.readFile(modulePath);
|
|
46
|
+
result.push({
|
|
47
|
+
type: "file",
|
|
48
|
+
name: path.basename(modulePath),
|
|
49
|
+
content: new Uint8Array(content),
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
const content = await fs.readFile(modulePath, "utf8");
|
|
53
|
+
result.push({ type: "inline", content });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|