trackswap 0.0.2 → 0.0.3

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.
Files changed (82) hide show
  1. package/README.md +322 -281
  2. package/dist/FIT/base.d.ts +0 -30
  3. package/dist/FIT/decoder.d.ts +0 -48
  4. package/dist/FIT/encoder.d.ts +0 -97
  5. package/dist/FIT/mesg.d.ts +0 -3
  6. package/dist/FIT/middleware.d.ts +0 -15
  7. package/dist/FIT/plugins.d.ts +0 -9
  8. package/dist/FIT/processor.d.ts +0 -24
  9. package/dist/FIT/types/mesg_num.d.ts +102 -102
  10. package/dist/FIT/types/sport.d.ts +44 -44
  11. package/dist/FIT/types.d.ts +0 -35
  12. package/dist/GPX/base.d.ts +0 -39
  13. package/dist/GPX/converters.d.ts +0 -30
  14. package/dist/GPX/decoder.d.ts +0 -66
  15. package/dist/GPX/encoder.d.ts +0 -109
  16. package/dist/GPX/middleware.d.ts +0 -9
  17. package/dist/GPX/processor.d.ts +0 -18
  18. package/dist/GPX/types.d.ts +0 -108
  19. package/dist/TCX/base.d.ts +0 -33
  20. package/dist/TCX/converters.d.ts +0 -24
  21. package/dist/TCX/decoder.d.ts +0 -54
  22. package/dist/TCX/encoder.d.ts +0 -109
  23. package/dist/TCX/index.d.ts +4 -1
  24. package/dist/TCX/middleware.d.ts +0 -9
  25. package/dist/TCX/processor.d.ts +0 -18
  26. package/dist/TCX/types.d.ts +0 -13
  27. package/dist/TrackSwap.d.ts +37 -33
  28. package/dist/core/base.d.ts +0 -46
  29. package/dist/core/xml-utils.d.ts +0 -24
  30. package/dist/index.js +24 -5
  31. package/dist/sport/base.d.ts +63 -0
  32. package/dist/sport/converters.d.ts +33 -0
  33. package/dist/sport/encoders.d.ts +34 -0
  34. package/dist/sport/index.d.ts +17 -0
  35. package/dist/sport/processor.d.ts +19 -0
  36. package/dist/types.d.ts +91 -13
  37. package/package.json +18 -3
  38. package/dist/FIT/base.js +0 -101
  39. package/dist/FIT/crc.js +0 -16
  40. package/dist/FIT/decoder.js +0 -323
  41. package/dist/FIT/encoder.js +0 -445
  42. package/dist/FIT/index.js +0 -13
  43. package/dist/FIT/mesg.js +0 -208
  44. package/dist/FIT/middleware.js +0 -206
  45. package/dist/FIT/plugins.js +0 -104
  46. package/dist/FIT/processor.js +0 -170
  47. package/dist/FIT/types/course_point.js +0 -58
  48. package/dist/FIT/types/event.js +0 -50
  49. package/dist/FIT/types/event_type.js +0 -19
  50. package/dist/FIT/types/file.js +0 -24
  51. package/dist/FIT/types/index.js +0 -297
  52. package/dist/FIT/types/mesg_num.js +0 -106
  53. package/dist/FIT/types/sport.js +0 -48
  54. package/dist/FIT/types.js +0 -1
  55. package/dist/FIT/util.js +0 -7
  56. package/dist/GPX/base.js +0 -56
  57. package/dist/GPX/converters.js +0 -352
  58. package/dist/GPX/decoder.js +0 -409
  59. package/dist/GPX/encoder.js +0 -513
  60. package/dist/GPX/extensions/index.js +0 -14
  61. package/dist/GPX/index.js +0 -11
  62. package/dist/GPX/middleware.js +0 -93
  63. package/dist/GPX/processor.js +0 -188
  64. package/dist/GPX/types.js +0 -1
  65. package/dist/GPX/util.js +0 -18
  66. package/dist/TCX/base.js +0 -62
  67. package/dist/TCX/converters.js +0 -333
  68. package/dist/TCX/decoder.js +0 -350
  69. package/dist/TCX/encoder.js +0 -513
  70. package/dist/TCX/index.js +0 -9
  71. package/dist/TCX/middleware.js +0 -93
  72. package/dist/TCX/processor.js +0 -197
  73. package/dist/TCX/types.js +0 -1
  74. package/dist/TrackSwap.js +0 -84
  75. package/dist/core/base.js +0 -138
  76. package/dist/core/decoder.d.ts +0 -66
  77. package/dist/core/decoder.js +0 -90
  78. package/dist/core/index.js +0 -4
  79. package/dist/core/processor.d.ts +0 -69
  80. package/dist/core/processor.js +0 -265
  81. package/dist/core/xml-utils.js +0 -140
  82. package/dist/types.js +0 -1
