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.
- package/README.md +322 -281
- package/dist/FIT/base.d.ts +0 -30
- package/dist/FIT/decoder.d.ts +0 -48
- package/dist/FIT/encoder.d.ts +0 -97
- package/dist/FIT/mesg.d.ts +0 -3
- package/dist/FIT/middleware.d.ts +0 -15
- package/dist/FIT/plugins.d.ts +0 -9
- package/dist/FIT/processor.d.ts +0 -24
- package/dist/FIT/types/mesg_num.d.ts +102 -102
- package/dist/FIT/types/sport.d.ts +44 -44
- package/dist/FIT/types.d.ts +0 -35
- package/dist/GPX/base.d.ts +0 -39
- package/dist/GPX/converters.d.ts +0 -30
- package/dist/GPX/decoder.d.ts +0 -66
- package/dist/GPX/encoder.d.ts +0 -109
- package/dist/GPX/middleware.d.ts +0 -9
- package/dist/GPX/processor.d.ts +0 -18
- package/dist/GPX/types.d.ts +0 -108
- package/dist/TCX/base.d.ts +0 -33
- package/dist/TCX/converters.d.ts +0 -24
- package/dist/TCX/decoder.d.ts +0 -54
- package/dist/TCX/encoder.d.ts +0 -109
- package/dist/TCX/index.d.ts +4 -1
- package/dist/TCX/middleware.d.ts +0 -9
- package/dist/TCX/processor.d.ts +0 -18
- package/dist/TCX/types.d.ts +0 -13
- package/dist/TrackSwap.d.ts +37 -33
- package/dist/core/base.d.ts +0 -46
- package/dist/core/xml-utils.d.ts +0 -24
- package/dist/index.js +24 -5
- package/dist/sport/base.d.ts +63 -0
- package/dist/sport/converters.d.ts +33 -0
- package/dist/sport/encoders.d.ts +34 -0
- package/dist/sport/index.d.ts +17 -0
- package/dist/sport/processor.d.ts +19 -0
- package/dist/types.d.ts +91 -13
- package/package.json +18 -3
- package/dist/FIT/base.js +0 -101
- package/dist/FIT/crc.js +0 -16
- package/dist/FIT/decoder.js +0 -323
- package/dist/FIT/encoder.js +0 -445
- package/dist/FIT/index.js +0 -13
- package/dist/FIT/mesg.js +0 -208
- package/dist/FIT/middleware.js +0 -206
- package/dist/FIT/plugins.js +0 -104
- package/dist/FIT/processor.js +0 -170
- package/dist/FIT/types/course_point.js +0 -58
- package/dist/FIT/types/event.js +0 -50
- package/dist/FIT/types/event_type.js +0 -19
- package/dist/FIT/types/file.js +0 -24
- package/dist/FIT/types/index.js +0 -297
- package/dist/FIT/types/mesg_num.js +0 -106
- package/dist/FIT/types/sport.js +0 -48
- package/dist/FIT/types.js +0 -1
- package/dist/FIT/util.js +0 -7
- package/dist/GPX/base.js +0 -56
- package/dist/GPX/converters.js +0 -352
- package/dist/GPX/decoder.js +0 -409
- package/dist/GPX/encoder.js +0 -513
- package/dist/GPX/extensions/index.js +0 -14
- package/dist/GPX/index.js +0 -11
- package/dist/GPX/middleware.js +0 -93
- package/dist/GPX/processor.js +0 -188
- package/dist/GPX/types.js +0 -1
- package/dist/GPX/util.js +0 -18
- package/dist/TCX/base.js +0 -62
- package/dist/TCX/converters.js +0 -333
- package/dist/TCX/decoder.js +0 -350
- package/dist/TCX/encoder.js +0 -513
- package/dist/TCX/index.js +0 -9
- package/dist/TCX/middleware.js +0 -93
- package/dist/TCX/processor.js +0 -197
- package/dist/TCX/types.js +0 -1
- package/dist/TrackSwap.js +0 -84
- package/dist/core/base.js +0 -138
- package/dist/core/decoder.d.ts +0 -66
- package/dist/core/decoder.js +0 -90
- package/dist/core/index.js +0 -4
- package/dist/core/processor.d.ts +0 -69
- package/dist/core/processor.js +0 -265
- package/dist/core/xml-utils.js +0 -140
- package/dist/types.js +0 -1
package/dist/FIT/middleware.js
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { BaseFITMiddleware } from "./base.js";
|
|
2
|
-
// ============ 示例中间件 ============
|
|
3
|
-
/**
|
|
4
|
-
* 性能监控中间件
|
|
5
|
-
*/
|
|
6
|
-
export class PerformanceMiddleware extends BaseFITMiddleware {
|
|
7
|
-
constructor() {
|
|
8
|
-
super(...arguments);
|
|
9
|
-
this.name = "performance-middleware";
|
|
10
|
-
this.description = "监控FIT解析性能";
|
|
11
|
-
}
|
|
12
|
-
async onParse(buffer, context) {
|
|
13
|
-
console.log(`🔧 开始解析FIT文件,大小: ${buffer.length} bytes`);
|
|
14
|
-
return buffer;
|
|
15
|
-
}
|
|
16
|
-
async onExtractMessages(messages, context) {
|
|
17
|
-
const messageCount = this.countMessages(messages);
|
|
18
|
-
console.log(`📋 消息提取完成,包含 ${messageCount} 条消息`);
|
|
19
|
-
// 显示消息类型统计
|
|
20
|
-
const messageTypes = Object.keys(messages).filter((key) => Array.isArray(messages[key]) && messages[key].length > 0);
|
|
21
|
-
console.log(` 消息类型: ${messageTypes.join(", ")}`);
|
|
22
|
-
return messages;
|
|
23
|
-
}
|
|
24
|
-
async onStructure(result, context) {
|
|
25
|
-
const structureInfo = this.analyzeStructure(result);
|
|
26
|
-
console.log(`🏗️ 数据结构化完成: ${structureInfo}`);
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
async onComplete(result, context) {
|
|
30
|
-
const performance = context.metadata.get("performance");
|
|
31
|
-
console.log("⚡ FIT解析性能统计:", performance);
|
|
32
|
-
// 显示最终结果统计
|
|
33
|
-
console.log(`✅ 解析完成: ${this.getSummary(result)}`);
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
async onError(error, context) {
|
|
37
|
-
console.error(`❌ FIT解析出错: ${error.message}`);
|
|
38
|
-
console.error(` 错误详情:`, error.stack);
|
|
39
|
-
}
|
|
40
|
-
countMessages(messages) {
|
|
41
|
-
let count = 0;
|
|
42
|
-
Object.values(messages).forEach((messageArray) => {
|
|
43
|
-
if (Array.isArray(messageArray)) {
|
|
44
|
-
count += messageArray.length;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
return count;
|
|
48
|
-
}
|
|
49
|
-
analyzeStructure(result) {
|
|
50
|
-
const parts = [];
|
|
51
|
-
if (result.sessionMesgs?.length) {
|
|
52
|
-
parts.push(`${result.sessionMesgs.length} sessions`);
|
|
53
|
-
}
|
|
54
|
-
if (result.fileIdMesgs?.length) {
|
|
55
|
-
parts.push(`文件ID`);
|
|
56
|
-
}
|
|
57
|
-
if (result.eventMesgs?.length) {
|
|
58
|
-
parts.push(`${result.eventMesgs.length} events`);
|
|
59
|
-
}
|
|
60
|
-
return parts.join(", ") || "无结构化数据";
|
|
61
|
-
}
|
|
62
|
-
getSummary(result) {
|
|
63
|
-
const parts = [];
|
|
64
|
-
if (result.sessionMesgs?.length) {
|
|
65
|
-
parts.push(`${result.sessionMesgs.length}个会话`);
|
|
66
|
-
}
|
|
67
|
-
if (result.fileIdMesgs?.length) {
|
|
68
|
-
const fileId = result.fileIdMesgs[0];
|
|
69
|
-
parts.push(`类型:${fileId.type}, 厂商:${fileId.manufacturer}`);
|
|
70
|
-
}
|
|
71
|
-
return parts.join(", ") || "空结果";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* 数据验证中间件
|
|
76
|
-
*/
|
|
77
|
-
export class ValidationMiddleware extends BaseFITMiddleware {
|
|
78
|
-
constructor() {
|
|
79
|
-
super(...arguments);
|
|
80
|
-
this.name = "validation-middleware";
|
|
81
|
-
this.description = "验证FIT数据的完整性";
|
|
82
|
-
}
|
|
83
|
-
async onComplete(result, context) {
|
|
84
|
-
const errors = this.validateFIT(result);
|
|
85
|
-
if (errors.length > 0) {
|
|
86
|
-
console.warn("🚨 FIT验证发现问题:", errors);
|
|
87
|
-
context.metadata.set("validation-errors", errors);
|
|
88
|
-
// 将验证错误添加到上下文
|
|
89
|
-
errors.forEach((error) => context.warnings.push(error));
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
console.log("✅ FIT数据验证通过");
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
validateFIT(fit) {
|
|
97
|
-
const errors = [];
|
|
98
|
-
// 验证文件ID
|
|
99
|
-
if (!fit.fileIdMesgs || fit.fileIdMesgs.length === 0) {
|
|
100
|
-
errors.push("缺少文件ID信息");
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
const fileId = fit.fileIdMesgs[0];
|
|
104
|
-
if (!fileId.type) {
|
|
105
|
-
errors.push("文件类型未指定");
|
|
106
|
-
}
|
|
107
|
-
if (!fileId.manufacturer) {
|
|
108
|
-
errors.push("制造商信息缺失");
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// 验证会话数据
|
|
112
|
-
if (fit.sessionMesgs && fit.sessionMesgs.length > 0) {
|
|
113
|
-
fit.sessionMesgs.forEach((session, index) => {
|
|
114
|
-
if (!session.startTime) {
|
|
115
|
-
errors.push(`会话 ${index + 1} 缺少开始时间`);
|
|
116
|
-
}
|
|
117
|
-
if (!session.timestamp) {
|
|
118
|
-
errors.push(`会话 ${index + 1} 缺少结束时间`);
|
|
119
|
-
}
|
|
120
|
-
if (session.totalTimerTime === undefined) {
|
|
121
|
-
errors.push(`会话 ${index + 1} 缺少计时时间`);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return errors;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* 数据清理中间件
|
|
130
|
-
*/
|
|
131
|
-
export class DataCleanupMiddleware extends BaseFITMiddleware {
|
|
132
|
-
constructor() {
|
|
133
|
-
super(...arguments);
|
|
134
|
-
this.name = "data-cleanup-middleware";
|
|
135
|
-
this.description = "清理和优化FIT数据";
|
|
136
|
-
}
|
|
137
|
-
async onStructure(result, context) {
|
|
138
|
-
// 清理空数组和无效数据
|
|
139
|
-
return this.cleanupData(result);
|
|
140
|
-
}
|
|
141
|
-
cleanupData(result) {
|
|
142
|
-
const cleaned = { ...result };
|
|
143
|
-
// 移除空数组
|
|
144
|
-
Object.keys(cleaned).forEach((key) => {
|
|
145
|
-
const value = cleaned[key];
|
|
146
|
-
if (Array.isArray(value) && value.length === 0) {
|
|
147
|
-
delete cleaned[key];
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
// 清理会话数据中的无效值
|
|
151
|
-
if (cleaned.sessionMesgs) {
|
|
152
|
-
cleaned.sessionMesgs = cleaned.sessionMesgs.filter((session) => session.startTime && session.timestamp);
|
|
153
|
-
}
|
|
154
|
-
console.log("🧹 数据清理完成");
|
|
155
|
-
return cleaned;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* 文件信息提取中间件
|
|
160
|
-
*/
|
|
161
|
-
export class FileInfoMiddleware extends BaseFITMiddleware {
|
|
162
|
-
constructor() {
|
|
163
|
-
super(...arguments);
|
|
164
|
-
this.name = "file-info-middleware";
|
|
165
|
-
this.description = "提取和显示文件基本信息";
|
|
166
|
-
}
|
|
167
|
-
async onExtractMessages(messages, context) {
|
|
168
|
-
this.extractFileInfo(messages, context);
|
|
169
|
-
return messages;
|
|
170
|
-
}
|
|
171
|
-
extractFileInfo(messages, context) {
|
|
172
|
-
if (messages.fileIdMesgs && messages.fileIdMesgs.length > 0) {
|
|
173
|
-
const fileId = messages.fileIdMesgs[0];
|
|
174
|
-
const fileInfo = {
|
|
175
|
-
type: fileId.type,
|
|
176
|
-
manufacturer: fileId.manufacturer,
|
|
177
|
-
product: fileId.product,
|
|
178
|
-
serialNumber: fileId.serialNumber,
|
|
179
|
-
timeCreated: fileId.timeCreated,
|
|
180
|
-
};
|
|
181
|
-
context.metadata.set("file-info", fileInfo);
|
|
182
|
-
console.log("📄 文件信息:", {
|
|
183
|
-
类型: fileInfo.type,
|
|
184
|
-
制造商: fileInfo.manufacturer,
|
|
185
|
-
产品: fileInfo.product,
|
|
186
|
-
序列号: fileInfo.serialNumber,
|
|
187
|
-
创建时间: fileInfo.timeCreated,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// ============ 默认中间件注册函数 ============
|
|
193
|
-
/**
|
|
194
|
-
* 注册所有默认中间件
|
|
195
|
-
*/
|
|
196
|
-
export async function registerDefaultMiddlewares(decoder) {
|
|
197
|
-
const middlewares = [
|
|
198
|
-
new PerformanceMiddleware(),
|
|
199
|
-
new ValidationMiddleware(),
|
|
200
|
-
new DataCleanupMiddleware(),
|
|
201
|
-
new FileInfoMiddleware(),
|
|
202
|
-
];
|
|
203
|
-
for (const middleware of middlewares) {
|
|
204
|
-
await decoder.registerMiddleware(middleware);
|
|
205
|
-
}
|
|
206
|
-
}
|
package/dist/FIT/plugins.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import dayjs from "dayjs";
|
|
2
|
-
import { BaseFITStructurePlugin } from "./base.js";
|
|
3
|
-
/**
|
|
4
|
-
* Session 数据结构化插件
|
|
5
|
-
*/
|
|
6
|
-
export class SessionStructurePlugin extends BaseFITStructurePlugin {
|
|
7
|
-
constructor() {
|
|
8
|
-
super(...arguments);
|
|
9
|
-
this.name = "SessionStructurePlugin";
|
|
10
|
-
this.priority = 10;
|
|
11
|
-
}
|
|
12
|
-
structureData(messages, context) {
|
|
13
|
-
const { sessionMesgs, lapMesgs, recordMesgs } = messages;
|
|
14
|
-
if (!sessionMesgs?.length) {
|
|
15
|
-
console.warn("未发现结构化 session,数据结构不完整");
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
// 将每个 Session 的 Lap 和 Record 关联起来
|
|
19
|
-
const sessionStructureMesgs = sessionMesgs.map((session) => {
|
|
20
|
-
const sessionStartTime = dayjs(session.startTime).valueOf();
|
|
21
|
-
const sessionEndTime = dayjs(session.timestamp).valueOf();
|
|
22
|
-
// 找出属于当前 Session 的 Laps
|
|
23
|
-
const sessionLaps = lapMesgs?.filter((lap) => {
|
|
24
|
-
const lapStartTime = dayjs(lap.startTime).valueOf();
|
|
25
|
-
const lapEndTime = dayjs(lap.timestamp).valueOf();
|
|
26
|
-
return (lapStartTime >= sessionStartTime && lapEndTime <= sessionEndTime);
|
|
27
|
-
}) || [];
|
|
28
|
-
console.log("当前 session 下共有 Lap", sessionLaps.length);
|
|
29
|
-
// 为每个 Lap 找出对应的 Records
|
|
30
|
-
const lapsWithRecords = sessionLaps.map((lap) => {
|
|
31
|
-
const lapStartTime = dayjs(lap.startTime).valueOf();
|
|
32
|
-
const lapEndTime = dayjs(lap.timestamp).valueOf();
|
|
33
|
-
const lapRecords = recordMesgs?.filter((record) => {
|
|
34
|
-
const recordTime = dayjs(record.timestamp).valueOf();
|
|
35
|
-
return recordTime >= lapStartTime && recordTime <= lapEndTime;
|
|
36
|
-
}) || [];
|
|
37
|
-
console.log("找到当前 Lap 下的 records", lapRecords.length, recordMesgs?.length, dayjs(lapStartTime).format("YYYY-MM-DD HH:mm:ss"), dayjs(lapEndTime).format("YYYY-MM-DD HH:mm:ss"));
|
|
38
|
-
return { ...lap, recordMesgs: lapRecords };
|
|
39
|
-
});
|
|
40
|
-
// 返回重新组织后的 Session
|
|
41
|
-
return { ...session, lapMesgs: lapsWithRecords };
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
44
|
-
sessionMesgs: sessionStructureMesgs,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Course 数据结构化插件
|
|
50
|
-
*/
|
|
51
|
-
export class CourseStructurePlugin extends BaseFITStructurePlugin {
|
|
52
|
-
constructor() {
|
|
53
|
-
super(...arguments);
|
|
54
|
-
this.name = "CourseStructurePlugin";
|
|
55
|
-
this.priority = 10;
|
|
56
|
-
}
|
|
57
|
-
structureData(messages, context) {
|
|
58
|
-
const { courseMesgs, lapMesgs, recordMesgs } = messages;
|
|
59
|
-
if (!courseMesgs?.length) {
|
|
60
|
-
return {};
|
|
61
|
-
}
|
|
62
|
-
// 时间容错,默认2秒
|
|
63
|
-
const TIME_TOLERANCE = 2000;
|
|
64
|
-
const courseStructureMesgs = courseMesgs.map((course) => {
|
|
65
|
-
const lapsWithRecords = lapMesgs?.map((lap) => {
|
|
66
|
-
const lapStartTime = dayjs(lap.startTime).valueOf();
|
|
67
|
-
const lapEndTime = dayjs(lap.timestamp).valueOf();
|
|
68
|
-
// 添加时间容错,因为时间戳可能不太准确
|
|
69
|
-
const lapRecords = recordMesgs?.filter((record) => {
|
|
70
|
-
const recordTime = dayjs(record.timestamp).valueOf();
|
|
71
|
-
return (recordTime >= lapStartTime - TIME_TOLERANCE &&
|
|
72
|
-
recordTime <= lapEndTime + TIME_TOLERANCE);
|
|
73
|
-
}) || [];
|
|
74
|
-
return { ...lap, recordMesgs: lapRecords };
|
|
75
|
-
}) || [];
|
|
76
|
-
return { ...course, lapMesgs: lapsWithRecords };
|
|
77
|
-
});
|
|
78
|
-
return {
|
|
79
|
-
courseMesgs: courseStructureMesgs,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* 文件头信息提取插件
|
|
85
|
-
*/
|
|
86
|
-
export class FileHeaderPlugin extends BaseFITStructurePlugin {
|
|
87
|
-
constructor() {
|
|
88
|
-
super(...arguments);
|
|
89
|
-
this.name = "FileHeaderPlugin";
|
|
90
|
-
this.priority = 1; // 高优先级,先执行
|
|
91
|
-
}
|
|
92
|
-
structureData(messages, context) {
|
|
93
|
-
const { fileIdMesgs } = messages;
|
|
94
|
-
if (fileIdMesgs?.length && fileIdMesgs[0]) {
|
|
95
|
-
const fileId = fileIdMesgs[0];
|
|
96
|
-
context.fileHeader = {
|
|
97
|
-
type: fileId.type,
|
|
98
|
-
manufacturer: fileId.manufacturer,
|
|
99
|
-
product: fileId.product,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
return {};
|
|
103
|
-
}
|
|
104
|
-
}
|
package/dist/FIT/processor.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// 移除旧的 FITProcessor 类,所有内容都在上面的流水线处理器中实现了
|
|
2
|
-
/**
|
|
3
|
-
* FIT 流水线阶段
|
|
4
|
-
*/
|
|
5
|
-
export var FITPipelineStage;
|
|
6
|
-
(function (FITPipelineStage) {
|
|
7
|
-
FITPipelineStage["PARSE"] = "parse";
|
|
8
|
-
FITPipelineStage["EXTRACT"] = "extract";
|
|
9
|
-
FITPipelineStage["STRUCTURE"] = "structure";
|
|
10
|
-
FITPipelineStage["COMPLETE"] = "complete";
|
|
11
|
-
})(FITPipelineStage || (FITPipelineStage = {}));
|
|
12
|
-
/**
|
|
13
|
-
* Parse处理器 - 解析二进制FIT数据为消息
|
|
14
|
-
*/
|
|
15
|
-
export class ParseProcessor {
|
|
16
|
-
constructor() {
|
|
17
|
-
this.stage = FITPipelineStage.PARSE;
|
|
18
|
-
}
|
|
19
|
-
async process(context) {
|
|
20
|
-
const startTime = Date.now();
|
|
21
|
-
if (!context.rawData) {
|
|
22
|
-
throw new Error("FIT 数据不能为空");
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
// 使用 FIT SDK 解析二进制数据
|
|
26
|
-
const rawMessages = await this.parseWithFitSDK(context.rawData);
|
|
27
|
-
context.rawMessages = rawMessages;
|
|
28
|
-
context.performance.parseTime = Date.now() - startTime;
|
|
29
|
-
console.log(`🔧 FIT 解析完成,包含消息类型: ${Object.keys(rawMessages).join(", ")}`);
|
|
30
|
-
return context;
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
throw new Error(`FIT 解析失败: ${error.message}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* 使用 FIT SDK 解析二进制数据
|
|
38
|
-
*/
|
|
39
|
-
async parseWithFitSDK(buffer) {
|
|
40
|
-
const { Decoder, Stream } = await import("@garmin/fitsdk");
|
|
41
|
-
const stream = Stream.fromBuffer(buffer);
|
|
42
|
-
if (!Decoder.isFIT(stream)) {
|
|
43
|
-
throw new Error("不是有效的 FIT 文件");
|
|
44
|
-
}
|
|
45
|
-
const decoder = new Decoder(stream);
|
|
46
|
-
const { messages, errors } = decoder.read();
|
|
47
|
-
if (errors && errors.length > 0) {
|
|
48
|
-
console.warn("FIT 解析警告:", errors);
|
|
49
|
-
}
|
|
50
|
-
return messages;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Extract处理器 - 提取和转换消息
|
|
55
|
-
*/
|
|
56
|
-
export class ExtractProcessor {
|
|
57
|
-
constructor(getConverters) {
|
|
58
|
-
this.getConverters = getConverters;
|
|
59
|
-
this.stage = FITPipelineStage.EXTRACT;
|
|
60
|
-
}
|
|
61
|
-
async process(context) {
|
|
62
|
-
const startTime = Date.now();
|
|
63
|
-
if (!context.rawMessages) {
|
|
64
|
-
throw new Error("原始消息不能为空");
|
|
65
|
-
}
|
|
66
|
-
const extractedMessages = {};
|
|
67
|
-
// 遍历所有消息类型
|
|
68
|
-
for (const [messageType, messages] of Object.entries(context.rawMessages)) {
|
|
69
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
70
|
-
extractedMessages[messageType] = messages;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const converters = this.getConverters(messageType);
|
|
74
|
-
if (converters.length === 0) {
|
|
75
|
-
// 没有转换器,直接使用原始消息
|
|
76
|
-
extractedMessages[messageType] = messages;
|
|
77
|
-
console.log(`📋 消息类型 "${messageType}" 没有转换器,使用原始数据`);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// 使用优先级最高的转换器
|
|
81
|
-
const converter = converters[0];
|
|
82
|
-
try {
|
|
83
|
-
const convertedMessages = converter.convertMessages(messages, context);
|
|
84
|
-
extractedMessages[messageType] = convertedMessages;
|
|
85
|
-
console.log(`✅ 消息类型 "${messageType}" 使用转换器 ${converter.name}`);
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
context.errors.push(error);
|
|
89
|
-
extractedMessages[messageType] = messages; // 回退到原始消息
|
|
90
|
-
console.error(`❌ 转换器 ${converter.name} 处理 ${messageType} 时出错:`, error);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
context.rawMessages = extractedMessages;
|
|
95
|
-
context.performance.extractTime = Date.now() - startTime;
|
|
96
|
-
return context;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Structure处理器 - 数据结构化
|
|
101
|
-
*/
|
|
102
|
-
export class StructureProcessor {
|
|
103
|
-
constructor(getStructurePlugins) {
|
|
104
|
-
this.getStructurePlugins = getStructurePlugins;
|
|
105
|
-
this.stage = FITPipelineStage.STRUCTURE;
|
|
106
|
-
}
|
|
107
|
-
async process(context) {
|
|
108
|
-
const startTime = Date.now();
|
|
109
|
-
if (!context.rawMessages) {
|
|
110
|
-
throw new Error("消息数据不能为空");
|
|
111
|
-
}
|
|
112
|
-
let structuredResult = {};
|
|
113
|
-
// 应用所有结构化插件
|
|
114
|
-
const plugins = this.getStructurePlugins();
|
|
115
|
-
for (const plugin of plugins) {
|
|
116
|
-
try {
|
|
117
|
-
const pluginResult = plugin.structureData(context.rawMessages, context);
|
|
118
|
-
structuredResult = { ...structuredResult, ...pluginResult };
|
|
119
|
-
console.log(`🔧 结构化插件 ${plugin.name} 处理完成`);
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
context.errors.push(error);
|
|
123
|
-
console.error(`❌ 结构化插件 ${plugin.name} 出错:`, error);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
context.result = structuredResult;
|
|
127
|
-
context.performance.structureTime = Date.now() - startTime;
|
|
128
|
-
return context;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Complete处理器 - 最终处理和清理
|
|
133
|
-
*/
|
|
134
|
-
export class CompleteProcessor {
|
|
135
|
-
constructor() {
|
|
136
|
-
this.stage = FITPipelineStage.COMPLETE;
|
|
137
|
-
}
|
|
138
|
-
async process(context) {
|
|
139
|
-
context.performance.endTime = Date.now();
|
|
140
|
-
// 记录性能指标
|
|
141
|
-
const totalTime = context.performance.endTime - context.performance.startTime;
|
|
142
|
-
context.metadata.set("performance", {
|
|
143
|
-
totalTime,
|
|
144
|
-
parseTime: context.performance.parseTime,
|
|
145
|
-
extractTime: context.performance.extractTime,
|
|
146
|
-
structureTime: context.performance.structureTime,
|
|
147
|
-
});
|
|
148
|
-
// 更新统计信息
|
|
149
|
-
if (context.stats) {
|
|
150
|
-
context.stats.endTime = Date.now();
|
|
151
|
-
if (context.rawMessages) {
|
|
152
|
-
context.stats.processedTokens = this.countTotalMessages(context.rawMessages);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
console.log(`✅ FIT 处理完成,总耗时: ${totalTime}ms`);
|
|
156
|
-
return context;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* 计算消息总数
|
|
160
|
-
*/
|
|
161
|
-
countTotalMessages(messages) {
|
|
162
|
-
let count = 0;
|
|
163
|
-
Object.values(messages).forEach((messageArray) => {
|
|
164
|
-
if (Array.isArray(messageArray)) {
|
|
165
|
-
count += messageArray.length;
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
return count;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
export var CoursePoint;
|
|
2
|
-
(function (CoursePoint) {
|
|
3
|
-
CoursePoint[CoursePoint["GENERIC"] = 0] = "GENERIC";
|
|
4
|
-
CoursePoint[CoursePoint["SUMMIT"] = 1] = "SUMMIT";
|
|
5
|
-
CoursePoint[CoursePoint["VALLEY"] = 2] = "VALLEY";
|
|
6
|
-
CoursePoint[CoursePoint["WATER"] = 3] = "WATER";
|
|
7
|
-
CoursePoint[CoursePoint["FOOD"] = 4] = "FOOD";
|
|
8
|
-
CoursePoint[CoursePoint["DANGER"] = 5] = "DANGER";
|
|
9
|
-
CoursePoint[CoursePoint["LEFT"] = 6] = "LEFT";
|
|
10
|
-
CoursePoint[CoursePoint["RIGHT"] = 7] = "RIGHT";
|
|
11
|
-
CoursePoint[CoursePoint["STRAIGHT"] = 8] = "STRAIGHT";
|
|
12
|
-
CoursePoint[CoursePoint["FIRST_AID"] = 9] = "FIRST_AID";
|
|
13
|
-
CoursePoint[CoursePoint["FOURTH_CATEGORY"] = 10] = "FOURTH_CATEGORY";
|
|
14
|
-
CoursePoint[CoursePoint["THIRD_CATEGORY"] = 11] = "THIRD_CATEGORY";
|
|
15
|
-
CoursePoint[CoursePoint["SECOND_CATEGORY"] = 12] = "SECOND_CATEGORY";
|
|
16
|
-
CoursePoint[CoursePoint["FIRST_CATEGORY"] = 13] = "FIRST_CATEGORY";
|
|
17
|
-
CoursePoint[CoursePoint["HORS_CATEGORY"] = 14] = "HORS_CATEGORY";
|
|
18
|
-
CoursePoint[CoursePoint["SPRINT"] = 15] = "SPRINT";
|
|
19
|
-
CoursePoint[CoursePoint["LEFT_FORK"] = 16] = "LEFT_FORK";
|
|
20
|
-
CoursePoint[CoursePoint["RIGHT_FORK"] = 17] = "RIGHT_FORK";
|
|
21
|
-
CoursePoint[CoursePoint["MIDDLE_FORK"] = 18] = "MIDDLE_FORK";
|
|
22
|
-
CoursePoint[CoursePoint["SLIGHT_LEFT"] = 19] = "SLIGHT_LEFT";
|
|
23
|
-
CoursePoint[CoursePoint["SHARP_LEFT"] = 20] = "SHARP_LEFT";
|
|
24
|
-
CoursePoint[CoursePoint["SLIGHT_RIGHT"] = 21] = "SLIGHT_RIGHT";
|
|
25
|
-
CoursePoint[CoursePoint["SHARP_RIGHT"] = 22] = "SHARP_RIGHT";
|
|
26
|
-
CoursePoint[CoursePoint["U_TURN"] = 23] = "U_TURN";
|
|
27
|
-
CoursePoint[CoursePoint["SEGMENT_START"] = 24] = "SEGMENT_START";
|
|
28
|
-
CoursePoint[CoursePoint["SEGMENT_END"] = 25] = "SEGMENT_END";
|
|
29
|
-
// 保留 26 作为未使用的值
|
|
30
|
-
CoursePoint[CoursePoint["CAMPSITE"] = 27] = "CAMPSITE";
|
|
31
|
-
CoursePoint[CoursePoint["AID_STATION"] = 28] = "AID_STATION";
|
|
32
|
-
CoursePoint[CoursePoint["REST_AREA"] = 29] = "REST_AREA";
|
|
33
|
-
CoursePoint[CoursePoint["GENERAL_DISTANCE"] = 30] = "GENERAL_DISTANCE";
|
|
34
|
-
CoursePoint[CoursePoint["SERVICE"] = 31] = "SERVICE";
|
|
35
|
-
CoursePoint[CoursePoint["ENERGY_GEL"] = 32] = "ENERGY_GEL";
|
|
36
|
-
CoursePoint[CoursePoint["SPORTS_DRINK"] = 33] = "SPORTS_DRINK";
|
|
37
|
-
CoursePoint[CoursePoint["MILE_MARKER"] = 34] = "MILE_MARKER";
|
|
38
|
-
CoursePoint[CoursePoint["CHECKPOINT"] = 35] = "CHECKPOINT";
|
|
39
|
-
CoursePoint[CoursePoint["SHELTER"] = 36] = "SHELTER";
|
|
40
|
-
CoursePoint[CoursePoint["MEETING_SPOT"] = 37] = "MEETING_SPOT";
|
|
41
|
-
CoursePoint[CoursePoint["OVERLOOK"] = 38] = "OVERLOOK";
|
|
42
|
-
CoursePoint[CoursePoint["TOILET"] = 39] = "TOILET";
|
|
43
|
-
CoursePoint[CoursePoint["SHOWER"] = 40] = "SHOWER";
|
|
44
|
-
CoursePoint[CoursePoint["GEAR"] = 41] = "GEAR";
|
|
45
|
-
CoursePoint[CoursePoint["SHARP_CURVE"] = 42] = "SHARP_CURVE";
|
|
46
|
-
CoursePoint[CoursePoint["STEEP_INCLINE"] = 43] = "STEEP_INCLINE";
|
|
47
|
-
CoursePoint[CoursePoint["TUNNEL"] = 44] = "TUNNEL";
|
|
48
|
-
CoursePoint[CoursePoint["BRIDGE"] = 45] = "BRIDGE";
|
|
49
|
-
CoursePoint[CoursePoint["OBSTACLE"] = 46] = "OBSTACLE";
|
|
50
|
-
CoursePoint[CoursePoint["CROSSING"] = 47] = "CROSSING";
|
|
51
|
-
CoursePoint[CoursePoint["STORE"] = 48] = "STORE";
|
|
52
|
-
CoursePoint[CoursePoint["TRANSITION"] = 49] = "TRANSITION";
|
|
53
|
-
CoursePoint[CoursePoint["NAVAID"] = 50] = "NAVAID";
|
|
54
|
-
CoursePoint[CoursePoint["TRANSPORT"] = 51] = "TRANSPORT";
|
|
55
|
-
CoursePoint[CoursePoint["ALERT"] = 52] = "ALERT";
|
|
56
|
-
CoursePoint[CoursePoint["INFO"] = 53] = "INFO";
|
|
57
|
-
CoursePoint[CoursePoint["INVALID"] = 255] = "INVALID";
|
|
58
|
-
})(CoursePoint || (CoursePoint = {}));
|
package/dist/FIT/types/event.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export var Event;
|
|
2
|
-
(function (Event) {
|
|
3
|
-
Event[Event["TIMER"] = 0] = "TIMER";
|
|
4
|
-
Event[Event["WORKOUT"] = 3] = "WORKOUT";
|
|
5
|
-
Event[Event["WORKOUT_STEP"] = 4] = "WORKOUT_STEP";
|
|
6
|
-
Event[Event["POWER_DOWN"] = 5] = "POWER_DOWN";
|
|
7
|
-
Event[Event["POWER_UP"] = 6] = "POWER_UP";
|
|
8
|
-
Event[Event["OFF_COURSE"] = 7] = "OFF_COURSE";
|
|
9
|
-
Event[Event["SESSION"] = 8] = "SESSION";
|
|
10
|
-
Event[Event["LAP"] = 9] = "LAP";
|
|
11
|
-
Event[Event["COURSE_POINT"] = 10] = "COURSE_POINT";
|
|
12
|
-
Event[Event["BATTERY"] = 11] = "BATTERY";
|
|
13
|
-
Event[Event["VIRTUAL_PARTNER_PACE"] = 12] = "VIRTUAL_PARTNER_PACE";
|
|
14
|
-
Event[Event["HR_HIGH_ALERT"] = 13] = "HR_HIGH_ALERT";
|
|
15
|
-
Event[Event["HR_LOW_ALERT"] = 14] = "HR_LOW_ALERT";
|
|
16
|
-
Event[Event["SPEED_HIGH_ALERT"] = 15] = "SPEED_HIGH_ALERT";
|
|
17
|
-
Event[Event["SPEED_LOW_ALERT"] = 16] = "SPEED_LOW_ALERT";
|
|
18
|
-
Event[Event["CAD_HIGH_ALERT"] = 17] = "CAD_HIGH_ALERT";
|
|
19
|
-
Event[Event["CAD_LOW_ALERT"] = 18] = "CAD_LOW_ALERT";
|
|
20
|
-
Event[Event["POWER_HIGH_ALERT"] = 19] = "POWER_HIGH_ALERT";
|
|
21
|
-
Event[Event["POWER_LOW_ALERT"] = 20] = "POWER_LOW_ALERT";
|
|
22
|
-
Event[Event["RECOVERY_HR"] = 21] = "RECOVERY_HR";
|
|
23
|
-
Event[Event["BATTERY_LOW"] = 22] = "BATTERY_LOW";
|
|
24
|
-
Event[Event["TIME_DURATION_ALERT"] = 23] = "TIME_DURATION_ALERT";
|
|
25
|
-
Event[Event["DISTANCE_DURATION_ALERT"] = 24] = "DISTANCE_DURATION_ALERT";
|
|
26
|
-
Event[Event["CALORIE_DURATION_ALERT"] = 25] = "CALORIE_DURATION_ALERT";
|
|
27
|
-
Event[Event["ACTIVITY"] = 26] = "ACTIVITY";
|
|
28
|
-
Event[Event["FITNESS_EQUIPMENT"] = 27] = "FITNESS_EQUIPMENT";
|
|
29
|
-
Event[Event["LENGTH"] = 28] = "LENGTH";
|
|
30
|
-
Event[Event["USER_MARKER"] = 32] = "USER_MARKER";
|
|
31
|
-
Event[Event["SPORT_POINT"] = 33] = "SPORT_POINT";
|
|
32
|
-
Event[Event["CALIBRATION"] = 36] = "CALIBRATION";
|
|
33
|
-
Event[Event["FRONT_GEAR_CHANGE"] = 42] = "FRONT_GEAR_CHANGE";
|
|
34
|
-
Event[Event["REAR_GEAR_CHANGE"] = 43] = "REAR_GEAR_CHANGE";
|
|
35
|
-
Event[Event["RIDER_POSITION_CHANGE"] = 44] = "RIDER_POSITION_CHANGE";
|
|
36
|
-
Event[Event["ELEV_HIGH_ALERT"] = 45] = "ELEV_HIGH_ALERT";
|
|
37
|
-
Event[Event["ELEV_LOW_ALERT"] = 46] = "ELEV_LOW_ALERT";
|
|
38
|
-
Event[Event["COMM_TIMEOUT"] = 47] = "COMM_TIMEOUT";
|
|
39
|
-
Event[Event["AUTO_ACTIVITY_DETECT"] = 54] = "AUTO_ACTIVITY_DETECT";
|
|
40
|
-
Event[Event["DIVE_ALERT"] = 56] = "DIVE_ALERT";
|
|
41
|
-
Event[Event["DIVE_GAS_SWITCHED"] = 57] = "DIVE_GAS_SWITCHED";
|
|
42
|
-
Event[Event["TANK_PRESSURE_RESERVE"] = 71] = "TANK_PRESSURE_RESERVE";
|
|
43
|
-
Event[Event["TANK_PRESSURE_CRITICAL"] = 72] = "TANK_PRESSURE_CRITICAL";
|
|
44
|
-
Event[Event["TANK_LOST"] = 73] = "TANK_LOST";
|
|
45
|
-
Event[Event["RADAR_THREAT_ALERT"] = 75] = "RADAR_THREAT_ALERT";
|
|
46
|
-
Event[Event["TANK_BATTERY_LOW"] = 76] = "TANK_BATTERY_LOW";
|
|
47
|
-
Event[Event["TANK_POD_CONNECTED"] = 81] = "TANK_POD_CONNECTED";
|
|
48
|
-
Event[Event["TANK_POD_DISCONNECTED"] = 82] = "TANK_POD_DISCONNECTED";
|
|
49
|
-
Event[Event["INVALID"] = 255] = "INVALID";
|
|
50
|
-
})(Event || (Event = {}));
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export var EventType;
|
|
2
|
-
(function (EventType) {
|
|
3
|
-
EventType[EventType["START"] = 0] = "START";
|
|
4
|
-
EventType[EventType["STOP"] = 1] = "STOP";
|
|
5
|
-
EventType[EventType["CONSECUTIVE_DEPRECIATED"] = 2] = "CONSECUTIVE_DEPRECIATED";
|
|
6
|
-
EventType[EventType["MARKER"] = 3] = "MARKER";
|
|
7
|
-
EventType[EventType["STOP_ALL"] = 4] = "STOP_ALL";
|
|
8
|
-
EventType[EventType["BEGIN_DEPRECIATED"] = 5] = "BEGIN_DEPRECIATED";
|
|
9
|
-
EventType[EventType["END_DEPRECIATED"] = 6] = "END_DEPRECIATED";
|
|
10
|
-
EventType[EventType["END_ALL_DEPRECIATED"] = 7] = "END_ALL_DEPRECIATED";
|
|
11
|
-
EventType[EventType["STOP_DISABLE"] = 8] = "STOP_DISABLE";
|
|
12
|
-
EventType[EventType["STOP_DISABLE_ALL"] = 9] = "STOP_DISABLE_ALL";
|
|
13
|
-
EventType[EventType["MARKER_START"] = 10] = "MARKER_START";
|
|
14
|
-
EventType[EventType["MARKER_STOP"] = 11] = "MARKER_STOP";
|
|
15
|
-
EventType[EventType["STOP_ALL_ENTER_RECOVERY"] = 12] = "STOP_ALL_ENTER_RECOVERY";
|
|
16
|
-
EventType[EventType["STOP_ENTER_RECOVERY"] = 13] = "STOP_ENTER_RECOVERY";
|
|
17
|
-
EventType[EventType["MARK_END"] = 14] = "MARK_END";
|
|
18
|
-
EventType[EventType["UNDEFINED"] = 255] = "UNDEFINED";
|
|
19
|
-
})(EventType || (EventType = {}));
|
package/dist/FIT/types/file.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export var FITFile;
|
|
2
|
-
(function (FITFile) {
|
|
3
|
-
FITFile[FITFile["DEVICE"] = 1] = "DEVICE";
|
|
4
|
-
FITFile[FITFile["SETTINGS"] = 2] = "SETTINGS";
|
|
5
|
-
FITFile[FITFile["SPORT"] = 3] = "SPORT";
|
|
6
|
-
FITFile[FITFile["ACTIVITY"] = 4] = "ACTIVITY";
|
|
7
|
-
FITFile[FITFile["WORKOUT"] = 5] = "WORKOUT";
|
|
8
|
-
FITFile[FITFile["COURSE"] = 6] = "COURSE";
|
|
9
|
-
FITFile[FITFile["SCHEDULES"] = 7] = "SCHEDULES";
|
|
10
|
-
FITFile[FITFile["WEIGHT"] = 9] = "WEIGHT";
|
|
11
|
-
FITFile[FITFile["TOTALS"] = 10] = "TOTALS";
|
|
12
|
-
FITFile[FITFile["GOALS"] = 11] = "GOALS";
|
|
13
|
-
FITFile[FITFile["BLOOD_PRESSURE"] = 14] = "BLOOD_PRESSURE";
|
|
14
|
-
FITFile[FITFile["MONITORING_A"] = 15] = "MONITORING_A";
|
|
15
|
-
FITFile[FITFile["ACTIVITY_SUMMARY"] = 20] = "ACTIVITY_SUMMARY";
|
|
16
|
-
FITFile[FITFile["MONITORING_DAILY"] = 28] = "MONITORING_DAILY";
|
|
17
|
-
FITFile[FITFile["MONITORING_B"] = 32] = "MONITORING_B";
|
|
18
|
-
FITFile[FITFile["SEGMENT"] = 34] = "SEGMENT";
|
|
19
|
-
FITFile[FITFile["SEGMENT_LIST"] = 35] = "SEGMENT_LIST";
|
|
20
|
-
FITFile[FITFile["EXD_CONFIGURATION"] = 40] = "EXD_CONFIGURATION";
|
|
21
|
-
FITFile[FITFile["MFG_RANGE_MIN"] = 247] = "MFG_RANGE_MIN";
|
|
22
|
-
FITFile[FITFile["MFG_RANGE_MAX"] = 254] = "MFG_RANGE_MAX";
|
|
23
|
-
FITFile[FITFile["INVALID"] = 255] = "INVALID";
|
|
24
|
-
})(FITFile || (FITFile = {}));
|