tirecheck-device-sdk 0.2.27 → 0.2.29
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 +297 -102
- package/dist/index.d.cts +10 -2
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.mjs +297 -102
- 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;
|
|
@@ -1475,7 +1478,7 @@ const bridgeSecurity = {
|
|
|
1475
1478
|
const { randomAdvNumber, configVersion } = device?.advertisingData || {};
|
|
1476
1479
|
if (!randomAdvNumber || !___default.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
|
|
1477
1480
|
const rand = ___default.clone(randomAdvNumber);
|
|
1478
|
-
const security_level =
|
|
1481
|
+
const security_level = decimalToHex(command[0]);
|
|
1479
1482
|
const keyIndex = Number(security_level[0]);
|
|
1480
1483
|
const keyShift = Number(security_level[1]);
|
|
1481
1484
|
let paddedCommand = [];
|
|
@@ -1490,8 +1493,8 @@ const bridgeSecurity = {
|
|
|
1490
1493
|
const key = store.securityKeys.bridge.signatureKeys[configVersion][keyIndex];
|
|
1491
1494
|
const startArray = key.slice(keyShift);
|
|
1492
1495
|
const endArray = key.slice(0, keyShift);
|
|
1493
|
-
const shiftedKey = startArray.concat(endArray).map((n) =>
|
|
1494
|
-
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("");
|
|
1495
1498
|
const keyWA = CryptoJs__default.enc.Hex.parse(shiftedKey);
|
|
1496
1499
|
const dataInWA = CryptoJs__default.enc.Hex.parse(dataIn);
|
|
1497
1500
|
const encrypted = CryptoJs__default.AES.encrypt(dataInWA, keyWA, {
|
|
@@ -1509,8 +1512,8 @@ const bridgeSecurity = {
|
|
|
1509
1512
|
for (let index2 = 0; index2 < block.length; index2++) {
|
|
1510
1513
|
xorBlock.push(block[index2] ^ blockVector[index2]);
|
|
1511
1514
|
}
|
|
1512
|
-
const vector = blockVector.map((x) =>
|
|
1513
|
-
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(""));
|
|
1514
1517
|
const testo = CryptoJs__default.AES.encrypt(dataWa, keyWA, {
|
|
1515
1518
|
iv: CryptoJs__default.enc.Hex.parse(vector),
|
|
1516
1519
|
mode: CryptoJs__default.mode.ECB,
|
|
@@ -1527,8 +1530,8 @@ const bridgeSecurity = {
|
|
|
1527
1530
|
if (deviceData?.type !== "bridge") throw new Error("Device data missing 1 ");
|
|
1528
1531
|
const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
|
|
1529
1532
|
if (!randomAdvNumber || !macAddress || !___default.isNumber(configVersion)) throw new Error("Cannot compute pin");
|
|
1530
|
-
const mac = macAddress.map((n) =>
|
|
1531
|
-
const rand = randomAdvNumber.map((n) =>
|
|
1533
|
+
const mac = macAddress.map((n) => decimalToHex(n)).join("");
|
|
1534
|
+
const rand = randomAdvNumber.map((n) => decimalToHex(n)).join("");
|
|
1532
1535
|
const message = `${mac}0000${rand}`;
|
|
1533
1536
|
if (!store.securityKeys?.bridge?.pinKeys?.[configVersion]) throw new Error("No pin key found");
|
|
1534
1537
|
const key = store.securityKeys?.bridge?.pinKeys[configVersion];
|
|
@@ -1540,9 +1543,7 @@ const bridgeSecurity = {
|
|
|
1540
1543
|
const keyHex = CryptoJs__default.enc.Hex.parse(key);
|
|
1541
1544
|
const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
|
|
1542
1545
|
mode: CryptoJs__default.mode.ECB,
|
|
1543
|
-
// You can use other modes like CBC or CTR for additional security
|
|
1544
1546
|
padding: CryptoJs__default.pad.ZeroPadding
|
|
1545
|
-
// You can use other padding modes as well
|
|
1546
1547
|
});
|
|
1547
1548
|
const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1548
1549
|
const hexArray = this.encryptedStringToNum(encryptedByteString);
|
|
@@ -1569,7 +1570,7 @@ const bridgeSecurity = {
|
|
|
1569
1570
|
},
|
|
1570
1571
|
getCrcArray(msg) {
|
|
1571
1572
|
const crc = this.crc32mpeg2(msg);
|
|
1572
|
-
const crcHex =
|
|
1573
|
+
const crcHex = decimalToHex(crc).padStart(8, "0");
|
|
1573
1574
|
return bridgeTools.hexToDecimalArray(crcHex).reverse();
|
|
1574
1575
|
},
|
|
1575
1576
|
uint32ToLittleEndian(value) {
|
|
@@ -1587,7 +1588,7 @@ const devicePromiseQueue$1 = {};
|
|
|
1587
1588
|
const deviceCurrentResolve$1 = {};
|
|
1588
1589
|
const deviceCurrentReject$1 = {};
|
|
1589
1590
|
const deviceResponseIdentifier$1 = {};
|
|
1590
|
-
const promiseQueue = {
|
|
1591
|
+
const promiseQueue$1 = {
|
|
1591
1592
|
clearQueue(deviceId, message) {
|
|
1592
1593
|
if (deviceCurrentReject$1[deviceId]) {
|
|
1593
1594
|
deviceCurrentReject$1[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
@@ -1628,7 +1629,7 @@ const promiseQueue = {
|
|
|
1628
1629
|
);
|
|
1629
1630
|
}
|
|
1630
1631
|
});
|
|
1631
|
-
return withTimeout(promise,
|
|
1632
|
+
return withTimeout(promise, 8e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
|
|
1632
1633
|
});
|
|
1633
1634
|
return devicePromiseQueue$1[device.id];
|
|
1634
1635
|
},
|
|
@@ -2001,7 +2002,7 @@ const bridgeCommands = {
|
|
|
2001
2002
|
async writeCommand(device, commandId, subCommandId, payload) {
|
|
2002
2003
|
const commandHead = this.getCommandHeader(device);
|
|
2003
2004
|
const macAddress = [0, 0, 0, 0, 0, 0];
|
|
2004
|
-
const timestamp =
|
|
2005
|
+
const timestamp = decimalToHex(Math.floor(Date.now() / 1e3));
|
|
2005
2006
|
const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16)).reverse();
|
|
2006
2007
|
if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
|
|
2007
2008
|
const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
|
|
@@ -2039,7 +2040,7 @@ const bridgeCommands = {
|
|
|
2039
2040
|
}
|
|
2040
2041
|
this.sendKeepAliveCommand(device);
|
|
2041
2042
|
}, 8e3);
|
|
2042
|
-
return promiseQueue.enqueue(device, writeCommand);
|
|
2043
|
+
return promiseQueue$1.enqueue(device, writeCommand);
|
|
2043
2044
|
}
|
|
2044
2045
|
};
|
|
2045
2046
|
|
|
@@ -2230,7 +2231,7 @@ function getVehicleAxlesTypes(tcVehicle) {
|
|
|
2230
2231
|
if (axle?.isDrive) {
|
|
2231
2232
|
binaryRepresentation |= 16;
|
|
2232
2233
|
}
|
|
2233
|
-
return
|
|
2234
|
+
return decimalToHex(binaryRepresentation);
|
|
2234
2235
|
});
|
|
2235
2236
|
return axleTypesBinary;
|
|
2236
2237
|
}
|
|
@@ -2385,7 +2386,7 @@ async function setVehicle(deviceId, tcVehicle) {
|
|
|
2385
2386
|
}
|
|
2386
2387
|
async function getSensorReading(deviceId, positionId) {
|
|
2387
2388
|
const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
|
|
2388
|
-
const sensorId = value.slice(5, 9).reverse().map((s) =>
|
|
2389
|
+
const sensorId = value.slice(5, 9).reverse().map((s) => decimalToHex(s).toUpperCase()).join("");
|
|
2389
2390
|
const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
|
|
2390
2391
|
return {
|
|
2391
2392
|
date: Date.now() - value[14] * 2e3,
|
|
@@ -2427,7 +2428,7 @@ async function getAutolearnStatuses(deviceId, tcVehicle) {
|
|
|
2427
2428
|
let autolearnedSensorId;
|
|
2428
2429
|
if (["1", "4", "9"].includes(tyreStatus)) {
|
|
2429
2430
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
|
|
2430
|
-
const axleData = axleInfo.map((i) =>
|
|
2431
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2431
2432
|
const joinedAxleData = Array.from(
|
|
2432
2433
|
{ length: axleData.length / 4 },
|
|
2433
2434
|
(_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
|
|
@@ -2637,7 +2638,7 @@ async function assignTyres(deviceId, tcVehicle) {
|
|
|
2637
2638
|
for (let index = 0; index < tcVehicle.axles.length; index++) {
|
|
2638
2639
|
const axle = tcVehicle.axles[index];
|
|
2639
2640
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
|
|
2640
|
-
const axleData = axleInfo.map((i) =>
|
|
2641
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2641
2642
|
const joinedAxleData = Array.from(
|
|
2642
2643
|
{ length: axleData.length / 4 },
|
|
2643
2644
|
(_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
|
|
@@ -2713,7 +2714,7 @@ const bridge = {
|
|
|
2713
2714
|
};
|
|
2714
2715
|
async function connect(deviceId, accessLevel) {
|
|
2715
2716
|
if (canCommunicateWith(deviceId)) return;
|
|
2716
|
-
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2717
|
+
await promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2717
2718
|
const bridgeMeta = deviceMeta.bridge;
|
|
2718
2719
|
await bluetooth.connect(deviceId, disconnect);
|
|
2719
2720
|
if (store.deviceState[deviceId] === "paired") {
|
|
@@ -2728,7 +2729,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2728
2729
|
_deviceId,
|
|
2729
2730
|
bridgeMeta.communication.serviceId,
|
|
2730
2731
|
bridgeMeta.communication.characteristicId,
|
|
2731
|
-
(notification) => promiseQueue.processMessage(deviceId, notification),
|
|
2732
|
+
(notification) => promiseQueue$1.processMessage(deviceId, notification),
|
|
2732
2733
|
(error) => console.error("startNotification Error", error)
|
|
2733
2734
|
);
|
|
2734
2735
|
store.deviceAccessLevel[deviceId] = accessLevel ?? "driver";
|
|
@@ -2742,7 +2743,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2742
2743
|
}
|
|
2743
2744
|
async function disconnect(deviceId, reason) {
|
|
2744
2745
|
store.setState(deviceId, "disconnecting");
|
|
2745
|
-
promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2746
|
+
promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2746
2747
|
await bluetooth.disconnect(deviceId);
|
|
2747
2748
|
delete store.devices[deviceId];
|
|
2748
2749
|
deviceUnreachableCallback?.(deviceId);
|
|
@@ -2773,10 +2774,119 @@ const simulatorSvc = {
|
|
|
2773
2774
|
}
|
|
2774
2775
|
};
|
|
2775
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
|
+
|
|
2776
2877
|
const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
|
|
2777
2878
|
const flexiGaugeTpmsCommands = {
|
|
2778
2879
|
startTpmsScan,
|
|
2779
|
-
getBattery
|
|
2880
|
+
getBattery,
|
|
2881
|
+
vdaRequestSensor,
|
|
2882
|
+
vdaRequestSeed,
|
|
2883
|
+
vdaSendWriteModePin,
|
|
2884
|
+
vdaReadConfiguration,
|
|
2885
|
+
vdaSendConfiguration,
|
|
2886
|
+
vdaRequestCommandsAccess,
|
|
2887
|
+
vdaRequestWriteAccess,
|
|
2888
|
+
processMessage,
|
|
2889
|
+
getFirmwareVersion: getFirmwareVersion$1
|
|
2780
2890
|
};
|
|
2781
2891
|
async function startTpmsScan(deviceId) {
|
|
2782
2892
|
if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
|
|
@@ -2797,23 +2907,96 @@ async function getBattery(deviceId) {
|
|
|
2797
2907
|
const battery = Array.from(new Uint8Array(batteryValue))[0];
|
|
2798
2908
|
return battery;
|
|
2799
2909
|
}
|
|
2910
|
+
async function getFirmwareVersion$1(deviceId) {
|
|
2911
|
+
const msg = "*FWVER?";
|
|
2912
|
+
return sendCommand(deviceId, msg, "FWV");
|
|
2913
|
+
}
|
|
2914
|
+
async function vdaRequestSensor(deviceId) {
|
|
2915
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=1A";
|
|
2916
|
+
return sendCommand(deviceId, scanMsg);
|
|
2917
|
+
}
|
|
2918
|
+
async function vdaRequestSeed(deviceId, sensorId) {
|
|
2919
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2920
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=04 DATA=${sensorIdReversed}CE9A5713`;
|
|
2921
|
+
return sendCommand(deviceId, scanMsg);
|
|
2922
|
+
}
|
|
2923
|
+
async function vdaRequestCommandsAccess(deviceId, sensorId, signature) {
|
|
2924
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2925
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=${sensorIdReversed}${signature}`;
|
|
2926
|
+
return sendCommand(deviceId, scanMsg);
|
|
2927
|
+
}
|
|
2928
|
+
async function vdaRequestWriteAccess(deviceId, sensorId, signature) {
|
|
2929
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2930
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=02 DATA=${sensorIdReversed}${signature}`;
|
|
2931
|
+
return sendCommand(deviceId, scanMsg);
|
|
2932
|
+
}
|
|
2933
|
+
async function vdaSendWriteModePin(deviceId) {
|
|
2934
|
+
const scanMsg = stringToArrayBuffer("*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=FCCC71F6\r\n");
|
|
2935
|
+
await bluetooth.write(
|
|
2936
|
+
deviceId,
|
|
2937
|
+
flexiGaugeTpmsMeta.communication.serviceId,
|
|
2938
|
+
flexiGaugeTpmsMeta.communication.characteristicId,
|
|
2939
|
+
scanMsg
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
async function vdaReadConfiguration(deviceId) {
|
|
2943
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=DA PAR=17";
|
|
2944
|
+
return sendCommand(deviceId, scanMsg);
|
|
2945
|
+
}
|
|
2946
|
+
async function vdaSendConfiguration(deviceId, configuration) {
|
|
2947
|
+
if (configuration.length !== 60) throw new Error("Invalid sensor configuration");
|
|
2948
|
+
const crc = flexiGaugeTpmsSecurity.getCrc(configuration);
|
|
2949
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=EA PAR=17 DATA=${configuration}${crc}`;
|
|
2950
|
+
return sendCommand(deviceId, scanMsg);
|
|
2951
|
+
}
|
|
2952
|
+
async function sendCommand(deviceId, command, identifier = "VDA") {
|
|
2953
|
+
const message = `${command}\r
|
|
2954
|
+
`;
|
|
2955
|
+
const decimalArray = stringToDecimalArray(message);
|
|
2956
|
+
let result;
|
|
2957
|
+
try {
|
|
2958
|
+
result = await promiseQueue.enqueue(deviceId, flexiGaugeTpmsMeta.communication, decimalArray, identifier);
|
|
2959
|
+
} catch (error) {
|
|
2960
|
+
flexiGaugeTpms.disconnect(deviceId, "lostConnection");
|
|
2961
|
+
throw error;
|
|
2962
|
+
}
|
|
2963
|
+
return result.map((num) => String.fromCharCode(num)).join("");
|
|
2964
|
+
}
|
|
2965
|
+
function processMessage(deviceId, payload) {
|
|
2966
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier$1, isComplete$1, isError$1);
|
|
2967
|
+
}
|
|
2968
|
+
function isComplete$1(message) {
|
|
2969
|
+
const completeCommand = message.at(-1) === 10;
|
|
2970
|
+
return completeCommand;
|
|
2971
|
+
}
|
|
2972
|
+
function isError$1(message) {
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
function getIdentifier$1(message) {
|
|
2976
|
+
const messageString = message.map((num) => String.fromCharCode(num)).join("");
|
|
2977
|
+
return messageString.slice(0, 3);
|
|
2978
|
+
}
|
|
2979
|
+
function getReversedSensorId(sensorId) {
|
|
2980
|
+
return sensorId.match(/.{1,2}/g).reverse().join("");
|
|
2981
|
+
}
|
|
2800
2982
|
|
|
2801
2983
|
let sensorReadings = [];
|
|
2802
2984
|
const capabilities$1 = [
|
|
2803
2985
|
{
|
|
2804
2986
|
id: "td",
|
|
2805
2987
|
processFn: processTreadDepth,
|
|
2806
|
-
regex:
|
|
2988
|
+
regex: /^\*?TD.*/
|
|
2989
|
+
// old fw sends * for some responses and new fw doesnt send * on any response
|
|
2807
2990
|
},
|
|
2808
2991
|
{
|
|
2809
2992
|
id: "button",
|
|
2810
2993
|
processFn: processButtonPress,
|
|
2811
|
-
regex:
|
|
2994
|
+
regex: /^\*?[UDLRC] $/
|
|
2812
2995
|
},
|
|
2813
2996
|
{
|
|
2814
2997
|
id: "tpms",
|
|
2815
2998
|
processFn: processTpms,
|
|
2816
|
-
regex:
|
|
2999
|
+
regex: /^\*?TPMS.*/
|
|
2817
3000
|
}
|
|
2818
3001
|
];
|
|
2819
3002
|
const flexiGaugeTpmsService = {
|
|
@@ -2841,7 +3024,10 @@ const flexiGaugeTpmsService = {
|
|
|
2841
3024
|
return capability.processFn(deviceId, stringFromBytes);
|
|
2842
3025
|
}
|
|
2843
3026
|
}
|
|
2844
|
-
|
|
3027
|
+
flexiGaugeTpmsCommands.processMessage(deviceId, message);
|
|
3028
|
+
},
|
|
3029
|
+
programSensor,
|
|
3030
|
+
getFirmwareVersion
|
|
2845
3031
|
};
|
|
2846
3032
|
function processTreadDepth(deviceId, value) {
|
|
2847
3033
|
const treadDepth = value.match(/\d+/)?.[0];
|
|
@@ -2880,6 +3066,62 @@ function processTpms(deviceId, value) {
|
|
|
2880
3066
|
simulatorSvc.triggerEvent("fg:tpms", deviceId, strongestReading);
|
|
2881
3067
|
}
|
|
2882
3068
|
}
|
|
3069
|
+
async function programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3070
|
+
const fw = await getFirmwareVersion(deviceId);
|
|
3071
|
+
if (fw < "1.4.4") throw new Error("Programming sensors is not supported on this firmware version");
|
|
3072
|
+
await findSensor(deviceId, oldSensorId);
|
|
3073
|
+
const seed = await getSeed(deviceId, oldSensorId);
|
|
3074
|
+
const commandsSignature = await flexiGaugeTpmsSecurity.getCommandsSignature(seed);
|
|
3075
|
+
const writeSignature = await flexiGaugeTpmsSecurity.getWriteSignature(seed);
|
|
3076
|
+
await flexiGaugeTpmsCommands.vdaRequestCommandsAccess(deviceId, oldSensorId, commandsSignature);
|
|
3077
|
+
await flexiGaugeTpmsCommands.vdaRequestWriteAccess(deviceId, oldSensorId, writeSignature);
|
|
3078
|
+
const config = await getSensorConfiguration(deviceId);
|
|
3079
|
+
const newConfig = config.replace(/^.{8}/, newSensorId);
|
|
3080
|
+
await flexiGaugeTpmsCommands.vdaSendConfiguration(deviceId, newConfig);
|
|
3081
|
+
await findSensor(deviceId, newSensorId);
|
|
3082
|
+
}
|
|
3083
|
+
async function getSeed(deviceId, sensorId) {
|
|
3084
|
+
try {
|
|
3085
|
+
const response = await vdaRepeat(() => flexiGaugeTpmsCommands.vdaRequestSeed(deviceId, sensorId));
|
|
3086
|
+
return response[3];
|
|
3087
|
+
} catch {
|
|
3088
|
+
throw new Error("Failed to get signature seed");
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
async function findSensor(deviceId, sensorId) {
|
|
3092
|
+
let seen = 0;
|
|
3093
|
+
for (let i = 0; i < 6; i++) {
|
|
3094
|
+
try {
|
|
3095
|
+
const response = await vdaRepeat(() => flexiGaugeTpmsCommands.vdaRequestSensor(deviceId));
|
|
3096
|
+
if (response[2] === sensorId) seen++;
|
|
3097
|
+
if (seen === 3) return;
|
|
3098
|
+
} catch {
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
throw new Error(`Sensor ${sensorId} not found`);
|
|
3102
|
+
}
|
|
3103
|
+
async function getSensorConfiguration(deviceId) {
|
|
3104
|
+
try {
|
|
3105
|
+
const response = await vdaRepeat(() => flexiGaugeTpmsCommands.vdaReadConfiguration(deviceId));
|
|
3106
|
+
return response[3].slice(2, -4);
|
|
3107
|
+
} catch {
|
|
3108
|
+
throw new Error("Failed to get sensor configuration");
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
async function vdaRepeat(fn) {
|
|
3112
|
+
for (let i = 0; i < 5; i++) {
|
|
3113
|
+
const response = await fn();
|
|
3114
|
+
const groupedValue = response.split(" ");
|
|
3115
|
+
if (groupedValue[1].includes("TIMEOUT")) continue;
|
|
3116
|
+
if (response) return groupedValue;
|
|
3117
|
+
}
|
|
3118
|
+
throw new Error("VDA Timeout");
|
|
3119
|
+
}
|
|
3120
|
+
async function getFirmwareVersion(deviceId) {
|
|
3121
|
+
const response = await flexiGaugeTpmsCommands.getFirmwareVersion(deviceId);
|
|
3122
|
+
const groupedValue = response.split(" ");
|
|
3123
|
+
return groupedValue[1];
|
|
3124
|
+
}
|
|
2883
3125
|
|
|
2884
3126
|
const flexiGaugeTpms = {
|
|
2885
3127
|
async connect(deviceId) {
|
|
@@ -2903,7 +3145,9 @@ const flexiGaugeTpms = {
|
|
|
2903
3145
|
onButtonPress: flexiGaugeTpmsService.onButton,
|
|
2904
3146
|
onTpms: flexiGaugeTpmsService.onTpms,
|
|
2905
3147
|
getBattery: flexiGaugeTpmsService.getBattery,
|
|
2906
|
-
|
|
3148
|
+
getFirmwareVersion: flexiGaugeTpmsService.getFirmwareVersion,
|
|
3149
|
+
startTpmsScan: flexiGaugeTpmsService.startTpmsScan,
|
|
3150
|
+
programSensor: flexiGaugeTpmsService.programSensor
|
|
2907
3151
|
};
|
|
2908
3152
|
|
|
2909
3153
|
const capabilities = [
|
|
@@ -2955,60 +3199,6 @@ const pressureStick = {
|
|
|
2955
3199
|
onPressure: pressureStickService.onPressure
|
|
2956
3200
|
};
|
|
2957
3201
|
|
|
2958
|
-
const devicePromiseQueue = {};
|
|
2959
|
-
const deviceCurrentResolve = {};
|
|
2960
|
-
const deviceCurrentReject = {};
|
|
2961
|
-
const deviceResponseIdentifier = {};
|
|
2962
|
-
const deviceCurrentPartialMessage = {};
|
|
2963
|
-
const torqueWrenchPromiseQueue = {
|
|
2964
|
-
clearQueue(deviceId, message) {
|
|
2965
|
-
if (deviceCurrentReject[deviceId]) {
|
|
2966
|
-
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2967
|
-
}
|
|
2968
|
-
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2969
|
-
deviceResponseIdentifier[deviceId] = void 0;
|
|
2970
|
-
},
|
|
2971
|
-
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2972
|
-
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2973
|
-
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2974
|
-
const promise = new Promise((resolve, reject) => {
|
|
2975
|
-
deviceCurrentResolve[deviceId] = resolve;
|
|
2976
|
-
deviceCurrentReject[deviceId] = reject;
|
|
2977
|
-
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2978
|
-
deviceCurrentPartialMessage[deviceId] = [];
|
|
2979
|
-
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2980
|
-
const chunks = [];
|
|
2981
|
-
for (let i = 0; i < payload.length; i += mtu) {
|
|
2982
|
-
chunks.push(payload.slice(i, i + mtu));
|
|
2983
|
-
}
|
|
2984
|
-
for (const chunk of chunks) {
|
|
2985
|
-
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2986
|
-
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2987
|
-
}
|
|
2988
|
-
});
|
|
2989
|
-
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2990
|
-
});
|
|
2991
|
-
return devicePromiseQueue[deviceId];
|
|
2992
|
-
},
|
|
2993
|
-
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2994
|
-
const numberArray = Array.from(new Uint8Array(payload));
|
|
2995
|
-
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2996
|
-
deviceCurrentPartialMessage[deviceId] = ___default.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2997
|
-
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2998
|
-
return;
|
|
2999
|
-
}
|
|
3000
|
-
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
3001
|
-
return deviceCurrentReject[deviceId](
|
|
3002
|
-
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
3006
|
-
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
3007
|
-
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3010
|
-
};
|
|
3011
|
-
|
|
3012
3202
|
const commandStart = "$9";
|
|
3013
3203
|
const commandEnd = "*";
|
|
3014
3204
|
const commandIds = {
|
|
@@ -3068,16 +3258,15 @@ const torqueWrenchCommands = {
|
|
|
3068
3258
|
const decimalArray = Array.from(array);
|
|
3069
3259
|
let result;
|
|
3070
3260
|
try {
|
|
3071
|
-
result = await
|
|
3261
|
+
result = await promiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
|
|
3072
3262
|
} catch (error) {
|
|
3073
3263
|
const formattedError = formatError(error);
|
|
3074
|
-
torqueWrench.disconnect(deviceId, "lostConnection");
|
|
3075
3264
|
throw new Error(formattedError);
|
|
3076
3265
|
}
|
|
3077
3266
|
return result.map((num) => String.fromCharCode(num)).join("");
|
|
3078
3267
|
},
|
|
3079
3268
|
processMessage(deviceId, payload) {
|
|
3080
|
-
|
|
3269
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
|
|
3081
3270
|
}
|
|
3082
3271
|
};
|
|
3083
3272
|
function isComplete(message) {
|
|
@@ -3100,7 +3289,7 @@ function getChecksum(message) {
|
|
|
3100
3289
|
return decimalToHex(checksum).toUpperCase();
|
|
3101
3290
|
}
|
|
3102
3291
|
function formatError(error) {
|
|
3103
|
-
if (!error.cause) throw
|
|
3292
|
+
if (!error.cause) throw error;
|
|
3104
3293
|
const message = error.cause.slice(6, 8);
|
|
3105
3294
|
const errorId = message.map((num) => String.fromCharCode(num)).join("");
|
|
3106
3295
|
return errors[errorId] || "Unknown error";
|
|
@@ -3212,8 +3401,7 @@ const torqueWrenchService = {
|
|
|
3212
3401
|
},
|
|
3213
3402
|
async getTime(deviceId) {
|
|
3214
3403
|
const message = await torqueWrenchCommands.sendCommand(deviceId, "getTime");
|
|
3215
|
-
const match = message.match(/209,([^*]*)\*/);
|
|
3216
|
-
console.log("\u{1F680} ~ getTime ~ match:", match);
|
|
3404
|
+
const match = message.match(/209,([^*]*)\*/) ?? [];
|
|
3217
3405
|
if (!match[1]) throw new Error("Invalid time response");
|
|
3218
3406
|
const hh = match[1].substring(0, 2);
|
|
3219
3407
|
const mm = match[1].substring(2, 4);
|
|
@@ -3262,7 +3450,7 @@ const torqueWrench = {
|
|
|
3262
3450
|
async connect(deviceId) {
|
|
3263
3451
|
const twMeta = deviceMeta.torqueWrench;
|
|
3264
3452
|
await bluetooth.connect(deviceId, this.disconnect);
|
|
3265
|
-
await
|
|
3453
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3266
3454
|
await ble.startNotification(
|
|
3267
3455
|
deviceId,
|
|
3268
3456
|
twMeta.communication.serviceId,
|
|
@@ -3282,7 +3470,7 @@ const torqueWrench = {
|
|
|
3282
3470
|
await torqueWrenchService.stopJob(deviceId);
|
|
3283
3471
|
} catch {
|
|
3284
3472
|
}
|
|
3285
|
-
await
|
|
3473
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3286
3474
|
await bluetooth.disconnect(deviceId);
|
|
3287
3475
|
store.setState(deviceId, void 0, reason ?? "manualDisconnection");
|
|
3288
3476
|
},
|
|
@@ -3633,6 +3821,13 @@ const flexiGaugeTpmsSimulator = {
|
|
|
3633
3821
|
await toolsSvc.delay(200);
|
|
3634
3822
|
return fg.simulatorData.battery;
|
|
3635
3823
|
},
|
|
3824
|
+
async getFirmwareVersion(deviceId) {
|
|
3825
|
+
await toolsSvc.delay(100);
|
|
3826
|
+
return "9.9.9";
|
|
3827
|
+
},
|
|
3828
|
+
programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3829
|
+
return new Promise((resolve) => resolve());
|
|
3830
|
+
},
|
|
3636
3831
|
onButtonPress(callback) {
|
|
3637
3832
|
},
|
|
3638
3833
|
onTpms(callback) {
|