@@ -1,445 +0,0 @@
1
- /* global Blob */
2
- import { Mesg } from "./mesg.js";
3
- import { crc } from "./crc.js";
4
- import dayjs from "dayjs";
5
- const HEADER_LEN = 14;
6
- const PROTOCOL_VERSION = 0x10; // 1.0
7
- const PROFILE_VERSION = 2078; // 20.78
8
- const MAGIC = 0x2e464954; // ".FIT"
9
- /**
10
- * FIT 编码器 - 将 FITFileType 数据编码为 FIT 二进制格式
11
- */
12
- export class FITEncoder {
13
- /**
14
- * 构造函数
15
- * @param options 编码器选项
16
- */
17
- constructor(options = {}) {
18
- this.localNum = {};
19
- this.mesgDefn = [];
20
- this.messages = [];
21
- this.options = {
22
- compact: options.compact ?? true,
23
- defaultSport: options.defaultSport ?? "cycling",
24
- defaultSubSport: options.defaultSubSport ?? "generic",
25
- };
26
- }
27
- // ==================== 公共接口 ====================
28
- /**
29
- * 编码 FIT 数据为 Buffer - Activity 文件类型
30
- */
31
- async encode(fitData) {
32
- this.reset();
33
- this.buildFITContent(fitData, "activity");
34
- return Buffer.from(await this.blob.arrayBuffer());
35
- }
36
- /**
37
- * 编码 FIT 数据为 Buffer - Course 文件类型
38
- */
39
- async encodeCourse(fitData) {
40
- this.reset();
41
- this.buildFITContent(fitData, "course");
42
- return Buffer.from(await this.blob.arrayBuffer());
43
- }
44
- /**
45
- * 编码 FIT 数据为 Blob
46
- */
47
- async encodeToBlob(fitData, fileType = "activity") {
48
- this.reset();
49
- this.buildFITContent(fitData, fileType);
50
- return this.blob;
51
- }
52
- // ==================== 核心构建方法 ====================
53
- /**
54
- * 重置编码器状态
55
- */
56
- reset() {
57
- this.localNum = {};
58
- this.mesgDefn = [];
59
- this.messages = [];
60
- }
61
- /**
62
- * 构建 FIT 文件内容
63
- */
64
- buildFITContent(fitData, fileType) {
65
- // 1. 写入文件ID(必须第一个)
66
- this.buildFileId(fitData, fileType);
67
- // 2. 根据文件类型构建不同的内容
68
- if (fileType === "course") {
69
- this.buildCourseContent(fitData);
70
- }
71
- else {
72
- this.buildActivityContent(fitData);
73
- }
74
- }
75
- /**
76
- * 构建 Activity 文件内容
77
- */
78
- buildActivityContent(fitData) {
79
- const sessions = fitData.sessionMesgs || [];
80
- if (sessions.length === 0) {
81
- throw new Error("Activity文件必须包含至少一个Session");
82
- }
83
- // 处理所有sessions
84
- let totalSessions = 0;
85
- const allEvents = [];
86
- const allActivities = [];
87
- for (const session of sessions) {
88
- // 构建session相关的events
89
- if (session.lapMesgs) {
90
- this.buildEventsFromLaps(session.lapMesgs, allEvents);
91
- }
92
- // 构建records
93
- if (session.lapMesgs) {
94
- for (const lap of session.lapMesgs) {
95
- if (lap.recordMesgs) {
96
- this.buildRecords(lap.recordMesgs);
97
- }
98
- }
99
- }
100
- // 构建laps
101
- if (session.lapMesgs) {
102
- this.buildLaps(session.lapMesgs);
103
- }
104
- // 构建session
105
- this.buildSession(session);
106
- totalSessions++;
107
- // 构建activity
108
- this.buildActivityFromSession(session, allActivities);
109
- }
110
- // 写入所有events
111
- allEvents.forEach((event) => this.buildEvent(event));
112
- // 写入所有activities
113
- allActivities.forEach((activity) => this.buildActivity(activity));
114
- }
115
- /**
116
- * 构建 Course 文件内容
117
- */
118
- buildCourseContent(fitData) {
119
- // Course文件的特殊处理逻辑
120
- const sessions = fitData.sessionMesgs || [];
121
- if (sessions.length === 0) {
122
- throw new Error("Course文件必须包含轨迹数据");
123
- }
124
- const session = sessions[0]; // Course通常只有一个session
125
- // 写入course信息
126
- this.buildCourse({
127
- name: "course",
128
- sport: this.options.defaultSport,
129
- });
130
- // 构建events和records
131
- if (session.lapMesgs) {
132
- const allRecords = [];
133
- // 收集所有records
134
- for (const lap of session.lapMesgs) {
135
- if (lap.recordMesgs) {
136
- allRecords.push(...lap.recordMesgs);
137
- }
138
- }
139
- // 排序records(按时间戳或距离)
140
- allRecords.sort((a, b) => {
141
- if (a.timestamp && b.timestamp) {
142
- return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
143
- }
144
- return (a.distance || 0) - (b.distance || 0);
145
- });
146
- // 构建course points和records
147
- const startRecord = allRecords[0];
148
- const endRecord = allRecords[allRecords.length - 1];
149
- // 开始事件
150
- if (startRecord.timestamp) {
151
- this.buildEvent({
152
- timestamp: startRecord.timestamp,
153
- event: "timer",
154
- eventType: "start",
155
- eventGroup: 0,
156
- });
157
- }
158
- // 写入所有records
159
- allRecords.forEach((record) => this.buildRecord(record));
160
- // 结束事件
161
- if (endRecord.timestamp) {
162
- this.buildEvent({
163
- timestamp: endRecord.timestamp,
164
- event: "timer",
165
- eventType: "stop_all",
166
- eventGroup: 0,
167
- });
168
- }
169
- // 写入lap
170
- if (session.lapMesgs[0]) {
171
- this.buildLap(session.lapMesgs[0]);
172
- }
173
- }
174
- }
175
- // ==================== 辅助构建方法 ====================
176
- /**
177
- * 构建文件ID
178
- */
179
- buildFileId(fitData, fileType) {
180
- const fileId = fitData.fileIdMesgs?.[0] || {};
181
- this.writeMesg("file_id", {
182
- type: fileType,
183
- time_created: fileId.timeCreated
184
- ? this.parseTimestamp(fileId.timeCreated)
185
- : Date.now(),
186
- manufacturer: fileId.manufacturer,
187
- product: fileId.product,
188
- serial_number: fileId.serialNumber,
189
- });
190
- }
191
- /**
192
- * 构建Session
193
- */
194
- buildSession(session) {
195
- this.writeMesg("session", {
196
- timestamp: session.timestamp
197
- ? this.parseTimestamp(session.timestamp)
198
- : undefined,
199
- start_time: session.startTime
200
- ? this.parseTimestamp(session.startTime)
201
- : undefined,
202
- total_elapsed_time: session.totalElapsedTime,
203
- total_timer_time: session.totalTimerTime,
204
- start_position_lat: this.convertToSemicircles(session.startPositionLat),
205
- start_position_long: this.convertToSemicircles(session.startPositionLong),
206
- total_distance: session.totalDistance,
207
- total_ascent: session.totalAscent,
208
- total_descent: session.totalDescent,
209
- sport: session.sport || this.options.defaultSport,
210
- sub_sport: session.subSport || this.options.defaultSubSport,
211
- first_lap_index: session.firstLapIndex || 0,
212
- num_laps: session.numLaps || session.lapMesgs?.length || 1,
213
- });
214
- }
215
- /**
216
- * 构建Laps
217
- */
218
- buildLaps(laps) {
219
- laps.forEach((lap) => this.buildLap(lap));
220
- }
221
- /**
222
- * 构建单个Lap
223
- */
224
- buildLap(lap) {
225
- this.writeMesg("lap", {
226
- timestamp: lap.timestamp ? this.parseTimestamp(lap.timestamp) : undefined,
227
- start_time: lap.startTime
228
- ? this.parseTimestamp(lap.startTime)
229
- : undefined,
230
- start_position_lat: this.convertToSemicircles(lap.startPositionLat),
231
- start_position_long: this.convertToSemicircles(lap.startPositionLong),
232
- end_position_lat: this.convertToSemicircles(lap.endPositionLat),
233
- end_position_long: this.convertToSemicircles(lap.endPositionLong),
234
- total_elapsed_time: lap.totalElapsedTime,
235
- total_timer_time: lap.totalTimerTime,
236
- total_distance: lap.totalDistance,
237
- total_ascent: lap.totalAscent,
238
- total_descent: lap.totalDescent,
239
- });
240
- }
241
- /**
242
- * 构建Records
243
- */
244
- buildRecords(records) {
245
- records.forEach((record) => this.buildRecord(record));
246
- }
247
- /**
248
- * 构建单个Record
249
- */
250
- buildRecord(record) {
251
- this.writeMesg("record", {
252
- timestamp: record.timestamp
253
- ? this.parseTimestamp(record.timestamp)
254
- : undefined,
255
- position_lat: this.convertToSemicircles(record.positionLat),
256
- position_long: this.convertToSemicircles(record.positionLong),
257
- altitude: record.enhancedAltitude || record.altitude,
258
- distance: record.distance,
259
- heart_rate: record.heartRate,
260
- cadence: record.cadence,
261
- speed: record.enhancedSpeed || record.speed,
262
- power: record.power,
263
- });
264
- }
265
- /**
266
- * 构建Event
267
- */
268
- buildEvent(event) {
269
- this.writeMesg("event", {
270
- timestamp: event.timestamp
271
- ? this.parseTimestamp(event.timestamp)
272
- : undefined,
273
- event: event.event,
274
- event_type: event.eventType,
275
- event_group: event.eventGroup,
276
- });
277
- }
278
- /**
279
- * 构建Activity
280
- */
281
- buildActivity(activity) {
282
- this.writeMesg("activity", {
283
- timestamp: activity.timestamp
284
- ? this.parseTimestamp(activity.timestamp)
285
- : undefined,
286
- total_timer_time: activity.totalTimerTime,
287
- num_sessions: activity.numSessions,
288
- type: activity.type,
289
- event: activity.event,
290
- event_type: activity.eventType,
291
- local_timestamp: activity.localTimestamp
292
- ? this.parseTimestamp(activity.localTimestamp)
293
- : undefined,
294
- event_group: activity.eventGroup,
295
- });
296
- }
297
- /**
298
- * 构建Course
299
- */
300
- buildCourse(course) {
301
- this.writeMesg("course", {
302
- name: course.name,
303
- sport: course.sport,
304
- });
305
- }
306
- // ==================== 数据转换和验证方法 ====================
307
- /**
308
- * 从Laps构建Events
309
- */
310
- buildEventsFromLaps(laps, events) {
311
- laps.forEach((lap, index) => {
312
- // 开始事件
313
- if (lap.startTime) {
314
- events.push({
315
- timestamp: lap.startTime,
316
- event: "timer",
317
- eventType: "start",
318
- eventGroup: index,
319
- });
320
- }
321
- // 结束事件
322
- if (lap.timestamp) {
323
- events.push({
324
- timestamp: lap.timestamp,
325
- event: "timer",
326
- eventType: index === laps.length - 1 ? "stop_all" : "stop",
327
- eventGroup: index,
328
- });
329
- }
330
- });
331
- }
332
- /**
333
- * 从Session构建Activity
334
- */
335
- buildActivityFromSession(session, activities) {
336
- activities.push({
337
- timestamp: session.timestamp,
338
- totalTimerTime: session.totalTimerTime,
339
- numSessions: 1,
340
- type: session.sport || this.options.defaultSport,
341
- event: "activity",
342
- eventType: "stop",
343
- eventGroup: 0,
344
- });
345
- }
346
- /**
347
- * 解析时间戳
348
- */
349
- parseTimestamp(timestamp) {
350
- if (typeof timestamp === "number") {
351
- return timestamp;
352
- }
353
- const date = dayjs(timestamp);
354
- return date.valueOf();
355
- }
356
- /**
357
- * 转换坐标为半圆单位
358
- */
359
- convertToSemicircles(coord) {
360
- if (coord === undefined || coord === null)
361
- return undefined;
362
- return Math.round((coord * 2 ** 31) / 180);
363
- }
364
- /**
365
- * 验证必要字段
366
- */
367
- validateRequiredFields(fitData) {
368
- if (!fitData.sessionMesgs || fitData.sessionMesgs.length === 0) {
369
- throw new Error("FIT文件必须包含至少一个Session");
370
- }
371
- }
372
- // ==================== 低级别消息写入方法 ====================
373
- /**
374
- * 写入消息
375
- */
376
- writeMesg(mesgName, values) {
377
- // 过滤掉undefined值
378
- const filteredValues = Object.fromEntries(Object.entries(values).filter(([_, value]) => value !== undefined));
379
- if (Object.keys(filteredValues).length === 0)
380
- return;
381
- let localNum = this.localNum[mesgName];
382
- if (localNum === undefined) {
383
- localNum = this.localNum[mesgName] = Object.keys(this.localNum).length;
384
- }
385
- const mesg = new Mesg(localNum, mesgName, filteredValues);
386
- const mesgDefn = this.mesgDefn[localNum];
387
- if (!mesgDefn || !mesg.isSameDefn(mesgDefn)) {
388
- this.messages.push(mesg.defnRecord);
389
- this.mesgDefn[localNum] = mesg.mesgDefn;
390
- }
391
- this.messages.push(mesg.dataRecord);
392
- }
393
- // ==================== 文件生成属性 ====================
394
- /**
395
- * 获取Blob对象
396
- */
397
- get blob() {
398
- const content = [this.header, ...this.messages, this.trailer];
399
- return new Blob(content, { type: "application/octet-stream" });
400
- }
401
- /**
402
- * 获取数据长度
403
- */
404
- get dataLen() {
405
- return this.messages.reduce((len, message) => len + message.byteLength, 0);
406
- }
407
- /**
408
- * 获取数据CRC
409
- */
410
- get dataCrc() {
411
- return this.messages.reduce((dataCrc, message) => crc(message, dataCrc), 0);
412
- }
413
- /**
414
- * 获取文件头
415
- */
416
- get header() {
417
- const dv = new DataView(new ArrayBuffer(HEADER_LEN));
418
- dv.setUint8(0, HEADER_LEN);
419
- dv.setUint8(1, PROTOCOL_VERSION);
420
- dv.setUint16(2, PROFILE_VERSION, true);
421
- dv.setUint32(4, this.dataLen, true);
422
- dv.setUint32(8, MAGIC);
423
- dv.setUint16(12, crc(dv.buffer.slice(0, 12)), true);
424
- return dv.buffer;
425
- }
426
- /**
427
- * 获取文件尾
428
- */
429
- get trailer() {
430
- const dv = new DataView(new ArrayBuffer(2));
431
- dv.setUint16(0, this.dataCrc, true);
432
- return dv.buffer;
433
- }
434
- // ==================== 兼容性方法 (Deprecated) ====================
435
- /**
436
- * @deprecated 使用 encode() 替代
437
- */
438
- async encoder(fitData) {
439
- console.warn("encoder() 方法已废弃,请使用 encode() 方法");
440
- return this.encode(fitData);
441
- }
442
- }
443
- export default {
444
- FITEncoder,
445
- };
package/dist/FIT/index.js DELETED
@@ -1,13 +0,0 @@
1
- // ============ 核心类和接口 ============
2
- export { FITDecoder } from "./decoder.js";
3
- export { FITEncoder } from "./encoder.js";
4
- // ============ 基础架构 ============
5
- export { BaseFITMessageConverter, BaseFITStructurePlugin, BaseFITMiddleware, } from "./base.js";
6
- // ============ 默认插件 ============
7
- export { SessionStructurePlugin, CourseStructurePlugin, FileHeaderPlugin, } from "./plugins.js";
8
- // ============ 类型定义 ============
9
- export * from "./types.js";
10
- // ============ 默认导出 ============
11
- import { FITDecoder } from "./decoder.js";
12
- import { FITEncoder } from "./encoder.js";
13
- export default { FITDecoder, FITEncoder };
package/dist/FIT/mesg.js DELETED
@@ -1,208 +0,0 @@
1
- import { types, encodedStrlen } from "./types/index.js";
2
- import { MesgNum } from "./types/mesg_num.js";
3
- const mesgDefns = {
4
- file_id: {
5
- mesgNum: MesgNum.FILE_ID,
6
- fieldDefns: [
7
- { name: "type", number: 0, type: "enum_file" },
8
- { name: "time_created", number: 4, type: "date_time" },
9
- ],
10
- },
11
- session: {
12
- mesgNum: MesgNum.SESSION,
13
- fieldDefns: [
14
- { name: "timestamp", number: 253, type: "date_time" },
15
- { name: "start_time", number: 2, type: "date_time" },
16
- { name: "total_elapsed_time", number: 7, type: "seconds" },
17
- { name: "total_timer_time", number: 8, type: "seconds" },
18
- { name: "start_position_lat", number: 3, type: "semicircles" },
19
- { name: "start_position_long", number: 4, type: "semicircles" },
20
- { name: "total_distance", number: 9, type: "distance" },
21
- { name: "total_ascent", number: 21, type: "uint16" },
22
- { name: "total_descent", number: 22, type: "uint16" },
23
- { name: "sport", number: 5, type: "enum_sport" },
24
- { name: "sub_sport", number: 6, type: "enum_sub_sport" },
25
- { name: "first_lap_index", number: 11, type: "uint16" },
26
- { name: "num_laps", number: 26, type: "uint16" },
27
- ],
28
- },
29
- lap: {
30
- mesgNum: MesgNum.LAP,
31
- fieldDefns: [
32
- { name: "timestamp", number: 253, type: "date_time" },
33
- { name: "start_time", number: 2, type: "date_time" },
34
- { name: "start_position_lat", number: 3, type: "semicircles" },
35
- { name: "start_position_long", number: 4, type: "semicircles" },
36
- { name: "end_position_lat", number: 5, type: "semicircles" },
37
- { name: "end_position_long", number: 6, type: "semicircles" },
38
- { name: "total_elapsed_time", number: 7, type: "seconds" },
39
- { name: "total_timer_time", number: 8, type: "seconds" },
40
- { name: "total_distance", number: 9, type: "distance" },
41
- { name: "total_ascent", number: 21, type: "uint16" },
42
- { name: "total_descent", number: 22, type: "uint16" },
43
- ],
44
- },
45
- record: {
46
- mesgNum: MesgNum.RECORD,
47
- fieldDefns: [
48
- { name: "timestamp", number: 253, type: "date_time" },
49
- { name: "position_lat", number: 0, type: "semicircles" },
50
- { name: "position_long", number: 1, type: "semicircles" },
51
- { name: "altitude", number: 2, type: "altitude" },
52
- { name: "distance", number: 5, type: "distance" },
53
- { name: "heart_rate", number: 3, type: "uint8" },
54
- { name: "cadence", number: 4, type: "uint8" },
55
- { name: "speed", number: 6, type: "speed" },
56
- { name: "power", number: 7, type: "uint16" },
57
- ],
58
- },
59
- event: {
60
- mesgNum: MesgNum.EVENT,
61
- fieldDefns: [
62
- { name: "timestamp", number: 253, type: "date_time" },
63
- { name: "event", number: 0, type: "enum_event" },
64
- { name: "event_type", number: 1, type: "enum_event_type" },
65
- { name: "event_group", number: 4, type: "uint8" },
66
- ],
67
- },
68
- course: {
69
- mesgNum: MesgNum.COURSE,
70
- fieldDefns: [
71
- { name: "name", number: 5, type: "string" },
72
- { name: "sport", number: 4, type: "enum_sport" },
73
- { name: "sub_sport", number: 7, type: "enum_sub_sport" },
74
- ],
75
- },
76
- activity: {
77
- mesgNum: MesgNum.ACTIVITY,
78
- fieldDefns: [
79
- { name: "timestamp", number: 253, type: "date_time" },
80
- { name: "total_timer_time", number: 0, type: "seconds" },
81
- { name: "num_sessions", number: 1, type: "uint16" },
82
- { name: "type", number: 2, type: "enum_activity" },
83
- { name: "event", number: 3, type: "enum_event" },
84
- { name: "event_type", number: 4, type: "enum_event_type" },
85
- { name: "local_timestamp", number: 5, type: "date_time" },
86
- { name: "event_group", number: 6, type: "uint8" },
87
- ],
88
- },
89
- course_point: {
90
- mesgNum: MesgNum.COURSE_POINT,
91
- fieldDefns: [
92
- { name: "timestamp", number: 1, type: "date_time" },
93
- { name: "position_lat", number: 2, type: "semicircles" },
94
- { name: "position_long", number: 3, type: "semicircles" },
95
- { name: "distance", number: 4, type: "distance" },
96
- { name: "type", number: 5, type: "enum_course_point" },
97
- ],
98
- },
99
- };
100
- /**
101
- * 将 fieldDefns 和 fieldValues 转换为 fields 对象
102
- */
103
- const fields = (fieldDefns, fieldValues) => {
104
- return fieldDefns
105
- .map((fieldDefn) => ({ ...fieldDefn, value: fieldValues[fieldDefn.name] }))
106
- .filter(({ value }) => value !== undefined);
107
- };
108
- export class Mesg {
109
- static check(mesgName, mesgNum, fieldDefns, values) {
110
- if (mesgNum === undefined) {
111
- throw new Error(`Message '${mesgName}' not known`);
112
- }
113
- if (fieldDefns === undefined) {
114
- throw new Error(`Message '${mesgName}' has no field definitions`);
115
- }
116
- const fieldNames = fieldDefns.map((fieldDefn) => fieldDefn.name);
117
- const unknownFields = Object.keys(values).filter((fieldName) => !fieldNames.includes(fieldName));
118
- if (unknownFields.length) {
119
- throw new Error(`Message '${mesgName}' has no field definitions named '${unknownFields}'`);
120
- }
121
- }
122
- constructor(localNum, mesgName, values) {
123
- const mesgDefn = mesgDefns[mesgName];
124
- if (!mesgDefn) {
125
- throw new Error(`Unknown message type: ${mesgName}`);
126
- }
127
- const { mesgNum, fieldDefns } = mesgDefn;
128
- Mesg.check(mesgName, mesgNum, fieldDefns, values);
129
- this.localNum = localNum;
130
- this.mesgNum = mesgNum;
131
- this.fields = fields(fieldDefns, values);
132
- }
133
- /**
134
- * 获取 defns,并获取了 size
135
- */
136
- get mesgDefn() {
137
- const fieldDefns = this.fields.map(({ number, type, value }) => {
138
- const typeInfo = types[type];
139
- if (!typeInfo) {
140
- throw new Error(`Unknown type: ${type}`);
141
- }
142
- const { size, baseType } = typeInfo;
143
- if (type === "string") {
144
- return { number, size: encodedStrlen(value), baseType };
145
- }
146
- return { number, size, baseType };
147
- });
148
- return {
149
- localNum: this.localNum,
150
- mesgNum: this.mesgNum,
151
- fieldDefns,
152
- };
153
- }
154
- isSameDefn(mesgDefn) {
155
- const isSameFieldDefn = (defn1, defn2) => defn1.number === defn2.number &&
156
- defn1.size === defn2.size &&
157
- defn1.baseType === defn2.baseType;
158
- const areSameFieldDefns = (defns1, defns2) => defns1.length === defns2.length &&
159
- defns1.every((defn1, i) => isSameFieldDefn(defn1, defns2[i]));
160
- const { localNum, mesgNum, fieldDefns } = this.mesgDefn;
161
- return (mesgNum === mesgDefn.mesgNum &&
162
- localNum === mesgDefn.localNum &&
163
- areSameFieldDefns(fieldDefns, mesgDefn.fieldDefns));
164
- }
165
- get defnRecord() {
166
- const { localNum, mesgNum, fieldDefns } = this.mesgDefn;
167
- const recordLen = 6 + 3 * fieldDefns.length;
168
- const dv = new DataView(new ArrayBuffer(recordLen));
169
- dv.setUint8(0, 0x40 | localNum);
170
- dv.setUint8(2, 1); // big endian
171
- dv.setUint16(3, mesgNum);
172
- dv.setUint8(5, fieldDefns.length);
173
- let offset = 6;
174
- for (const fieldDefn of fieldDefns) {
175
- dv.setUint8(offset++, fieldDefn.number);
176
- dv.setUint8(offset++, fieldDefn.size);
177
- dv.setUint8(offset++, fieldDefn.baseType);
178
- }
179
- return dv.buffer;
180
- }
181
- get dataRecord() {
182
- const { fieldDefns } = this.mesgDefn;
183
- const recordLen = 1 +
184
- fieldDefns.reduce((len, { size }) => len + size, 0);
185
- const dv = new DataView(new ArrayBuffer(recordLen));
186
- dv.setUint8(0, this.localNum);
187
- let offset = 1;
188
- for (const { number, type, value } of this.fields) {
189
- const typeInfo = types[type];
190
- if (!typeInfo) {
191
- throw new Error(`Unknown type: ${type}`);
192
- }
193
- const { size, setValue } = typeInfo;
194
- let mappedValue;
195
- if ("mapValue" in typeInfo) {
196
- mappedValue = typeInfo.mapValue(value);
197
- }
198
- else {
199
- mappedValue = value;
200
- }
201
- if (setValue) {
202
- setValue.call(dv, offset, mappedValue);
203
- }
204
- offset += size;
205
- }
206
- return dv.buffer;
207
- }
208
- }