trackswap 0.2.3 → 0.2.5
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 +20 -0
- package/dist/FIT/base.d.ts +30 -0
- package/dist/FIT/decoder.d.ts +48 -0
- package/dist/FIT/encoder.d.ts +96 -0
- package/dist/FIT/encoderCourse.d.ts +45 -0
- package/dist/FIT/encoderWorkout.d.ts +45 -0
- package/dist/FIT/health.d.ts +195 -7
- package/dist/FIT/index.d.ts +1 -1
- package/dist/FIT/mesg.d.ts +3 -0
- package/dist/FIT/middleware.d.ts +15 -0
- package/dist/FIT/plugins.d.ts +9 -0
- package/dist/FIT/processor.d.ts +24 -0
- package/dist/FIT/types/checksum_type.d.ts +6 -0
- package/dist/FIT/types/date_time_type.d.ts +3 -0
- package/dist/FIT/types/file.d.ts +63 -0
- package/dist/FIT/types/file_flags_type.d.ts +9 -0
- package/dist/FIT/types/local_date_time_type.d.ts +3 -0
- package/dist/FIT/types/mesg_count_type.d.ts +6 -0
- package/dist/FIT/types/mesg_num.d.ts +369 -0
- package/dist/FIT/types/message_index_type.d.ts +6591 -0
- package/dist/FIT/types/sport.d.ts +44 -44
- package/dist/FIT/types.d.ts +509 -0
- package/dist/GPX/base.d.ts +24 -0
- package/dist/GPX/converters.d.ts +44 -0
- package/dist/GPX/decoder.d.ts +66 -0
- package/dist/GPX/encoder.d.ts +109 -0
- package/dist/GPX/middleware.d.ts +9 -0
- package/dist/GPX/processor.d.ts +18 -0
- package/dist/GPX/types.d.ts +138 -0
- package/dist/TCX/base.d.ts +33 -0
- package/dist/TCX/converters.d.ts +33 -0
- package/dist/TCX/decoder.d.ts +54 -0
- package/dist/TCX/encoder.d.ts +116 -0
- package/dist/TCX/middleware.d.ts +12 -0
- package/dist/TCX/processor.d.ts +30 -0
- package/dist/TCX/types.d.ts +13 -0
- package/dist/TrackSwap.d.ts +4 -0
- package/dist/activity/base.d.ts +54 -0
- package/dist/activity/converters.d.ts +57 -0
- package/dist/activity/encoders.d.ts +34 -0
- package/dist/activity/metrics/MetricsCalculator.d.ts +30 -0
- package/dist/activity/processor.d.ts +31 -0
- package/dist/core/base.d.ts +46 -0
- package/dist/core/builtin-adapters.d.ts +1 -0
- package/dist/core/xml-utils.d.ts +24 -0
- package/dist/errors.d.ts +1 -1
- package/dist/fit-debug.d.ts +3 -0
- package/dist/index.cjs +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -29
- package/dist/types.d.ts +102 -1
- package/dist/util.d.ts +9 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -59,6 +59,22 @@ const converted = await trackSwap.transcode(input, {
|
|
|
59
59
|
const course = await trackSwap.encodeCourse(input);
|
|
60
60
|
~~~
|
|
61
61
|
|
|
62
|
+
FIT 力量训练会将 `set` message 按原始顺序投影到
|
|
63
|
+
`activity.strengthSets`,保留动作、休息、次数、重量、时长和设备动作标签:
|
|
64
|
+
|
|
65
|
+
~~~ts
|
|
66
|
+
const file = await trackSwap.decodeActivity(fitBuffer, { format: "fit" });
|
|
67
|
+
const [activity] = file.activities ?? [];
|
|
68
|
+
|
|
69
|
+
for (const phase of activity?.strengthSets ?? []) {
|
|
70
|
+
console.log(phase.setType, phase.repetitions, phase.weight, phase.exercises);
|
|
71
|
+
}
|
|
72
|
+
~~~
|
|
73
|
+
|
|
74
|
+
部分 FIT 活动包含 `record`、但没有实际 `lap` message。TrackSwap 会把这些
|
|
75
|
+
Record 放入一个合成 Lap,确保统一活动模型不丢样本;不会根据 Session 的
|
|
76
|
+
`numLaps` 编造 Lap 明细。
|
|
77
|
+
|
|
62
78
|
## FIT 健康数据
|
|
63
79
|
|
|
64
80
|
FIT 文件只解码一次,活动与健康数据作为同一 `TrackDocument` 的 facet 返回。健康结构不直接绑定 Garmin Connect JSON,使用稳定的中间表示:
|
|
@@ -69,6 +85,10 @@ FIT 文件只解码一次,活动与健康数据作为同一 `TrackDocument`
|
|
|
69
85
|
- `summaries`:日汇总、睡眠汇总及设备汇总;
|
|
70
86
|
- `diagnostics`:FIT SDK profile、无效值、未知消息和解析警告。
|
|
71
87
|
|
|
88
|
+
标准化边界由 TrackSwap 负责:FIT epoch、`timestamp_16`、Garmin sentinel、
|
|
89
|
+
字段 scale/unit、时区偏移和 HSA 数组展开均不会泄漏给业务调用方。所有时区偏移
|
|
90
|
+
统一使用秒;sentinel 只保留在 `status`/`attributes.rawValue`,不会伪装成有效测量值。
|
|
91
|
+
|
|
72
92
|
~~~ts
|
|
73
93
|
const document = await trackSwap.decode(fitBuffer, {
|
|
74
94
|
fit: {
|
package/dist/FIT/base.d.ts
CHANGED
|
@@ -1,23 +1,47 @@
|
|
|
1
1
|
import { IPlugin } from "../core/base.js";
|
|
2
2
|
import { FITContext, FITDecoderMesgs, FITFileType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* FIT message converter plugin interface
|
|
5
|
+
*/
|
|
3
6
|
export interface IFITMessageConverter extends IPlugin<FITContext> {
|
|
7
|
+
/** Supported message types */
|
|
4
8
|
supportedMessageTypes: string[];
|
|
9
|
+
/** Convert messages */
|
|
5
10
|
convertMessages(messages: any[], context: FITContext): any;
|
|
11
|
+
/** Whether it supports the message type */
|
|
6
12
|
supports(messageType: string): boolean;
|
|
13
|
+
/** Priority */
|
|
7
14
|
priority?: number;
|
|
8
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* FIT data structuring plugin interface
|
|
18
|
+
*/
|
|
9
19
|
export interface IFITStructurePlugin extends IPlugin<FITContext> {
|
|
20
|
+
/** Structure data */
|
|
10
21
|
structureData(messages: FITDecoderMesgs, context: FITContext): Partial<FITFileType>;
|
|
22
|
+
/** Priority */
|
|
11
23
|
priority?: number;
|
|
12
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* FIT middleware plugin interface
|
|
27
|
+
*/
|
|
13
28
|
export interface IFITMiddlewarePlugin extends IPlugin<FITContext> {
|
|
29
|
+
/** Priority */
|
|
14
30
|
priority?: number;
|
|
31
|
+
/** Execute during parse stage */
|
|
15
32
|
onParse?(buffer: Buffer, context: FITContext): Promise<Buffer> | Buffer;
|
|
33
|
+
/** Execute during message extraction stage */
|
|
16
34
|
onExtractMessages?(messages: FITDecoderMesgs, context: FITContext): Promise<FITDecoderMesgs> | FITDecoderMesgs;
|
|
35
|
+
/** Execute during data structuring stage */
|
|
17
36
|
onStructure?(result: Partial<FITFileType>, context: FITContext): Promise<Partial<FITFileType>> | Partial<FITFileType>;
|
|
37
|
+
/** Execute during completion stage */
|
|
18
38
|
onComplete?(result: FITFileType, context: FITContext): Promise<FITFileType> | FITFileType;
|
|
39
|
+
/** Error handling */
|
|
19
40
|
onError?(error: Error, context: FITContext): Promise<void> | void;
|
|
20
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* FIT message converter base class
|
|
44
|
+
*/
|
|
21
45
|
export declare abstract class BaseFITMessageConverter implements IFITMessageConverter {
|
|
22
46
|
abstract name: string;
|
|
23
47
|
abstract supportedMessageTypes: string[];
|
|
@@ -36,6 +60,9 @@ export declare abstract class BaseFITMessageConverter implements IFITMessageConv
|
|
|
36
60
|
records: any[];
|
|
37
61
|
}>;
|
|
38
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* FIT data structuring plugin base class
|
|
65
|
+
*/
|
|
39
66
|
export declare abstract class BaseFITStructurePlugin implements IFITStructurePlugin {
|
|
40
67
|
abstract name: string;
|
|
41
68
|
version: string;
|
|
@@ -45,6 +72,9 @@ export declare abstract class BaseFITStructurePlugin implements IFITStructurePlu
|
|
|
45
72
|
destroy(context: FITContext): Promise<void>;
|
|
46
73
|
validate(context: FITContext): Promise<boolean>;
|
|
47
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* FIT middleware base class
|
|
77
|
+
*/
|
|
48
78
|
export declare abstract class BaseFITMiddleware implements IFITMiddlewarePlugin {
|
|
49
79
|
abstract name: string;
|
|
50
80
|
version: string;
|
package/dist/FIT/decoder.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { IFITMessageConverter, IFITStructurePlugin, IFITMiddlewarePlugin } from "./base.js";
|
|
2
2
|
import { FITFileType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* FIT decoder - follows GPX style plugin architecture
|
|
5
|
+
*/
|
|
3
6
|
export declare class FITDecoder {
|
|
4
7
|
private converterPlugins;
|
|
5
8
|
private registeredPlugins;
|
|
@@ -9,20 +12,65 @@ export declare class FITDecoder {
|
|
|
9
12
|
private initialized;
|
|
10
13
|
private defaultPluginsRegistered;
|
|
11
14
|
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Initialize pipeline - register fixed core processors
|
|
17
|
+
*/
|
|
12
18
|
private initializePipeline;
|
|
19
|
+
/**
|
|
20
|
+
* Auto-register default plugins
|
|
21
|
+
*/
|
|
13
22
|
private registerDefaultPlugins;
|
|
23
|
+
/**
|
|
24
|
+
* Add converter for specific message type, sorted by priority
|
|
25
|
+
*/
|
|
14
26
|
private addConverterForMessageType;
|
|
27
|
+
/**
|
|
28
|
+
* Add structure plugin
|
|
29
|
+
*/
|
|
15
30
|
private addStructurePlugin;
|
|
31
|
+
/**
|
|
32
|
+
* Register message converter plugin
|
|
33
|
+
*/
|
|
16
34
|
registerMessageConverter(plugin: IFITMessageConverter): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Register structure plugin
|
|
37
|
+
*/
|
|
17
38
|
registerStructurePlugin(plugin: IFITStructurePlugin): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Register middleware plugin
|
|
41
|
+
*/
|
|
18
42
|
registerMiddleware(plugin: IFITMiddlewarePlugin): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Get message type converter (returns highest priority one)
|
|
45
|
+
*/
|
|
19
46
|
getConverter(messageType: string): IFITMessageConverter | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Get all converters for specified message type (sorted by priority)
|
|
49
|
+
*/
|
|
20
50
|
getConverters(messageType: string): IFITMessageConverter[];
|
|
51
|
+
/**
|
|
52
|
+
* Remove plugin
|
|
53
|
+
*/
|
|
21
54
|
unregisterPlugin(pluginName: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Initialize decoder
|
|
57
|
+
*/
|
|
22
58
|
initialize(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Destroy decoder
|
|
61
|
+
*/
|
|
23
62
|
destroy(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Create decode context
|
|
65
|
+
*/
|
|
24
66
|
private createContext;
|
|
67
|
+
/**
|
|
68
|
+
* Execute middleware hook
|
|
69
|
+
*/
|
|
25
70
|
private executeMiddlewareHook;
|
|
71
|
+
/**
|
|
72
|
+
* Main parse method - fixed pipeline + flexible middleware
|
|
73
|
+
*/
|
|
26
74
|
parseByBuffer(buffer: Buffer, options?: {
|
|
27
75
|
userData?: Record<string, any>;
|
|
28
76
|
}): Promise<FITFileType | undefined>;
|
package/dist/FIT/encoder.d.ts
CHANGED
|
@@ -1,41 +1,137 @@
|
|
|
1
1
|
import { FITFileType } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* FIT encoder options
|
|
4
|
+
*/
|
|
2
5
|
export interface FITEncoderOptions {
|
|
6
|
+
/** Whether to enable compact mode */
|
|
3
7
|
compact?: boolean;
|
|
8
|
+
/** Default sport type */
|
|
4
9
|
defaultActivity?: string;
|
|
10
|
+
/** Default sub sport type */
|
|
5
11
|
defaultSubActivity?: string;
|
|
6
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* FIT encoder - encodes FITFileType data to FIT binary format
|
|
15
|
+
*/
|
|
7
16
|
export declare class FITEncoder {
|
|
8
17
|
private localNum;
|
|
9
18
|
private mesgDefn;
|
|
10
19
|
private messages;
|
|
11
20
|
private options;
|
|
21
|
+
/**
|
|
22
|
+
* Constructor
|
|
23
|
+
* @param options Encoder options
|
|
24
|
+
*/
|
|
12
25
|
constructor(options?: FITEncoderOptions);
|
|
26
|
+
/**
|
|
27
|
+
* Encode FIT data to Buffer - Activity file type
|
|
28
|
+
*/
|
|
13
29
|
encode(fitData: FITFileType): Promise<Buffer>;
|
|
30
|
+
/**
|
|
31
|
+
* Encode FIT data to Buffer - Course file type
|
|
32
|
+
*/
|
|
14
33
|
encodeCourse(fitData: FITFileType): Promise<Buffer>;
|
|
34
|
+
/**
|
|
35
|
+
* Encode FIT data to Blob
|
|
36
|
+
*/
|
|
15
37
|
encodeToBlob(fitData: FITFileType, fileType?: "activity" | "course"): Promise<Blob>;
|
|
38
|
+
/**
|
|
39
|
+
* Reset encoder state
|
|
40
|
+
*/
|
|
16
41
|
private reset;
|
|
42
|
+
/**
|
|
43
|
+
* Build FIT file content
|
|
44
|
+
*/
|
|
17
45
|
private buildFITContent;
|
|
46
|
+
/**
|
|
47
|
+
* Build Activity file content
|
|
48
|
+
*/
|
|
18
49
|
private buildActivityContent;
|
|
50
|
+
/**
|
|
51
|
+
* Build Course file content
|
|
52
|
+
*/
|
|
19
53
|
private buildCourseContent;
|
|
54
|
+
/**
|
|
55
|
+
* Build file ID
|
|
56
|
+
*/
|
|
20
57
|
private buildFileId;
|
|
58
|
+
/**
|
|
59
|
+
* Build Session
|
|
60
|
+
* 构造 Session 信息
|
|
61
|
+
*/
|
|
21
62
|
private buildSession;
|
|
63
|
+
/**
|
|
64
|
+
* Build Laps
|
|
65
|
+
*/
|
|
22
66
|
private buildLaps;
|
|
67
|
+
/**
|
|
68
|
+
* Build single Lap
|
|
69
|
+
* 构造 Lap 信息
|
|
70
|
+
*/
|
|
23
71
|
private buildLap;
|
|
72
|
+
/**
|
|
73
|
+
* Build Records
|
|
74
|
+
*/
|
|
24
75
|
private buildRecords;
|
|
76
|
+
/**
|
|
77
|
+
* Build single Record
|
|
78
|
+
*/
|
|
25
79
|
private buildRecord;
|
|
80
|
+
/**
|
|
81
|
+
* Build Event
|
|
82
|
+
*/
|
|
26
83
|
private buildEvent;
|
|
84
|
+
/**
|
|
85
|
+
* Build Activity
|
|
86
|
+
*/
|
|
27
87
|
private buildActivity;
|
|
88
|
+
/**
|
|
89
|
+
* Build Course
|
|
90
|
+
*/
|
|
28
91
|
private buildCourse;
|
|
92
|
+
/**
|
|
93
|
+
* Build Events from Laps
|
|
94
|
+
*/
|
|
29
95
|
private buildEventsFromLaps;
|
|
96
|
+
/**
|
|
97
|
+
* Build Activity from Session
|
|
98
|
+
*/
|
|
30
99
|
private buildActivityFromSession;
|
|
100
|
+
/**
|
|
101
|
+
* Parse timestamp
|
|
102
|
+
*/
|
|
31
103
|
private parseTimestamp;
|
|
104
|
+
/**
|
|
105
|
+
* Validate required fields
|
|
106
|
+
*/
|
|
32
107
|
private validateRequiredFields;
|
|
108
|
+
/**
|
|
109
|
+
* Write message
|
|
110
|
+
*/
|
|
33
111
|
private writeMesg;
|
|
112
|
+
/**
|
|
113
|
+
* Get Blob object
|
|
114
|
+
*/
|
|
34
115
|
get blob(): Blob;
|
|
116
|
+
/**
|
|
117
|
+
* Get data length
|
|
118
|
+
*/
|
|
35
119
|
get dataLen(): number;
|
|
120
|
+
/**
|
|
121
|
+
* Get data CRC
|
|
122
|
+
*/
|
|
36
123
|
get dataCrc(): number;
|
|
124
|
+
/**
|
|
125
|
+
* Get file header
|
|
126
|
+
*/
|
|
37
127
|
get header(): ArrayBuffer;
|
|
128
|
+
/**
|
|
129
|
+
* Get file trailer
|
|
130
|
+
*/
|
|
38
131
|
get trailer(): ArrayBuffer;
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated Use encode() instead
|
|
134
|
+
*/
|
|
39
135
|
encoder(fitData: FITFileType): Promise<Buffer>;
|
|
40
136
|
}
|
|
41
137
|
declare const _default: {
|
|
@@ -1,37 +1,82 @@
|
|
|
1
1
|
import { CoursePoint as CoursePointType } from "./types/course_point.js";
|
|
2
2
|
import { MesgNum } from "./types/mesg_num.js";
|
|
3
3
|
import { Sport, SubSport } from "./types/message_index_type.js";
|
|
4
|
+
/**
|
|
5
|
+
* 路线轨迹点(用于生成 record 消息)。
|
|
6
|
+
* 默认输入经纬度为「度」,内部会转换为 FIT semicircles。
|
|
7
|
+
*/
|
|
4
8
|
export interface CourseRecordPoint {
|
|
9
|
+
/** 轨迹点时间(Date 或 FIT 时间戳/Unix 时间戳)。 */
|
|
5
10
|
timestamp: Date | number;
|
|
11
|
+
/** 纬度(默认按度处理,范围通常 -90~90)。 */
|
|
6
12
|
latitude: number;
|
|
13
|
+
/** 经度(默认按度处理,范围通常 -180~180)。 */
|
|
7
14
|
longitude: number;
|
|
15
|
+
/** 距离(单位 m,可选)。 */
|
|
8
16
|
distance?: number;
|
|
17
|
+
/** 海拔(单位 m,可选)。 */
|
|
9
18
|
altitude?: number;
|
|
19
|
+
/** 速度(单位 m/s,可选)。 */
|
|
10
20
|
speed?: number;
|
|
11
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* 路线拐点(用于生成 course_point 消息)。
|
|
24
|
+
*/
|
|
12
25
|
export interface CourseCuePoint {
|
|
26
|
+
/** 拐点时间。 */
|
|
13
27
|
timestamp: Date | number;
|
|
28
|
+
/** 拐点纬度(默认按度处理)。 */
|
|
14
29
|
latitude: number;
|
|
30
|
+
/** 拐点经度(默认按度处理)。 */
|
|
15
31
|
longitude: number;
|
|
32
|
+
/** 拐点类型(左转/右转/补给点等),默认 GENERIC。 */
|
|
16
33
|
type?: CoursePointType;
|
|
34
|
+
/** 拐点名称。 */
|
|
17
35
|
name?: string;
|
|
36
|
+
/** 拐点对应路线距离(单位 m,可选)。 */
|
|
18
37
|
distance?: number;
|
|
38
|
+
/** 是否收藏点。 */
|
|
19
39
|
favorite?: boolean;
|
|
20
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* 课程计划(业务模型)。
|
|
43
|
+
*/
|
|
21
44
|
export interface CoursePlan {
|
|
45
|
+
/** 路线名称。 */
|
|
22
46
|
name: string;
|
|
47
|
+
/** 运动类型,默认 CYCLING。 */
|
|
23
48
|
sport?: Sport;
|
|
49
|
+
/** 子运动类型,默认 GENERIC。 */
|
|
24
50
|
subSport?: SubSport;
|
|
51
|
+
/** 路线轨迹点(至少 1 个)。 */
|
|
25
52
|
records: CourseRecordPoint[];
|
|
53
|
+
/** 路线拐点列表(可选)。 */
|
|
26
54
|
coursePoints?: CourseCuePoint[];
|
|
55
|
+
/** file_id.manufacturer,可选,默认 development。 */
|
|
27
56
|
manufacturer?: string | number;
|
|
57
|
+
/** file_id.product,可选,默认 1。 */
|
|
28
58
|
product?: number;
|
|
59
|
+
/** file_id.serialNumber,可选,默认 1。 */
|
|
29
60
|
serialNumber?: number;
|
|
61
|
+
/** file_id.timeCreated,可选,默认首个 record.timestamp。 */
|
|
30
62
|
timeCreated?: Date | number;
|
|
31
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* 中间消息结构,统一喂给 Garmin Encoder.onMesg()。
|
|
66
|
+
*/
|
|
32
67
|
export interface FitMesg {
|
|
68
|
+
/** FIT 消息号。 */
|
|
33
69
|
mesgNum: MesgNum;
|
|
70
|
+
/** 消息体。 */
|
|
34
71
|
data: Record<string, unknown>;
|
|
35
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* 课程映射:
|
|
75
|
+
* FILE_ID(course) + COURSE + LAP + EVENT(start/stop_all) + RECORD[] + COURSE_POINT[]。
|
|
76
|
+
*/
|
|
36
77
|
export declare function buildCourseMesgs(plan: CoursePlan): FitMesg[];
|
|
78
|
+
/**
|
|
79
|
+
* 编码入口:
|
|
80
|
+
* 先映射消息,再写入 Garmin Encoder,最后 close() 输出 FIT 字节。
|
|
81
|
+
*/
|
|
37
82
|
export declare function encodeCourse(plan: CoursePlan): Uint8Array;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { MesgNum } from "./types/mesg_num.js";
|
|
2
2
|
import { Intensity, Sport, SubSport } from "./types/message_index_type.js";
|
|
3
|
+
/**
|
|
4
|
+
* 业务层的「步骤时长」抽象。
|
|
5
|
+
* 注意:这里只表达业务含义,具体如何编码到 FIT 在 mapDuration() 里处理。
|
|
6
|
+
*/
|
|
3
7
|
export type WorkoutDuration = {
|
|
4
8
|
kind: "time";
|
|
5
9
|
seconds: number;
|
|
@@ -12,30 +16,71 @@ export type WorkoutDuration = {
|
|
|
12
16
|
} | {
|
|
13
17
|
kind: "open";
|
|
14
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* 业务层的「步骤目标」抽象。
|
|
21
|
+
* V1 仅支持 open(不设目标区间)。
|
|
22
|
+
*/
|
|
15
23
|
export type WorkoutTarget = {
|
|
16
24
|
kind: "open";
|
|
17
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* 单个训练步骤(业务模型)。
|
|
28
|
+
*/
|
|
18
29
|
export interface WorkoutStep {
|
|
30
|
+
/** 步骤名称(如 Warmup / Interval)。 */
|
|
19
31
|
name?: string;
|
|
32
|
+
/** 步骤强度,默认 ACTIVE。 */
|
|
20
33
|
intensity?: Intensity;
|
|
34
|
+
/** 步骤时长/结束条件(必填)。 */
|
|
21
35
|
duration: WorkoutDuration;
|
|
36
|
+
/** 步骤目标,V1 默认 open。 */
|
|
22
37
|
target?: WorkoutTarget;
|
|
23
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 一个完整的训练计划(业务模型)。
|
|
41
|
+
* - 前 5 个字段是核心业务字段
|
|
42
|
+
* - 后面的 manufacturer/product... 是 FIT file_id 附加字段,可选
|
|
43
|
+
*/
|
|
24
44
|
export interface WorkoutPlan {
|
|
45
|
+
/** 训练名称,映射到 FIT 的 wktName。 */
|
|
25
46
|
name: string;
|
|
47
|
+
/** 训练描述,映射到 FIT 的 wktDescription。 */
|
|
26
48
|
description?: string;
|
|
49
|
+
/** 运动类型(跑步/骑行/游泳等)。 */
|
|
27
50
|
sport: Sport;
|
|
51
|
+
/** 子运动类型,默认 GENERIC。 */
|
|
28
52
|
subSport?: SubSport;
|
|
53
|
+
/** 训练步骤列表,至少 1 条。 */
|
|
29
54
|
steps: WorkoutStep[];
|
|
55
|
+
/** file_id.manufacturer,可选,不传则用 development。 */
|
|
30
56
|
manufacturer?: string | number;
|
|
57
|
+
/** file_id.product,可选,默认 1。 */
|
|
31
58
|
product?: number;
|
|
59
|
+
/** file_id.serialNumber,可选,默认 1。 */
|
|
32
60
|
serialNumber?: number;
|
|
61
|
+
/** workout.messageIndex,可选,默认 0。 */
|
|
33
62
|
messageIndex?: number;
|
|
63
|
+
/** file_id.timeCreated,可选,默认 new Date()。 */
|
|
34
64
|
timeCreated?: Date | number;
|
|
35
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 中间层消息结构:后续统一喂给 Garmin Encoder.onMesg()。
|
|
68
|
+
*/
|
|
36
69
|
export interface FitMesg {
|
|
70
|
+
/** FIT 消息号,例如 FILE_ID/WORKOUT/WORKOUT_STEP。 */
|
|
37
71
|
mesgNum: MesgNum;
|
|
72
|
+
/** Garmin Encoder 需要的消息体数据。 */
|
|
38
73
|
data: Record<string, unknown>;
|
|
39
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* 核心映射函数:
|
|
77
|
+
* WorkoutPlan -> FILE_ID + WORKOUT + WORKOUT_STEP[]。
|
|
78
|
+
*/
|
|
40
79
|
export declare function buildWorkoutMesgs(plan: WorkoutPlan): FitMesg[];
|
|
80
|
+
/**
|
|
81
|
+
* 编码入口:
|
|
82
|
+
* 1) 先构造中间消息数组
|
|
83
|
+
* 2) 逐条写入 Garmin Encoder
|
|
84
|
+
* 3) close() 得到 FIT 二进制(Uint8Array)
|
|
85
|
+
*/
|
|
41
86
|
export declare function encodeWorkout(plan: WorkoutPlan): Uint8Array;
|