tirecheck-device-sdk 0.2.26 → 0.2.28
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/dist/index.cjs +270 -103
- package/dist/index.d.cts +9 -2
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.mjs +270 -103
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -106,6 +106,13 @@ function stringToArrayBuffer(string) {
|
|
|
106
106
|
}
|
|
107
107
|
return array.buffer;
|
|
108
108
|
}
|
|
109
|
+
function stringToDecimalArray(string) {
|
|
110
|
+
const array = new Uint8Array(string.length);
|
|
111
|
+
for (let i = 0, l = string.length; i < l; i++) {
|
|
112
|
+
array[i] = string.charCodeAt(i);
|
|
113
|
+
}
|
|
114
|
+
return Array.from(array);
|
|
115
|
+
}
|
|
109
116
|
function decimalToHex(decimal, padStart = 2) {
|
|
110
117
|
const hex = decimal.toString(16);
|
|
111
118
|
return hex.padStart(padStart, "0");
|
|
@@ -127,7 +134,7 @@ const tyreLabels = getDefaultTyreLabels();
|
|
|
127
134
|
const bridgeTools = {
|
|
128
135
|
getBridgeFromStore(deviceId) {
|
|
129
136
|
const deviceData = store.devices[deviceId];
|
|
130
|
-
if (!deviceData) throw new Error(
|
|
137
|
+
if (!deviceData) throw new Error(`No device data found: ${deviceId}`);
|
|
131
138
|
if (deviceData.type !== "bridge") throw new Error("Device is not bridge");
|
|
132
139
|
return deviceData;
|
|
133
140
|
},
|
|
@@ -189,12 +196,12 @@ const bridgeTools = {
|
|
|
189
196
|
}
|
|
190
197
|
const _reversedData = _data.reverse();
|
|
191
198
|
if (displayUnits === "decimal") {
|
|
192
|
-
return Number(`0x${_reversedData.map((dec) =>
|
|
199
|
+
return Number(`0x${_reversedData.map((dec) => decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
|
|
193
200
|
}
|
|
194
201
|
if (displayUnits === "reverseHex") {
|
|
195
|
-
return _reversedData.map((dec) =>
|
|
202
|
+
return _reversedData.map((dec) => decimalToHex(dec).split("").reverse().join("")).join("");
|
|
196
203
|
}
|
|
197
|
-
return _reversedData.map((dec) =>
|
|
204
|
+
return _reversedData.map((dec) => decimalToHex(dec)).join("");
|
|
198
205
|
},
|
|
199
206
|
encodeData(data, displayUnits) {
|
|
200
207
|
const _data = ___default.clone(data);
|
|
@@ -202,7 +209,7 @@ const bridgeTools = {
|
|
|
202
209
|
return this.asciiToDecimalArray(_data);
|
|
203
210
|
}
|
|
204
211
|
if (displayUnits === "decimal") {
|
|
205
|
-
return this.hexToDecimalArray(
|
|
212
|
+
return this.hexToDecimalArray(decimalToHex(_data)).reverse();
|
|
206
213
|
}
|
|
207
214
|
if (displayUnits === "reverseHex") {
|
|
208
215
|
const byteArray = _data.match(/.{1,2}/g) || [];
|
|
@@ -218,10 +225,6 @@ const bridgeTools = {
|
|
|
218
225
|
}
|
|
219
226
|
return array;
|
|
220
227
|
},
|
|
221
|
-
decimalToHex(decimal) {
|
|
222
|
-
const hex = decimal.toString(16);
|
|
223
|
-
return hex.padStart(2, "0");
|
|
224
|
-
},
|
|
225
228
|
asciiToDecimalArray(ascii) {
|
|
226
229
|
return ascii.split("").map((x) => x.charCodeAt(0));
|
|
227
230
|
},
|
|
@@ -234,7 +237,7 @@ const bridgeTools = {
|
|
|
234
237
|
return "";
|
|
235
238
|
}
|
|
236
239
|
return payload.reduce((acc, current) => {
|
|
237
|
-
const value =
|
|
240
|
+
const value = decimalToHex(current);
|
|
238
241
|
acc.push(value[0] === "0" ? value[1] : value);
|
|
239
242
|
return acc;
|
|
240
243
|
}, []).join(".");
|
|
@@ -356,7 +359,7 @@ const bridgeAdvertisingParser = {
|
|
|
356
359
|
if (adversitingType !== "connectable") return;
|
|
357
360
|
const advertisingData = getAdvertisingData({ advertising: device.advertising, deviceName: device.name });
|
|
358
361
|
if (!advertisingData) return;
|
|
359
|
-
const macArray = advertisingData?.macAddress?.map((n) =>
|
|
362
|
+
const macArray = advertisingData?.macAddress?.map((n) => decimalToHex(n).toUpperCase()).reverse() || [];
|
|
360
363
|
const bridgeId = macArray.join("");
|
|
361
364
|
const vin = String.fromCharCode(...advertisingData?.vinNum || []).split("\0").join("");
|
|
362
365
|
const macString = macArray.join(":");
|
|
@@ -472,7 +475,7 @@ const bridgeOtaAdvertisingParser = {
|
|
|
472
475
|
let processedByteCount = 0;
|
|
473
476
|
if (store.platform === "ios") {
|
|
474
477
|
if (!device.advertising.kCBAdvDataLeBluetoothDeviceAddress) return void 0;
|
|
475
|
-
const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) =>
|
|
478
|
+
const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
|
|
476
479
|
deviceId = macAddress.join(":");
|
|
477
480
|
bridgeId = macAddress.join("");
|
|
478
481
|
} else {
|
|
@@ -485,7 +488,7 @@ const bridgeOtaAdvertisingParser = {
|
|
|
485
488
|
}
|
|
486
489
|
processedByteCount += length + 1;
|
|
487
490
|
if (identificator === 27) {
|
|
488
|
-
const macAddress = packet.slice(-6).reverse().map((x) =>
|
|
491
|
+
const macAddress = packet.slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
|
|
489
492
|
deviceId = macAddress.join(":");
|
|
490
493
|
bridgeId = macAddress.join("");
|
|
491
494
|
processedByteCount = adv.length;
|
|
@@ -543,7 +546,8 @@ const deviceMeta = {
|
|
|
543
546
|
// },
|
|
544
547
|
// },
|
|
545
548
|
flexiGaugeTpms: {
|
|
546
|
-
|
|
549
|
+
// after implementation of normal fg replace with /^Flexi.*TPMS.*/,
|
|
550
|
+
nameRegex: /^Flexi.*/,
|
|
547
551
|
communication: {
|
|
548
552
|
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
549
553
|
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d6"
|
|
@@ -1474,7 +1478,7 @@ const bridgeSecurity = {
|
|
|
1474
1478
|
const { randomAdvNumber, configVersion } = device?.advertisingData || {};
|
|
1475
1479
|
if (!randomAdvNumber || !___default.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
|
|
1476
1480
|
const rand = ___default.clone(randomAdvNumber);
|
|
1477
|
-
const security_level =
|
|
1481
|
+
const security_level = decimalToHex(command[0]);
|
|
1478
1482
|
const keyIndex = Number(security_level[0]);
|
|
1479
1483
|
const keyShift = Number(security_level[1]);
|
|
1480
1484
|
let paddedCommand = [];
|
|
@@ -1489,8 +1493,8 @@ const bridgeSecurity = {
|
|
|
1489
1493
|
const key = store.securityKeys.bridge.signatureKeys[configVersion][keyIndex];
|
|
1490
1494
|
const startArray = key.slice(keyShift);
|
|
1491
1495
|
const endArray = key.slice(0, keyShift);
|
|
1492
|
-
const shiftedKey = startArray.concat(endArray).map((n) =>
|
|
1493
|
-
const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) =>
|
|
1496
|
+
const shiftedKey = startArray.concat(endArray).map((n) => decimalToHex(n)).join("");
|
|
1497
|
+
const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => decimalToHex(x)).join("");
|
|
1494
1498
|
const keyWA = CryptoJs__default.enc.Hex.parse(shiftedKey);
|
|
1495
1499
|
const dataInWA = CryptoJs__default.enc.Hex.parse(dataIn);
|
|
1496
1500
|
const encrypted = CryptoJs__default.AES.encrypt(dataInWA, keyWA, {
|
|
@@ -1508,8 +1512,8 @@ const bridgeSecurity = {
|
|
|
1508
1512
|
for (let index2 = 0; index2 < block.length; index2++) {
|
|
1509
1513
|
xorBlock.push(block[index2] ^ blockVector[index2]);
|
|
1510
1514
|
}
|
|
1511
|
-
const vector = blockVector.map((x) =>
|
|
1512
|
-
const dataWa = CryptoJs__default.enc.Hex.parse(xorBlock.map((x) =>
|
|
1515
|
+
const vector = blockVector.map((x) => decimalToHex(x)).join("");
|
|
1516
|
+
const dataWa = CryptoJs__default.enc.Hex.parse(xorBlock.map((x) => decimalToHex(x)).join(""));
|
|
1513
1517
|
const testo = CryptoJs__default.AES.encrypt(dataWa, keyWA, {
|
|
1514
1518
|
iv: CryptoJs__default.enc.Hex.parse(vector),
|
|
1515
1519
|
mode: CryptoJs__default.mode.ECB,
|
|
@@ -1526,8 +1530,8 @@ const bridgeSecurity = {
|
|
|
1526
1530
|
if (deviceData?.type !== "bridge") throw new Error("Device data missing 1 ");
|
|
1527
1531
|
const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
|
|
1528
1532
|
if (!randomAdvNumber || !macAddress || !___default.isNumber(configVersion)) throw new Error("Cannot compute pin");
|
|
1529
|
-
const mac = macAddress.map((n) =>
|
|
1530
|
-
const rand = randomAdvNumber.map((n) =>
|
|
1533
|
+
const mac = macAddress.map((n) => decimalToHex(n)).join("");
|
|
1534
|
+
const rand = randomAdvNumber.map((n) => decimalToHex(n)).join("");
|
|
1531
1535
|
const message = `${mac}0000${rand}`;
|
|
1532
1536
|
if (!store.securityKeys?.bridge?.pinKeys?.[configVersion]) throw new Error("No pin key found");
|
|
1533
1537
|
const key = store.securityKeys?.bridge?.pinKeys[configVersion];
|
|
@@ -1539,9 +1543,7 @@ const bridgeSecurity = {
|
|
|
1539
1543
|
const keyHex = CryptoJs__default.enc.Hex.parse(key);
|
|
1540
1544
|
const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
|
|
1541
1545
|
mode: CryptoJs__default.mode.ECB,
|
|
1542
|
-
// You can use other modes like CBC or CTR for additional security
|
|
1543
1546
|
padding: CryptoJs__default.pad.ZeroPadding
|
|
1544
|
-
// You can use other padding modes as well
|
|
1545
1547
|
});
|
|
1546
1548
|
const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1547
1549
|
const hexArray = this.encryptedStringToNum(encryptedByteString);
|
|
@@ -1568,7 +1570,7 @@ const bridgeSecurity = {
|
|
|
1568
1570
|
},
|
|
1569
1571
|
getCrcArray(msg) {
|
|
1570
1572
|
const crc = this.crc32mpeg2(msg);
|
|
1571
|
-
const crcHex =
|
|
1573
|
+
const crcHex = decimalToHex(crc).padStart(8, "0");
|
|
1572
1574
|
return bridgeTools.hexToDecimalArray(crcHex).reverse();
|
|
1573
1575
|
},
|
|
1574
1576
|
uint32ToLittleEndian(value) {
|
|
@@ -1586,7 +1588,7 @@ const devicePromiseQueue$1 = {};
|
|
|
1586
1588
|
const deviceCurrentResolve$1 = {};
|
|
1587
1589
|
const deviceCurrentReject$1 = {};
|
|
1588
1590
|
const deviceResponseIdentifier$1 = {};
|
|
1589
|
-
const promiseQueue = {
|
|
1591
|
+
const promiseQueue$1 = {
|
|
1590
1592
|
clearQueue(deviceId, message) {
|
|
1591
1593
|
if (deviceCurrentReject$1[deviceId]) {
|
|
1592
1594
|
deviceCurrentReject$1[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
@@ -1627,7 +1629,7 @@ const promiseQueue = {
|
|
|
1627
1629
|
);
|
|
1628
1630
|
}
|
|
1629
1631
|
});
|
|
1630
|
-
return withTimeout(promise,
|
|
1632
|
+
return withTimeout(promise, 8e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
|
|
1631
1633
|
});
|
|
1632
1634
|
return devicePromiseQueue$1[device.id];
|
|
1633
1635
|
},
|
|
@@ -2000,7 +2002,7 @@ const bridgeCommands = {
|
|
|
2000
2002
|
async writeCommand(device, commandId, subCommandId, payload) {
|
|
2001
2003
|
const commandHead = this.getCommandHeader(device);
|
|
2002
2004
|
const macAddress = [0, 0, 0, 0, 0, 0];
|
|
2003
|
-
const timestamp =
|
|
2005
|
+
const timestamp = decimalToHex(Math.floor(Date.now() / 1e3));
|
|
2004
2006
|
const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16)).reverse();
|
|
2005
2007
|
if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
|
|
2006
2008
|
const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
|
|
@@ -2038,7 +2040,7 @@ const bridgeCommands = {
|
|
|
2038
2040
|
}
|
|
2039
2041
|
this.sendKeepAliveCommand(device);
|
|
2040
2042
|
}, 8e3);
|
|
2041
|
-
return promiseQueue.enqueue(device, writeCommand);
|
|
2043
|
+
return promiseQueue$1.enqueue(device, writeCommand);
|
|
2042
2044
|
}
|
|
2043
2045
|
};
|
|
2044
2046
|
|
|
@@ -2229,7 +2231,7 @@ function getVehicleAxlesTypes(tcVehicle) {
|
|
|
2229
2231
|
if (axle?.isDrive) {
|
|
2230
2232
|
binaryRepresentation |= 16;
|
|
2231
2233
|
}
|
|
2232
|
-
return
|
|
2234
|
+
return decimalToHex(binaryRepresentation);
|
|
2233
2235
|
});
|
|
2234
2236
|
return axleTypesBinary;
|
|
2235
2237
|
}
|
|
@@ -2384,7 +2386,7 @@ async function setVehicle(deviceId, tcVehicle) {
|
|
|
2384
2386
|
}
|
|
2385
2387
|
async function getSensorReading(deviceId, positionId) {
|
|
2386
2388
|
const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
|
|
2387
|
-
const sensorId = value.slice(5, 9).reverse().map((s) =>
|
|
2389
|
+
const sensorId = value.slice(5, 9).reverse().map((s) => decimalToHex(s).toUpperCase()).join("");
|
|
2388
2390
|
const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
|
|
2389
2391
|
return {
|
|
2390
2392
|
date: Date.now() - value[14] * 2e3,
|
|
@@ -2426,7 +2428,7 @@ async function getAutolearnStatuses(deviceId, tcVehicle) {
|
|
|
2426
2428
|
let autolearnedSensorId;
|
|
2427
2429
|
if (["1", "4", "9"].includes(tyreStatus)) {
|
|
2428
2430
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
|
|
2429
|
-
const axleData = axleInfo.map((i) =>
|
|
2431
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2430
2432
|
const joinedAxleData = Array.from(
|
|
2431
2433
|
{ length: axleData.length / 4 },
|
|
2432
2434
|
(_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
|
|
@@ -2636,7 +2638,7 @@ async function assignTyres(deviceId, tcVehicle) {
|
|
|
2636
2638
|
for (let index = 0; index < tcVehicle.axles.length; index++) {
|
|
2637
2639
|
const axle = tcVehicle.axles[index];
|
|
2638
2640
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
|
|
2639
|
-
const axleData = axleInfo.map((i) =>
|
|
2641
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2640
2642
|
const joinedAxleData = Array.from(
|
|
2641
2643
|
{ length: axleData.length / 4 },
|
|
2642
2644
|
(_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
|
|
@@ -2712,7 +2714,7 @@ const bridge = {
|
|
|
2712
2714
|
};
|
|
2713
2715
|
async function connect(deviceId, accessLevel) {
|
|
2714
2716
|
if (canCommunicateWith(deviceId)) return;
|
|
2715
|
-
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2717
|
+
await promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2716
2718
|
const bridgeMeta = deviceMeta.bridge;
|
|
2717
2719
|
await bluetooth.connect(deviceId, disconnect);
|
|
2718
2720
|
if (store.deviceState[deviceId] === "paired") {
|
|
@@ -2727,7 +2729,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2727
2729
|
_deviceId,
|
|
2728
2730
|
bridgeMeta.communication.serviceId,
|
|
2729
2731
|
bridgeMeta.communication.characteristicId,
|
|
2730
|
-
(notification) => promiseQueue.processMessage(deviceId, notification),
|
|
2732
|
+
(notification) => promiseQueue$1.processMessage(deviceId, notification),
|
|
2731
2733
|
(error) => console.error("startNotification Error", error)
|
|
2732
2734
|
);
|
|
2733
2735
|
store.deviceAccessLevel[deviceId] = accessLevel ?? "driver";
|
|
@@ -2741,7 +2743,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2741
2743
|
}
|
|
2742
2744
|
async function disconnect(deviceId, reason) {
|
|
2743
2745
|
store.setState(deviceId, "disconnecting");
|
|
2744
|
-
promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2746
|
+
promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2745
2747
|
await bluetooth.disconnect(deviceId);
|
|
2746
2748
|
delete store.devices[deviceId];
|
|
2747
2749
|
deviceUnreachableCallback?.(deviceId);
|
|
@@ -2772,10 +2774,118 @@ const simulatorSvc = {
|
|
|
2772
2774
|
}
|
|
2773
2775
|
};
|
|
2774
2776
|
|
|
2777
|
+
const devicePromiseQueue = {};
|
|
2778
|
+
const deviceCurrentResolve = {};
|
|
2779
|
+
const deviceCurrentReject = {};
|
|
2780
|
+
const deviceResponseIdentifier = {};
|
|
2781
|
+
const deviceCurrentPartialMessage = {};
|
|
2782
|
+
const promiseQueue = {
|
|
2783
|
+
clearQueue(deviceId, message) {
|
|
2784
|
+
if (deviceCurrentReject[deviceId]) {
|
|
2785
|
+
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2786
|
+
}
|
|
2787
|
+
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2788
|
+
deviceResponseIdentifier[deviceId] = void 0;
|
|
2789
|
+
},
|
|
2790
|
+
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2791
|
+
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2792
|
+
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2793
|
+
const promise = new Promise((resolve, reject) => {
|
|
2794
|
+
deviceCurrentResolve[deviceId] = resolve;
|
|
2795
|
+
deviceCurrentReject[deviceId] = reject;
|
|
2796
|
+
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2797
|
+
deviceCurrentPartialMessage[deviceId] = [];
|
|
2798
|
+
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2799
|
+
const chunks = [];
|
|
2800
|
+
for (let i = 0; i < payload.length; i += mtu) {
|
|
2801
|
+
chunks.push(payload.slice(i, i + mtu));
|
|
2802
|
+
}
|
|
2803
|
+
for (const chunk of chunks) {
|
|
2804
|
+
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2805
|
+
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2809
|
+
});
|
|
2810
|
+
return devicePromiseQueue[deviceId];
|
|
2811
|
+
},
|
|
2812
|
+
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2813
|
+
const numberArray = Array.from(new Uint8Array(payload));
|
|
2814
|
+
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2815
|
+
deviceCurrentPartialMessage[deviceId] = ___default.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2816
|
+
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
2820
|
+
return deviceCurrentReject[deviceId](
|
|
2821
|
+
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
2825
|
+
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
2826
|
+
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
|
|
2831
|
+
const flexiGaugeTpmsSecurity = {
|
|
2832
|
+
getCommandsSignature(seed) {
|
|
2833
|
+
const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.allLFCommands;
|
|
2834
|
+
if (!key) throw new Error("No Command key found");
|
|
2835
|
+
return aesToCrc32(seed, key);
|
|
2836
|
+
},
|
|
2837
|
+
getWriteSignature(seed) {
|
|
2838
|
+
const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.writeConfiguration;
|
|
2839
|
+
if (!key) throw new Error("No Write key found");
|
|
2840
|
+
return aesToCrc32(seed, key);
|
|
2841
|
+
},
|
|
2842
|
+
getCrc(message) {
|
|
2843
|
+
const messageHex = message.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? [];
|
|
2844
|
+
const crc = crc16CCITT(messageHex);
|
|
2845
|
+
return decimalToHex(crc, 4).toUpperCase();
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
function aesToCrc32(message, key) {
|
|
2849
|
+
const keyString = key.map((n) => decimalToHex(n)).join("");
|
|
2850
|
+
const messageHex = CryptoJs__default.enc.Hex.parse(message);
|
|
2851
|
+
const keyHex = CryptoJs__default.enc.Hex.parse(keyString);
|
|
2852
|
+
const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
|
|
2853
|
+
mode: CryptoJs__default.mode.ECB,
|
|
2854
|
+
padding: CryptoJs__default.pad.ZeroPadding
|
|
2855
|
+
});
|
|
2856
|
+
const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 8).toUpperCase();
|
|
2857
|
+
return encryptedByteString;
|
|
2858
|
+
}
|
|
2859
|
+
function crc16CCITT(msg) {
|
|
2860
|
+
let crc = 65535;
|
|
2861
|
+
const polynomial = 4129;
|
|
2862
|
+
for (let i = 0; i < msg.length; i++) {
|
|
2863
|
+
const b = msg[i];
|
|
2864
|
+
crc ^= b << 8;
|
|
2865
|
+
for (let j = 0; j < 8; j++) {
|
|
2866
|
+
if (crc & 32768) {
|
|
2867
|
+
crc = crc << 1 ^ polynomial;
|
|
2868
|
+
} else {
|
|
2869
|
+
crc <<= 1;
|
|
2870
|
+
}
|
|
2871
|
+
crc &= 65535;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
return crc;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2775
2877
|
const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
|
|
2776
2878
|
const flexiGaugeTpmsCommands = {
|
|
2777
2879
|
startTpmsScan,
|
|
2778
|
-
getBattery
|
|
2880
|
+
getBattery,
|
|
2881
|
+
vdaRequestSensor,
|
|
2882
|
+
vdaRequestSeed,
|
|
2883
|
+
vdaSendWriteModePin,
|
|
2884
|
+
vdaReadConfiguration,
|
|
2885
|
+
vdaSendConfiguration,
|
|
2886
|
+
vdaRequestCommandsAccess,
|
|
2887
|
+
vdaRequestWriteAccess,
|
|
2888
|
+
processMessage
|
|
2779
2889
|
};
|
|
2780
2890
|
async function startTpmsScan(deviceId) {
|
|
2781
2891
|
if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
|
|
@@ -2796,23 +2906,92 @@ async function getBattery(deviceId) {
|
|
|
2796
2906
|
const battery = Array.from(new Uint8Array(batteryValue))[0];
|
|
2797
2907
|
return battery;
|
|
2798
2908
|
}
|
|
2909
|
+
async function vdaRequestSensor(deviceId) {
|
|
2910
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=1A";
|
|
2911
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2912
|
+
}
|
|
2913
|
+
async function vdaRequestSeed(deviceId, sensorId) {
|
|
2914
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2915
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=04 DATA=${sensorIdReversed}CE9A5713`;
|
|
2916
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2917
|
+
}
|
|
2918
|
+
async function vdaRequestCommandsAccess(deviceId, sensorId, signature) {
|
|
2919
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2920
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=${sensorIdReversed}${signature}`;
|
|
2921
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2922
|
+
}
|
|
2923
|
+
async function vdaRequestWriteAccess(deviceId, sensorId, signature) {
|
|
2924
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2925
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=02 DATA=${sensorIdReversed}${signature}`;
|
|
2926
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2927
|
+
}
|
|
2928
|
+
async function vdaSendWriteModePin(deviceId) {
|
|
2929
|
+
const scanMsg = stringToArrayBuffer("*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=FCCC71F6\r\n");
|
|
2930
|
+
await bluetooth.write(
|
|
2931
|
+
deviceId,
|
|
2932
|
+
flexiGaugeTpmsMeta.communication.serviceId,
|
|
2933
|
+
flexiGaugeTpmsMeta.communication.characteristicId,
|
|
2934
|
+
scanMsg
|
|
2935
|
+
);
|
|
2936
|
+
}
|
|
2937
|
+
async function vdaReadConfiguration(deviceId) {
|
|
2938
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=DA PAR=17";
|
|
2939
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2940
|
+
}
|
|
2941
|
+
async function vdaSendConfiguration(deviceId, configuration) {
|
|
2942
|
+
if (configuration.length !== 60) throw new Error("Invalid sensor configuration");
|
|
2943
|
+
const crc = flexiGaugeTpmsSecurity.getCrc(configuration);
|
|
2944
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=EA PAR=17 DATA=${configuration}${crc}`;
|
|
2945
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2946
|
+
}
|
|
2947
|
+
async function sendCommand(deviceId, command) {
|
|
2948
|
+
const message = `${command}\r
|
|
2949
|
+
`;
|
|
2950
|
+
const decimalArray = stringToDecimalArray(message);
|
|
2951
|
+
let result;
|
|
2952
|
+
try {
|
|
2953
|
+
result = await promiseQueue.enqueue(deviceId, flexiGaugeTpmsMeta.communication, decimalArray, "VDA");
|
|
2954
|
+
} catch (error) {
|
|
2955
|
+
flexiGaugeTpms.disconnect(deviceId, "lostConnection");
|
|
2956
|
+
throw error;
|
|
2957
|
+
}
|
|
2958
|
+
return result.map((num) => String.fromCharCode(num)).join("");
|
|
2959
|
+
}
|
|
2960
|
+
function processMessage(deviceId, payload) {
|
|
2961
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier$1, isComplete$1, isError$1);
|
|
2962
|
+
}
|
|
2963
|
+
function isComplete$1(message) {
|
|
2964
|
+
const completeCommand = message.at(-1) === 10;
|
|
2965
|
+
return completeCommand;
|
|
2966
|
+
}
|
|
2967
|
+
function isError$1(message) {
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
function getIdentifier$1(message) {
|
|
2971
|
+
const messageString = message.map((num) => String.fromCharCode(num)).join("");
|
|
2972
|
+
return messageString.slice(0, 3);
|
|
2973
|
+
}
|
|
2974
|
+
function getReversedSensorId(sensorId) {
|
|
2975
|
+
return sensorId.match(/.{1,2}/g).reverse().join("");
|
|
2976
|
+
}
|
|
2799
2977
|
|
|
2800
2978
|
let sensorReadings = [];
|
|
2801
2979
|
const capabilities$1 = [
|
|
2802
2980
|
{
|
|
2803
2981
|
id: "td",
|
|
2804
2982
|
processFn: processTreadDepth,
|
|
2805
|
-
regex:
|
|
2983
|
+
regex: /^\*?TD.*/
|
|
2984
|
+
// old fw sends * for some responses and new fw doesnt send * on any response
|
|
2806
2985
|
},
|
|
2807
2986
|
{
|
|
2808
2987
|
id: "button",
|
|
2809
2988
|
processFn: processButtonPress,
|
|
2810
|
-
regex:
|
|
2989
|
+
regex: /^\*?[UDLRC] $/
|
|
2811
2990
|
},
|
|
2812
2991
|
{
|
|
2813
2992
|
id: "tpms",
|
|
2814
2993
|
processFn: processTpms,
|
|
2815
|
-
regex:
|
|
2994
|
+
regex: /^\*?TPMS.*/
|
|
2816
2995
|
}
|
|
2817
2996
|
];
|
|
2818
2997
|
const flexiGaugeTpmsService = {
|
|
@@ -2840,7 +3019,9 @@ const flexiGaugeTpmsService = {
|
|
|
2840
3019
|
return capability.processFn(deviceId, stringFromBytes);
|
|
2841
3020
|
}
|
|
2842
3021
|
}
|
|
2843
|
-
|
|
3022
|
+
flexiGaugeTpmsCommands.processMessage(deviceId, message);
|
|
3023
|
+
},
|
|
3024
|
+
programSensor
|
|
2844
3025
|
};
|
|
2845
3026
|
function processTreadDepth(deviceId, value) {
|
|
2846
3027
|
const treadDepth = value.match(/\d+/)?.[0];
|
|
@@ -2879,6 +3060,44 @@ function processTpms(deviceId, value) {
|
|
|
2879
3060
|
simulatorSvc.triggerEvent("fg:tpms", deviceId, strongestReading);
|
|
2880
3061
|
}
|
|
2881
3062
|
}
|
|
3063
|
+
async function programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3064
|
+
await findSensor(deviceId, oldSensorId);
|
|
3065
|
+
const seed = await getSeed(deviceId, oldSensorId);
|
|
3066
|
+
const commandsSignature = await flexiGaugeTpmsSecurity.getCommandsSignature(seed);
|
|
3067
|
+
const writeSignature = await flexiGaugeTpmsSecurity.getWriteSignature(seed);
|
|
3068
|
+
await flexiGaugeTpmsCommands.vdaRequestCommandsAccess(deviceId, oldSensorId, commandsSignature);
|
|
3069
|
+
await flexiGaugeTpmsCommands.vdaRequestWriteAccess(deviceId, oldSensorId, writeSignature);
|
|
3070
|
+
const config = await getSensorConfiguration(deviceId);
|
|
3071
|
+
const newConfig = config.replace(/^.{8}/, newSensorId);
|
|
3072
|
+
await flexiGaugeTpmsCommands.vdaSendConfiguration(deviceId, newConfig);
|
|
3073
|
+
await findSensor(deviceId, newSensorId);
|
|
3074
|
+
}
|
|
3075
|
+
async function getSeed(deviceId, sensorId) {
|
|
3076
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSeed(deviceId, sensorId));
|
|
3077
|
+
return response[3];
|
|
3078
|
+
}
|
|
3079
|
+
async function findSensor(deviceId, sensorId) {
|
|
3080
|
+
let seen = 0;
|
|
3081
|
+
for (let i = 0; i < 13; i++) {
|
|
3082
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSensor(deviceId));
|
|
3083
|
+
if (response[2] === sensorId) seen++;
|
|
3084
|
+
if (seen === 3) return;
|
|
3085
|
+
}
|
|
3086
|
+
throw new Error(`Sensor ${sensorId} not found`);
|
|
3087
|
+
}
|
|
3088
|
+
async function getSensorConfiguration(deviceId) {
|
|
3089
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaReadConfiguration(deviceId));
|
|
3090
|
+
return response[3].slice(2, -4);
|
|
3091
|
+
}
|
|
3092
|
+
async function vdaRepeat(fn) {
|
|
3093
|
+
for (let i = 0; i < 5; i++) {
|
|
3094
|
+
const response = await fn;
|
|
3095
|
+
const groupedValue = response.split(" ");
|
|
3096
|
+
if (groupedValue[1].includes("TIMEOUT")) continue;
|
|
3097
|
+
if (response) return groupedValue;
|
|
3098
|
+
}
|
|
3099
|
+
throw new Error("VDA Timeout");
|
|
3100
|
+
}
|
|
2882
3101
|
|
|
2883
3102
|
const flexiGaugeTpms = {
|
|
2884
3103
|
async connect(deviceId) {
|
|
@@ -2902,7 +3121,8 @@ const flexiGaugeTpms = {
|
|
|
2902
3121
|
onButtonPress: flexiGaugeTpmsService.onButton,
|
|
2903
3122
|
onTpms: flexiGaugeTpmsService.onTpms,
|
|
2904
3123
|
getBattery: flexiGaugeTpmsService.getBattery,
|
|
2905
|
-
startTpmsScan: flexiGaugeTpmsService.startTpmsScan
|
|
3124
|
+
startTpmsScan: flexiGaugeTpmsService.startTpmsScan,
|
|
3125
|
+
programSensor: flexiGaugeTpmsService.programSensor
|
|
2906
3126
|
};
|
|
2907
3127
|
|
|
2908
3128
|
const capabilities = [
|
|
@@ -2954,60 +3174,6 @@ const pressureStick = {
|
|
|
2954
3174
|
onPressure: pressureStickService.onPressure
|
|
2955
3175
|
};
|
|
2956
3176
|
|
|
2957
|
-
const devicePromiseQueue = {};
|
|
2958
|
-
const deviceCurrentResolve = {};
|
|
2959
|
-
const deviceCurrentReject = {};
|
|
2960
|
-
const deviceResponseIdentifier = {};
|
|
2961
|
-
const deviceCurrentPartialMessage = {};
|
|
2962
|
-
const torqueWrenchPromiseQueue = {
|
|
2963
|
-
clearQueue(deviceId, message) {
|
|
2964
|
-
if (deviceCurrentReject[deviceId]) {
|
|
2965
|
-
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2966
|
-
}
|
|
2967
|
-
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2968
|
-
deviceResponseIdentifier[deviceId] = void 0;
|
|
2969
|
-
},
|
|
2970
|
-
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2971
|
-
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2972
|
-
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2973
|
-
const promise = new Promise((resolve, reject) => {
|
|
2974
|
-
deviceCurrentResolve[deviceId] = resolve;
|
|
2975
|
-
deviceCurrentReject[deviceId] = reject;
|
|
2976
|
-
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2977
|
-
deviceCurrentPartialMessage[deviceId] = [];
|
|
2978
|
-
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2979
|
-
const chunks = [];
|
|
2980
|
-
for (let i = 0; i < payload.length; i += mtu) {
|
|
2981
|
-
chunks.push(payload.slice(i, i + mtu));
|
|
2982
|
-
}
|
|
2983
|
-
for (const chunk of chunks) {
|
|
2984
|
-
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2985
|
-
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2986
|
-
}
|
|
2987
|
-
});
|
|
2988
|
-
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2989
|
-
});
|
|
2990
|
-
return devicePromiseQueue[deviceId];
|
|
2991
|
-
},
|
|
2992
|
-
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2993
|
-
const numberArray = Array.from(new Uint8Array(payload));
|
|
2994
|
-
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2995
|
-
deviceCurrentPartialMessage[deviceId] = ___default.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2996
|
-
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2997
|
-
return;
|
|
2998
|
-
}
|
|
2999
|
-
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
3000
|
-
return deviceCurrentReject[deviceId](
|
|
3001
|
-
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
3002
|
-
);
|
|
3003
|
-
}
|
|
3004
|
-
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
3005
|
-
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
3006
|
-
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3009
|
-
};
|
|
3010
|
-
|
|
3011
3177
|
const commandStart = "$9";
|
|
3012
3178
|
const commandEnd = "*";
|
|
3013
3179
|
const commandIds = {
|
|
@@ -3067,16 +3233,15 @@ const torqueWrenchCommands = {
|
|
|
3067
3233
|
const decimalArray = Array.from(array);
|
|
3068
3234
|
let result;
|
|
3069
3235
|
try {
|
|
3070
|
-
result = await
|
|
3236
|
+
result = await promiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
|
|
3071
3237
|
} catch (error) {
|
|
3072
3238
|
const formattedError = formatError(error);
|
|
3073
|
-
torqueWrench.disconnect(deviceId, "lostConnection");
|
|
3074
3239
|
throw new Error(formattedError);
|
|
3075
3240
|
}
|
|
3076
3241
|
return result.map((num) => String.fromCharCode(num)).join("");
|
|
3077
3242
|
},
|
|
3078
3243
|
processMessage(deviceId, payload) {
|
|
3079
|
-
|
|
3244
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
|
|
3080
3245
|
}
|
|
3081
3246
|
};
|
|
3082
3247
|
function isComplete(message) {
|
|
@@ -3099,7 +3264,7 @@ function getChecksum(message) {
|
|
|
3099
3264
|
return decimalToHex(checksum).toUpperCase();
|
|
3100
3265
|
}
|
|
3101
3266
|
function formatError(error) {
|
|
3102
|
-
if (!error.cause) throw
|
|
3267
|
+
if (!error.cause) throw error;
|
|
3103
3268
|
const message = error.cause.slice(6, 8);
|
|
3104
3269
|
const errorId = message.map((num) => String.fromCharCode(num)).join("");
|
|
3105
3270
|
return errors[errorId] || "Unknown error";
|
|
@@ -3211,8 +3376,7 @@ const torqueWrenchService = {
|
|
|
3211
3376
|
},
|
|
3212
3377
|
async getTime(deviceId) {
|
|
3213
3378
|
const message = await torqueWrenchCommands.sendCommand(deviceId, "getTime");
|
|
3214
|
-
const match = message.match(/209,([^*]*)\*/);
|
|
3215
|
-
console.log("\u{1F680} ~ getTime ~ match:", match);
|
|
3379
|
+
const match = message.match(/209,([^*]*)\*/) ?? [];
|
|
3216
3380
|
if (!match[1]) throw new Error("Invalid time response");
|
|
3217
3381
|
const hh = match[1].substring(0, 2);
|
|
3218
3382
|
const mm = match[1].substring(2, 4);
|
|
@@ -3261,7 +3425,7 @@ const torqueWrench = {
|
|
|
3261
3425
|
async connect(deviceId) {
|
|
3262
3426
|
const twMeta = deviceMeta.torqueWrench;
|
|
3263
3427
|
await bluetooth.connect(deviceId, this.disconnect);
|
|
3264
|
-
await
|
|
3428
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3265
3429
|
await ble.startNotification(
|
|
3266
3430
|
deviceId,
|
|
3267
3431
|
twMeta.communication.serviceId,
|
|
@@ -3281,7 +3445,7 @@ const torqueWrench = {
|
|
|
3281
3445
|
await torqueWrenchService.stopJob(deviceId);
|
|
3282
3446
|
} catch {
|
|
3283
3447
|
}
|
|
3284
|
-
await
|
|
3448
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3285
3449
|
await bluetooth.disconnect(deviceId);
|
|
3286
3450
|
store.setState(deviceId, void 0, reason ?? "manualDisconnection");
|
|
3287
3451
|
},
|
|
@@ -3632,6 +3796,9 @@ const flexiGaugeTpmsSimulator = {
|
|
|
3632
3796
|
await toolsSvc.delay(200);
|
|
3633
3797
|
return fg.simulatorData.battery;
|
|
3634
3798
|
},
|
|
3799
|
+
programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3800
|
+
return new Promise((resolve) => resolve());
|
|
3801
|
+
},
|
|
3635
3802
|
onButtonPress(callback) {
|
|
3636
3803
|
},
|
|
3637
3804
|
onTpms(callback) {
|