@hangtime/grip-connect 0.3.8 → 0.3.9

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 CHANGED
@@ -28,7 +28,7 @@ Learn more: [Docs](https://stevie-ray.github.io/hangtime-grip-connect/) -
28
28
  ## Try it out
29
29
 
30
30
  [Chart](https://grip-connect.vercel.app/) - [Flappy Bird](https://grip-connect-flappy-bird.vercel.app/) -
31
- [Kilter Board](https://grip-connect-kilter-board.vercel.app/)
31
+ [Kilter Board](https://grip-connect-kilter-board.vercel.app/?route=p1083r15p1117r15p1164r12p1185r12p1233r13p1282r13p1303r13p1372r13p1392r14p1505r15)
32
32
 
33
33
  ## Install
34
34
 
@@ -80,8 +80,8 @@ motherboardButton.addEventListener("click", () => {
80
80
  // Manually call stop method if stream is continues
81
81
  // await stop(Motherboard)
82
82
 
83
- // Download data to CSV: format => timestamp, frame, battery, samples, masses
84
- // download()
83
+ // Download data as CSV, JSON, or XML (default: CSV) format => timestamp, frame, battery, samples, masses
84
+ // download('json')
85
85
 
86
86
  // Disconnect from device after we are done
87
87
  disconnect(Motherboard)
@@ -117,7 +117,7 @@ available services with us.
117
117
  - ✅ Check if device is being used
118
118
  - ✅ Peak / Average load
119
119
  - ✅️ Tare / unladen weight
120
- - ✅️ Download data to CVS
120
+ - ✅️ Download data (CSV, JSON, XML)
121
121
  - ➡️ Endurance
122
122
  - ➡️ Rate of Force Development: RFD
123
123
  - ➡️ Critical Force
@@ -146,7 +146,7 @@ A special thank you to:
146
146
  - [@1-max-1](https://github.com/1-max-1) for the docs on his Kilter Board
147
147
  [simulator](https://github.com/1-max-1/fake_kilter_board) that I coverted to
148
148
  [hangtime-arduino-kilterboard](https://github.com/Stevie-Ray/hangtime-arduino-kilterboard).
149
- - [sebws](https://github.com/sebw) for a [code sample](https://github.com/sebws/Crane) of the Weiheng WH-C06 App.
149
+ - [@sebws](https://github.com/sebw) for a [code sample](https://github.com/sebws/Crane) of the Weiheng WH-C06 App.
150
150
 
151
151
  ## Disclaimer
152
152
 
@@ -21,3 +21,15 @@ export declare enum KilterBoardPacket {
21
21
  /** If this packet is the only packet in the message, the byte gets set to 84 (T). Note that this takes priority over the other conditions. */
22
22
  V3_ONLY = 84
23
23
  }
24
+ /**
25
+ * Extracted from placement_roles database table.
26
+ */
27
+ export declare const KilterBoardPlacementRoles: {
28
+ id: number;
29
+ product_id: number;
30
+ position: number;
31
+ name: string;
32
+ full_name: string;
33
+ led_color: string;
34
+ screen_color: string;
35
+ }[];
@@ -22,3 +22,44 @@ export var KilterBoardPacket;
22
22
  /** If this packet is the only packet in the message, the byte gets set to 84 (T). Note that this takes priority over the other conditions. */
23
23
  KilterBoardPacket[KilterBoardPacket["V3_ONLY"] = 84] = "V3_ONLY";
24
24
  })(KilterBoardPacket || (KilterBoardPacket = {}));
25
+ /**
26
+ * Extracted from placement_roles database table.
27
+ */
28
+ export const KilterBoardPlacementRoles = [
29
+ {
30
+ id: 12,
31
+ product_id: 1,
32
+ position: 1,
33
+ name: "start",
34
+ full_name: "Start",
35
+ led_color: "00FF00",
36
+ screen_color: "00DD00",
37
+ },
38
+ {
39
+ id: 13,
40
+ product_id: 1,
41
+ position: 2,
42
+ name: "middle",
43
+ full_name: "Middle",
44
+ led_color: "00FFFF",
45
+ screen_color: "00FFFF",
46
+ },
47
+ {
48
+ id: 14,
49
+ product_id: 1,
50
+ position: 3,
51
+ name: "finish",
52
+ full_name: "Finish",
53
+ led_color: "FF00FF",
54
+ screen_color: "FF00FF",
55
+ },
56
+ {
57
+ id: 15,
58
+ product_id: 1,
59
+ position: 4,
60
+ name: "foot",
61
+ full_name: "Foot Only",
62
+ led_color: "FFA500",
63
+ screen_color: "FFA500",
64
+ },
65
+ ];
@@ -5,6 +5,12 @@ import type { DownloadPacket } from "./types/download";
5
5
  export declare const DownloadPackets: DownloadPacket[];
6
6
  export declare const emptyDownloadPackets: () => void;
7
7
  /**
8
- * Exports the data as a CSV file.
8
+ * Exports the data in the specified format (CSV, JSON, XML) with a filename format:
9
+ * 'data-export-YYYY-MM-DD-HH-MM-SS.{format}'.
10
+ *
11
+ * @param {('csv' | 'json' | 'xml')} [format='csv'] - The format in which to download the data.
12
+ * Defaults to 'csv'. Accepted values are 'csv', 'json', and 'xml'.
13
+ *
14
+ * @returns {void} Initiates a download of the data in the specified format.
9
15
  */
10
- export declare const download: () => void;
16
+ export declare const download: (format?: "csv" | "json" | "xml") => void;
package/dist/download.js CHANGED
@@ -26,20 +26,76 @@ const packetsToCSV = (data) => {
26
26
  .join("\r\n");
27
27
  };
28
28
  /**
29
- * Exports the data as a CSV file.
29
+ * Converts an array of DownloadPacket objects to a JSON string.
30
+ * @param data - Array of DownloadPacket objects.
31
+ * @returns JSON string representation of the data.
32
+ */
33
+ const packetsToJSON = (data) => {
34
+ return JSON.stringify(data, null, 2); // Pretty print JSON with 2-space indentation
35
+ };
36
+ /**
37
+ * Converts an array of DownloadPacket objects to an XML string.
38
+ * @param data - Array of DownloadPacket objects.
39
+ * @returns XML string representation of the data.
40
+ */
41
+ const packetsToXML = (data) => {
42
+ const xmlPackets = data
43
+ .map((packet) => {
44
+ const samples = packet.samples.map((sample) => `<sample>${sample}</sample>`).join("");
45
+ const masses = packet.masses.map((mass) => `<mass>${mass}</mass>`).join("");
46
+ return `
47
+ <packet>
48
+ <received>${packet.received}</received>
49
+ <sampleNum>${packet.sampleNum}</sampleNum>
50
+ <battRaw>${packet.battRaw}</battRaw>
51
+ <samples>${samples}</samples>
52
+ <masses>${masses}</masses>
53
+ </packet>
54
+ `;
55
+ })
56
+ .join("");
57
+ return `<DownloadPackets>${xmlPackets}</DownloadPackets>`;
58
+ };
59
+ /**
60
+ * Exports the data in the specified format (CSV, JSON, XML) with a filename format:
61
+ * 'data-export-YYYY-MM-DD-HH-MM-SS.{format}'.
62
+ *
63
+ * @param {('csv' | 'json' | 'xml')} [format='csv'] - The format in which to download the data.
64
+ * Defaults to 'csv'. Accepted values are 'csv', 'json', and 'xml'.
65
+ *
66
+ * @returns {void} Initiates a download of the data in the specified format.
30
67
  */
31
- export const download = () => {
32
- // Generate CSV string from DownloadPackets array
33
- const csvContent = packetsToCSV(DownloadPackets);
34
- // Create a Blob object containing the CSV data
35
- const blob = new Blob([csvContent], { type: "text/csv" });
68
+ export const download = (format = "csv") => {
69
+ let content = "";
70
+ let mimeType = "";
71
+ let fileName = "";
72
+ if (format === "csv") {
73
+ content = packetsToCSV(DownloadPackets);
74
+ mimeType = "text/csv";
75
+ }
76
+ else if (format === "json") {
77
+ content = packetsToJSON(DownloadPackets);
78
+ mimeType = "application/json";
79
+ }
80
+ else if (format === "xml") {
81
+ content = packetsToXML(DownloadPackets);
82
+ mimeType = "application/xml";
83
+ }
84
+ const now = new Date();
85
+ // YYYY-MM-DD
86
+ const date = now.toISOString().split("T")[0];
87
+ // HH-MM-SS
88
+ const time = now.toTimeString().split(" ")[0].replace(/:/g, "-");
89
+ fileName = `data-export-${date}-${time}.${format}`;
90
+ // Create a Blob object containing the data
91
+ const blob = new Blob([content], { type: mimeType });
36
92
  // Create a URL for the Blob
37
93
  const url = window.URL.createObjectURL(blob);
38
94
  // Create a link element
39
95
  const link = document.createElement("a");
40
96
  // Set link attributes
41
97
  link.href = url;
42
- link.setAttribute("download", "data.csv");
98
+ link.setAttribute("download", fileName);
43
99
  // Append link to document body
44
100
  document.body.appendChild(link);
45
101
  // Programmatically click the link to trigger the download
package/dist/led.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Device } from "./types/devices";
2
2
  declare class ClimbPlacement {
3
3
  position: number;
4
- role_id: string;
5
- constructor(position: number, role_id: string);
4
+ role_id: number;
5
+ constructor(position: number, role_id: number);
6
6
  }
7
7
  /**
8
8
  * Prepares byte arrays for transmission based on a list of climb placements.
package/dist/led.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { write } from "./write";
2
2
  import { isConnected } from "./is-connected";
3
3
  import { KilterBoard, Motherboard } from "./devices";
4
- import { KilterBoardPacket } from "./commands/kilterboard";
4
+ import { KilterBoardPacket, KilterBoardPlacementRoles } from "./commands/kilterboard";
5
5
  /**
6
6
  * Maximum length of the message body for byte wrapping.
7
7
  */
@@ -102,8 +102,11 @@ export function prepBytesV3(climbPlacementList) {
102
102
  resultArray.push(tempArray);
103
103
  tempArray = [KilterBoardPacket.V3_MIDDLE];
104
104
  }
105
- const ledColor = climbPlacement.role_id;
106
- const encodedPlacement = encodePlacement(climbPlacement.position, ledColor);
105
+ const role = KilterBoardPlacementRoles.find((placement) => placement.id === climbPlacement.role_id);
106
+ if (!role) {
107
+ throw new Error(`Role with id ${climbPlacement.role_id} not found in placement_roles`);
108
+ }
109
+ const encodedPlacement = encodePlacement(climbPlacement.position, role.led_color);
107
110
  tempArray.push(...encodedPlacement);
108
111
  }
109
112
  resultArray.push(tempArray);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hangtime/grip-connect",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "A client that can establish connections with various Force-Sensing Hangboards/Plates used by climbers for strength measurement. Examples of such hangboards include the Griptonite Motherboard, Climbro, SmartBoard, Entralpi or Tindeq Progressor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,7 +23,9 @@
23
23
  "motherboard",
24
24
  "tindeq",
25
25
  "progressor",
26
- "entralpi"
26
+ "entralpi",
27
+ "wh-c06",
28
+ "kilterboard"
27
29
  ],
28
30
  "bugs": {
29
31
  "url": "https://github.com/Stevie-Ray/hangtime-grip-connect/issues"
@@ -21,3 +21,44 @@ export enum KilterBoardPacket {
21
21
  /** If this packet is the only packet in the message, the byte gets set to 84 (T). Note that this takes priority over the other conditions. */
22
22
  V3_ONLY,
23
23
  }
24
+ /**
25
+ * Extracted from placement_roles database table.
26
+ */
27
+ export const KilterBoardPlacementRoles = [
28
+ {
29
+ id: 12,
30
+ product_id: 1,
31
+ position: 1,
32
+ name: "start",
33
+ full_name: "Start",
34
+ led_color: "00FF00",
35
+ screen_color: "00DD00",
36
+ },
37
+ {
38
+ id: 13,
39
+ product_id: 1,
40
+ position: 2,
41
+ name: "middle",
42
+ full_name: "Middle",
43
+ led_color: "00FFFF",
44
+ screen_color: "00FFFF",
45
+ },
46
+ {
47
+ id: 14,
48
+ product_id: 1,
49
+ position: 3,
50
+ name: "finish",
51
+ full_name: "Finish",
52
+ led_color: "FF00FF",
53
+ screen_color: "FF00FF",
54
+ },
55
+ {
56
+ id: 15,
57
+ product_id: 1,
58
+ position: 4,
59
+ name: "foot",
60
+ full_name: "Foot Only",
61
+ led_color: "FFA500",
62
+ screen_color: "FFA500",
63
+ },
64
+ ]
package/src/download.ts CHANGED
@@ -32,14 +32,74 @@ const packetsToCSV = (data: DownloadPacket[]): string => {
32
32
  }
33
33
 
34
34
  /**
35
- * Exports the data as a CSV file.
35
+ * Converts an array of DownloadPacket objects to a JSON string.
36
+ * @param data - Array of DownloadPacket objects.
37
+ * @returns JSON string representation of the data.
38
+ */
39
+ const packetsToJSON = (data: DownloadPacket[]): string => {
40
+ return JSON.stringify(data, null, 2) // Pretty print JSON with 2-space indentation
41
+ }
42
+
43
+ /**
44
+ * Converts an array of DownloadPacket objects to an XML string.
45
+ * @param data - Array of DownloadPacket objects.
46
+ * @returns XML string representation of the data.
47
+ */
48
+ const packetsToXML = (data: DownloadPacket[]): string => {
49
+ const xmlPackets = data
50
+ .map((packet) => {
51
+ const samples = packet.samples.map((sample) => `<sample>${sample}</sample>`).join("")
52
+ const masses = packet.masses.map((mass) => `<mass>${mass}</mass>`).join("")
53
+ return `
54
+ <packet>
55
+ <received>${packet.received}</received>
56
+ <sampleNum>${packet.sampleNum}</sampleNum>
57
+ <battRaw>${packet.battRaw}</battRaw>
58
+ <samples>${samples}</samples>
59
+ <masses>${masses}</masses>
60
+ </packet>
61
+ `
62
+ })
63
+ .join("")
64
+
65
+ return `<DownloadPackets>${xmlPackets}</DownloadPackets>`
66
+ }
67
+
68
+ /**
69
+ * Exports the data in the specified format (CSV, JSON, XML) with a filename format:
70
+ * 'data-export-YYYY-MM-DD-HH-MM-SS.{format}'.
71
+ *
72
+ * @param {('csv' | 'json' | 'xml')} [format='csv'] - The format in which to download the data.
73
+ * Defaults to 'csv'. Accepted values are 'csv', 'json', and 'xml'.
74
+ *
75
+ * @returns {void} Initiates a download of the data in the specified format.
36
76
  */
37
- export const download = (): void => {
38
- // Generate CSV string from DownloadPackets array
39
- const csvContent: string = packetsToCSV(DownloadPackets)
77
+ export const download = (format: "csv" | "json" | "xml" = "csv"): void => {
78
+ let content = ""
79
+ let mimeType = ""
80
+ let fileName = ""
81
+
82
+ if (format === "csv") {
83
+ content = packetsToCSV(DownloadPackets)
84
+ mimeType = "text/csv"
85
+ } else if (format === "json") {
86
+ content = packetsToJSON(DownloadPackets)
87
+ mimeType = "application/json"
88
+ } else if (format === "xml") {
89
+ content = packetsToXML(DownloadPackets)
90
+ mimeType = "application/xml"
91
+ }
92
+
93
+ const now = new Date()
94
+ // YYYY-MM-DD
95
+ const date = now.toISOString().split("T")[0]
96
+ // HH-MM-SS
97
+ const time = now.toTimeString().split(" ")[0].replace(/:/g, "-")
98
+
99
+ fileName = `data-export-${date}-${time}.${format}`
40
100
 
41
- // Create a Blob object containing the CSV data
42
- const blob = new Blob([csvContent], { type: "text/csv" })
101
+ // Create a Blob object containing the data
102
+ const blob = new Blob([content], { type: mimeType })
43
103
 
44
104
  // Create a URL for the Blob
45
105
  const url = window.URL.createObjectURL(blob)
@@ -49,7 +109,7 @@ export const download = (): void => {
49
109
 
50
110
  // Set link attributes
51
111
  link.href = url
52
- link.setAttribute("download", "data.csv")
112
+ link.setAttribute("download", fileName)
53
113
 
54
114
  // Append link to document body
55
115
  document.body.appendChild(link)
package/src/led.ts CHANGED
@@ -2,7 +2,8 @@ import type { Device } from "./types/devices"
2
2
  import { write } from "./write"
3
3
  import { isConnected } from "./is-connected"
4
4
  import { KilterBoard, Motherboard } from "./devices"
5
- import { KilterBoardPacket } from "./commands/kilterboard"
5
+ import { KilterBoardPacket, KilterBoardPlacementRoles } from "./commands/kilterboard"
6
+
6
7
  /**
7
8
  * Maximum length of the message body for byte wrapping.
8
9
  */
@@ -46,9 +47,9 @@ function wrapBytes(data: number[]) {
46
47
  }
47
48
  class ClimbPlacement {
48
49
  position: number
49
- role_id: string
50
+ role_id: number
50
51
 
51
- constructor(position: number, role_id: string) {
52
+ constructor(position: number, role_id: number) {
52
53
  this.position = position
53
54
  this.role_id = role_id
54
55
  }
@@ -109,10 +110,11 @@ export function prepBytesV3(climbPlacementList: ClimbPlacement[]) {
109
110
  resultArray.push(tempArray)
110
111
  tempArray = [KilterBoardPacket.V3_MIDDLE]
111
112
  }
112
-
113
- const ledColor = climbPlacement.role_id
114
-
115
- const encodedPlacement = encodePlacement(climbPlacement.position, ledColor)
113
+ const role = KilterBoardPlacementRoles.find((placement) => placement.id === climbPlacement.role_id)
114
+ if (!role) {
115
+ throw new Error(`Role with id ${climbPlacement.role_id} not found in placement_roles`)
116
+ }
117
+ const encodedPlacement = encodePlacement(climbPlacement.position, role.led_color)
116
118
  tempArray.push(...encodedPlacement)
117
119
  }
118
120