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.mjs
CHANGED
|
@@ -99,6 +99,13 @@ function stringToArrayBuffer(string) {
|
|
|
99
99
|
}
|
|
100
100
|
return array.buffer;
|
|
101
101
|
}
|
|
102
|
+
function stringToDecimalArray(string) {
|
|
103
|
+
const array = new Uint8Array(string.length);
|
|
104
|
+
for (let i = 0, l = string.length; i < l; i++) {
|
|
105
|
+
array[i] = string.charCodeAt(i);
|
|
106
|
+
}
|
|
107
|
+
return Array.from(array);
|
|
108
|
+
}
|
|
102
109
|
function decimalToHex(decimal, padStart = 2) {
|
|
103
110
|
const hex = decimal.toString(16);
|
|
104
111
|
return hex.padStart(padStart, "0");
|
|
@@ -120,7 +127,7 @@ const tyreLabels = getDefaultTyreLabels();
|
|
|
120
127
|
const bridgeTools = {
|
|
121
128
|
getBridgeFromStore(deviceId) {
|
|
122
129
|
const deviceData = store.devices[deviceId];
|
|
123
|
-
if (!deviceData) throw new Error(
|
|
130
|
+
if (!deviceData) throw new Error(`No device data found: ${deviceId}`);
|
|
124
131
|
if (deviceData.type !== "bridge") throw new Error("Device is not bridge");
|
|
125
132
|
return deviceData;
|
|
126
133
|
},
|
|
@@ -182,12 +189,12 @@ const bridgeTools = {
|
|
|
182
189
|
}
|
|
183
190
|
const _reversedData = _data.reverse();
|
|
184
191
|
if (displayUnits === "decimal") {
|
|
185
|
-
return Number(`0x${_reversedData.map((dec) =>
|
|
192
|
+
return Number(`0x${_reversedData.map((dec) => decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
|
|
186
193
|
}
|
|
187
194
|
if (displayUnits === "reverseHex") {
|
|
188
|
-
return _reversedData.map((dec) =>
|
|
195
|
+
return _reversedData.map((dec) => decimalToHex(dec).split("").reverse().join("")).join("");
|
|
189
196
|
}
|
|
190
|
-
return _reversedData.map((dec) =>
|
|
197
|
+
return _reversedData.map((dec) => decimalToHex(dec)).join("");
|
|
191
198
|
},
|
|
192
199
|
encodeData(data, displayUnits) {
|
|
193
200
|
const _data = _.clone(data);
|
|
@@ -195,7 +202,7 @@ const bridgeTools = {
|
|
|
195
202
|
return this.asciiToDecimalArray(_data);
|
|
196
203
|
}
|
|
197
204
|
if (displayUnits === "decimal") {
|
|
198
|
-
return this.hexToDecimalArray(
|
|
205
|
+
return this.hexToDecimalArray(decimalToHex(_data)).reverse();
|
|
199
206
|
}
|
|
200
207
|
if (displayUnits === "reverseHex") {
|
|
201
208
|
const byteArray = _data.match(/.{1,2}/g) || [];
|
|
@@ -211,10 +218,6 @@ const bridgeTools = {
|
|
|
211
218
|
}
|
|
212
219
|
return array;
|
|
213
220
|
},
|
|
214
|
-
decimalToHex(decimal) {
|
|
215
|
-
const hex = decimal.toString(16);
|
|
216
|
-
return hex.padStart(2, "0");
|
|
217
|
-
},
|
|
218
221
|
asciiToDecimalArray(ascii) {
|
|
219
222
|
return ascii.split("").map((x) => x.charCodeAt(0));
|
|
220
223
|
},
|
|
@@ -227,7 +230,7 @@ const bridgeTools = {
|
|
|
227
230
|
return "";
|
|
228
231
|
}
|
|
229
232
|
return payload.reduce((acc, current) => {
|
|
230
|
-
const value =
|
|
233
|
+
const value = decimalToHex(current);
|
|
231
234
|
acc.push(value[0] === "0" ? value[1] : value);
|
|
232
235
|
return acc;
|
|
233
236
|
}, []).join(".");
|
|
@@ -349,7 +352,7 @@ const bridgeAdvertisingParser = {
|
|
|
349
352
|
if (adversitingType !== "connectable") return;
|
|
350
353
|
const advertisingData = getAdvertisingData({ advertising: device.advertising, deviceName: device.name });
|
|
351
354
|
if (!advertisingData) return;
|
|
352
|
-
const macArray = advertisingData?.macAddress?.map((n) =>
|
|
355
|
+
const macArray = advertisingData?.macAddress?.map((n) => decimalToHex(n).toUpperCase()).reverse() || [];
|
|
353
356
|
const bridgeId = macArray.join("");
|
|
354
357
|
const vin = String.fromCharCode(...advertisingData?.vinNum || []).split("\0").join("");
|
|
355
358
|
const macString = macArray.join(":");
|
|
@@ -465,7 +468,7 @@ const bridgeOtaAdvertisingParser = {
|
|
|
465
468
|
let processedByteCount = 0;
|
|
466
469
|
if (store.platform === "ios") {
|
|
467
470
|
if (!device.advertising.kCBAdvDataLeBluetoothDeviceAddress) return void 0;
|
|
468
|
-
const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) =>
|
|
471
|
+
const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
|
|
469
472
|
deviceId = macAddress.join(":");
|
|
470
473
|
bridgeId = macAddress.join("");
|
|
471
474
|
} else {
|
|
@@ -478,7 +481,7 @@ const bridgeOtaAdvertisingParser = {
|
|
|
478
481
|
}
|
|
479
482
|
processedByteCount += length + 1;
|
|
480
483
|
if (identificator === 27) {
|
|
481
|
-
const macAddress = packet.slice(-6).reverse().map((x) =>
|
|
484
|
+
const macAddress = packet.slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
|
|
482
485
|
deviceId = macAddress.join(":");
|
|
483
486
|
bridgeId = macAddress.join("");
|
|
484
487
|
processedByteCount = adv.length;
|
|
@@ -536,7 +539,8 @@ const deviceMeta = {
|
|
|
536
539
|
// },
|
|
537
540
|
// },
|
|
538
541
|
flexiGaugeTpms: {
|
|
539
|
-
|
|
542
|
+
// after implementation of normal fg replace with /^Flexi.*TPMS.*/,
|
|
543
|
+
nameRegex: /^Flexi.*/,
|
|
540
544
|
communication: {
|
|
541
545
|
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
542
546
|
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d6"
|
|
@@ -1467,7 +1471,7 @@ const bridgeSecurity = {
|
|
|
1467
1471
|
const { randomAdvNumber, configVersion } = device?.advertisingData || {};
|
|
1468
1472
|
if (!randomAdvNumber || !_.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
|
|
1469
1473
|
const rand = _.clone(randomAdvNumber);
|
|
1470
|
-
const security_level =
|
|
1474
|
+
const security_level = decimalToHex(command[0]);
|
|
1471
1475
|
const keyIndex = Number(security_level[0]);
|
|
1472
1476
|
const keyShift = Number(security_level[1]);
|
|
1473
1477
|
let paddedCommand = [];
|
|
@@ -1482,8 +1486,8 @@ const bridgeSecurity = {
|
|
|
1482
1486
|
const key = store.securityKeys.bridge.signatureKeys[configVersion][keyIndex];
|
|
1483
1487
|
const startArray = key.slice(keyShift);
|
|
1484
1488
|
const endArray = key.slice(0, keyShift);
|
|
1485
|
-
const shiftedKey = startArray.concat(endArray).map((n) =>
|
|
1486
|
-
const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) =>
|
|
1489
|
+
const shiftedKey = startArray.concat(endArray).map((n) => decimalToHex(n)).join("");
|
|
1490
|
+
const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => decimalToHex(x)).join("");
|
|
1487
1491
|
const keyWA = CryptoJs.enc.Hex.parse(shiftedKey);
|
|
1488
1492
|
const dataInWA = CryptoJs.enc.Hex.parse(dataIn);
|
|
1489
1493
|
const encrypted = CryptoJs.AES.encrypt(dataInWA, keyWA, {
|
|
@@ -1501,8 +1505,8 @@ const bridgeSecurity = {
|
|
|
1501
1505
|
for (let index2 = 0; index2 < block.length; index2++) {
|
|
1502
1506
|
xorBlock.push(block[index2] ^ blockVector[index2]);
|
|
1503
1507
|
}
|
|
1504
|
-
const vector = blockVector.map((x) =>
|
|
1505
|
-
const dataWa = CryptoJs.enc.Hex.parse(xorBlock.map((x) =>
|
|
1508
|
+
const vector = blockVector.map((x) => decimalToHex(x)).join("");
|
|
1509
|
+
const dataWa = CryptoJs.enc.Hex.parse(xorBlock.map((x) => decimalToHex(x)).join(""));
|
|
1506
1510
|
const testo = CryptoJs.AES.encrypt(dataWa, keyWA, {
|
|
1507
1511
|
iv: CryptoJs.enc.Hex.parse(vector),
|
|
1508
1512
|
mode: CryptoJs.mode.ECB,
|
|
@@ -1519,8 +1523,8 @@ const bridgeSecurity = {
|
|
|
1519
1523
|
if (deviceData?.type !== "bridge") throw new Error("Device data missing 1 ");
|
|
1520
1524
|
const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
|
|
1521
1525
|
if (!randomAdvNumber || !macAddress || !_.isNumber(configVersion)) throw new Error("Cannot compute pin");
|
|
1522
|
-
const mac = macAddress.map((n) =>
|
|
1523
|
-
const rand = randomAdvNumber.map((n) =>
|
|
1526
|
+
const mac = macAddress.map((n) => decimalToHex(n)).join("");
|
|
1527
|
+
const rand = randomAdvNumber.map((n) => decimalToHex(n)).join("");
|
|
1524
1528
|
const message = `${mac}0000${rand}`;
|
|
1525
1529
|
if (!store.securityKeys?.bridge?.pinKeys?.[configVersion]) throw new Error("No pin key found");
|
|
1526
1530
|
const key = store.securityKeys?.bridge?.pinKeys[configVersion];
|
|
@@ -1532,9 +1536,7 @@ const bridgeSecurity = {
|
|
|
1532
1536
|
const keyHex = CryptoJs.enc.Hex.parse(key);
|
|
1533
1537
|
const encrypted = CryptoJs.AES.encrypt(messageHex, keyHex, {
|
|
1534
1538
|
mode: CryptoJs.mode.ECB,
|
|
1535
|
-
// You can use other modes like CBC or CTR for additional security
|
|
1536
1539
|
padding: CryptoJs.pad.ZeroPadding
|
|
1537
|
-
// You can use other padding modes as well
|
|
1538
1540
|
});
|
|
1539
1541
|
const encryptedByteString = CryptoJs.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1540
1542
|
const hexArray = this.encryptedStringToNum(encryptedByteString);
|
|
@@ -1561,7 +1563,7 @@ const bridgeSecurity = {
|
|
|
1561
1563
|
},
|
|
1562
1564
|
getCrcArray(msg) {
|
|
1563
1565
|
const crc = this.crc32mpeg2(msg);
|
|
1564
|
-
const crcHex =
|
|
1566
|
+
const crcHex = decimalToHex(crc).padStart(8, "0");
|
|
1565
1567
|
return bridgeTools.hexToDecimalArray(crcHex).reverse();
|
|
1566
1568
|
},
|
|
1567
1569
|
uint32ToLittleEndian(value) {
|
|
@@ -1579,7 +1581,7 @@ const devicePromiseQueue$1 = {};
|
|
|
1579
1581
|
const deviceCurrentResolve$1 = {};
|
|
1580
1582
|
const deviceCurrentReject$1 = {};
|
|
1581
1583
|
const deviceResponseIdentifier$1 = {};
|
|
1582
|
-
const promiseQueue = {
|
|
1584
|
+
const promiseQueue$1 = {
|
|
1583
1585
|
clearQueue(deviceId, message) {
|
|
1584
1586
|
if (deviceCurrentReject$1[deviceId]) {
|
|
1585
1587
|
deviceCurrentReject$1[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
@@ -1620,7 +1622,7 @@ const promiseQueue = {
|
|
|
1620
1622
|
);
|
|
1621
1623
|
}
|
|
1622
1624
|
});
|
|
1623
|
-
return withTimeout(promise,
|
|
1625
|
+
return withTimeout(promise, 8e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
|
|
1624
1626
|
});
|
|
1625
1627
|
return devicePromiseQueue$1[device.id];
|
|
1626
1628
|
},
|
|
@@ -1993,7 +1995,7 @@ const bridgeCommands = {
|
|
|
1993
1995
|
async writeCommand(device, commandId, subCommandId, payload) {
|
|
1994
1996
|
const commandHead = this.getCommandHeader(device);
|
|
1995
1997
|
const macAddress = [0, 0, 0, 0, 0, 0];
|
|
1996
|
-
const timestamp =
|
|
1998
|
+
const timestamp = decimalToHex(Math.floor(Date.now() / 1e3));
|
|
1997
1999
|
const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16)).reverse();
|
|
1998
2000
|
if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
|
|
1999
2001
|
const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
|
|
@@ -2031,7 +2033,7 @@ const bridgeCommands = {
|
|
|
2031
2033
|
}
|
|
2032
2034
|
this.sendKeepAliveCommand(device);
|
|
2033
2035
|
}, 8e3);
|
|
2034
|
-
return promiseQueue.enqueue(device, writeCommand);
|
|
2036
|
+
return promiseQueue$1.enqueue(device, writeCommand);
|
|
2035
2037
|
}
|
|
2036
2038
|
};
|
|
2037
2039
|
|
|
@@ -2222,7 +2224,7 @@ function getVehicleAxlesTypes(tcVehicle) {
|
|
|
2222
2224
|
if (axle?.isDrive) {
|
|
2223
2225
|
binaryRepresentation |= 16;
|
|
2224
2226
|
}
|
|
2225
|
-
return
|
|
2227
|
+
return decimalToHex(binaryRepresentation);
|
|
2226
2228
|
});
|
|
2227
2229
|
return axleTypesBinary;
|
|
2228
2230
|
}
|
|
@@ -2377,7 +2379,7 @@ async function setVehicle(deviceId, tcVehicle) {
|
|
|
2377
2379
|
}
|
|
2378
2380
|
async function getSensorReading(deviceId, positionId) {
|
|
2379
2381
|
const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
|
|
2380
|
-
const sensorId = value.slice(5, 9).reverse().map((s) =>
|
|
2382
|
+
const sensorId = value.slice(5, 9).reverse().map((s) => decimalToHex(s).toUpperCase()).join("");
|
|
2381
2383
|
const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
|
|
2382
2384
|
return {
|
|
2383
2385
|
date: Date.now() - value[14] * 2e3,
|
|
@@ -2419,7 +2421,7 @@ async function getAutolearnStatuses(deviceId, tcVehicle) {
|
|
|
2419
2421
|
let autolearnedSensorId;
|
|
2420
2422
|
if (["1", "4", "9"].includes(tyreStatus)) {
|
|
2421
2423
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
|
|
2422
|
-
const axleData = axleInfo.map((i) =>
|
|
2424
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2423
2425
|
const joinedAxleData = Array.from(
|
|
2424
2426
|
{ length: axleData.length / 4 },
|
|
2425
2427
|
(_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
|
|
@@ -2629,7 +2631,7 @@ async function assignTyres(deviceId, tcVehicle) {
|
|
|
2629
2631
|
for (let index = 0; index < tcVehicle.axles.length; index++) {
|
|
2630
2632
|
const axle = tcVehicle.axles[index];
|
|
2631
2633
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
|
|
2632
|
-
const axleData = axleInfo.map((i) =>
|
|
2634
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2633
2635
|
const joinedAxleData = Array.from(
|
|
2634
2636
|
{ length: axleData.length / 4 },
|
|
2635
2637
|
(_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
|
|
@@ -2705,7 +2707,7 @@ const bridge = {
|
|
|
2705
2707
|
};
|
|
2706
2708
|
async function connect(deviceId, accessLevel) {
|
|
2707
2709
|
if (canCommunicateWith(deviceId)) return;
|
|
2708
|
-
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2710
|
+
await promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2709
2711
|
const bridgeMeta = deviceMeta.bridge;
|
|
2710
2712
|
await bluetooth.connect(deviceId, disconnect);
|
|
2711
2713
|
if (store.deviceState[deviceId] === "paired") {
|
|
@@ -2720,7 +2722,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2720
2722
|
_deviceId,
|
|
2721
2723
|
bridgeMeta.communication.serviceId,
|
|
2722
2724
|
bridgeMeta.communication.characteristicId,
|
|
2723
|
-
(notification) => promiseQueue.processMessage(deviceId, notification),
|
|
2725
|
+
(notification) => promiseQueue$1.processMessage(deviceId, notification),
|
|
2724
2726
|
(error) => console.error("startNotification Error", error)
|
|
2725
2727
|
);
|
|
2726
2728
|
store.deviceAccessLevel[deviceId] = accessLevel ?? "driver";
|
|
@@ -2734,7 +2736,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2734
2736
|
}
|
|
2735
2737
|
async function disconnect(deviceId, reason) {
|
|
2736
2738
|
store.setState(deviceId, "disconnecting");
|
|
2737
|
-
promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2739
|
+
promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2738
2740
|
await bluetooth.disconnect(deviceId);
|
|
2739
2741
|
delete store.devices[deviceId];
|
|
2740
2742
|
deviceUnreachableCallback?.(deviceId);
|
|
@@ -2765,10 +2767,118 @@ const simulatorSvc = {
|
|
|
2765
2767
|
}
|
|
2766
2768
|
};
|
|
2767
2769
|
|
|
2770
|
+
const devicePromiseQueue = {};
|
|
2771
|
+
const deviceCurrentResolve = {};
|
|
2772
|
+
const deviceCurrentReject = {};
|
|
2773
|
+
const deviceResponseIdentifier = {};
|
|
2774
|
+
const deviceCurrentPartialMessage = {};
|
|
2775
|
+
const promiseQueue = {
|
|
2776
|
+
clearQueue(deviceId, message) {
|
|
2777
|
+
if (deviceCurrentReject[deviceId]) {
|
|
2778
|
+
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2779
|
+
}
|
|
2780
|
+
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2781
|
+
deviceResponseIdentifier[deviceId] = void 0;
|
|
2782
|
+
},
|
|
2783
|
+
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2784
|
+
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2785
|
+
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2786
|
+
const promise = new Promise((resolve, reject) => {
|
|
2787
|
+
deviceCurrentResolve[deviceId] = resolve;
|
|
2788
|
+
deviceCurrentReject[deviceId] = reject;
|
|
2789
|
+
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2790
|
+
deviceCurrentPartialMessage[deviceId] = [];
|
|
2791
|
+
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2792
|
+
const chunks = [];
|
|
2793
|
+
for (let i = 0; i < payload.length; i += mtu) {
|
|
2794
|
+
chunks.push(payload.slice(i, i + mtu));
|
|
2795
|
+
}
|
|
2796
|
+
for (const chunk of chunks) {
|
|
2797
|
+
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2798
|
+
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2801
|
+
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2802
|
+
});
|
|
2803
|
+
return devicePromiseQueue[deviceId];
|
|
2804
|
+
},
|
|
2805
|
+
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2806
|
+
const numberArray = Array.from(new Uint8Array(payload));
|
|
2807
|
+
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2808
|
+
deviceCurrentPartialMessage[deviceId] = _.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2809
|
+
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
2813
|
+
return deviceCurrentReject[deviceId](
|
|
2814
|
+
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
2818
|
+
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
2819
|
+
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
|
|
2824
|
+
const flexiGaugeTpmsSecurity = {
|
|
2825
|
+
getCommandsSignature(seed) {
|
|
2826
|
+
const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.allLFCommands;
|
|
2827
|
+
if (!key) throw new Error("No Command key found");
|
|
2828
|
+
return aesToCrc32(seed, key);
|
|
2829
|
+
},
|
|
2830
|
+
getWriteSignature(seed) {
|
|
2831
|
+
const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.writeConfiguration;
|
|
2832
|
+
if (!key) throw new Error("No Write key found");
|
|
2833
|
+
return aesToCrc32(seed, key);
|
|
2834
|
+
},
|
|
2835
|
+
getCrc(message) {
|
|
2836
|
+
const messageHex = message.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? [];
|
|
2837
|
+
const crc = crc16CCITT(messageHex);
|
|
2838
|
+
return decimalToHex(crc, 4).toUpperCase();
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
function aesToCrc32(message, key) {
|
|
2842
|
+
const keyString = key.map((n) => decimalToHex(n)).join("");
|
|
2843
|
+
const messageHex = CryptoJs.enc.Hex.parse(message);
|
|
2844
|
+
const keyHex = CryptoJs.enc.Hex.parse(keyString);
|
|
2845
|
+
const encrypted = CryptoJs.AES.encrypt(messageHex, keyHex, {
|
|
2846
|
+
mode: CryptoJs.mode.ECB,
|
|
2847
|
+
padding: CryptoJs.pad.ZeroPadding
|
|
2848
|
+
});
|
|
2849
|
+
const encryptedByteString = CryptoJs.enc.Hex.stringify(encrypted.ciphertext).slice(0, 8).toUpperCase();
|
|
2850
|
+
return encryptedByteString;
|
|
2851
|
+
}
|
|
2852
|
+
function crc16CCITT(msg) {
|
|
2853
|
+
let crc = 65535;
|
|
2854
|
+
const polynomial = 4129;
|
|
2855
|
+
for (let i = 0; i < msg.length; i++) {
|
|
2856
|
+
const b = msg[i];
|
|
2857
|
+
crc ^= b << 8;
|
|
2858
|
+
for (let j = 0; j < 8; j++) {
|
|
2859
|
+
if (crc & 32768) {
|
|
2860
|
+
crc = crc << 1 ^ polynomial;
|
|
2861
|
+
} else {
|
|
2862
|
+
crc <<= 1;
|
|
2863
|
+
}
|
|
2864
|
+
crc &= 65535;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
return crc;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2768
2870
|
const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
|
|
2769
2871
|
const flexiGaugeTpmsCommands = {
|
|
2770
2872
|
startTpmsScan,
|
|
2771
|
-
getBattery
|
|
2873
|
+
getBattery,
|
|
2874
|
+
vdaRequestSensor,
|
|
2875
|
+
vdaRequestSeed,
|
|
2876
|
+
vdaSendWriteModePin,
|
|
2877
|
+
vdaReadConfiguration,
|
|
2878
|
+
vdaSendConfiguration,
|
|
2879
|
+
vdaRequestCommandsAccess,
|
|
2880
|
+
vdaRequestWriteAccess,
|
|
2881
|
+
processMessage
|
|
2772
2882
|
};
|
|
2773
2883
|
async function startTpmsScan(deviceId) {
|
|
2774
2884
|
if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
|
|
@@ -2789,23 +2899,92 @@ async function getBattery(deviceId) {
|
|
|
2789
2899
|
const battery = Array.from(new Uint8Array(batteryValue))[0];
|
|
2790
2900
|
return battery;
|
|
2791
2901
|
}
|
|
2902
|
+
async function vdaRequestSensor(deviceId) {
|
|
2903
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=1A";
|
|
2904
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2905
|
+
}
|
|
2906
|
+
async function vdaRequestSeed(deviceId, sensorId) {
|
|
2907
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2908
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=04 DATA=${sensorIdReversed}CE9A5713`;
|
|
2909
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2910
|
+
}
|
|
2911
|
+
async function vdaRequestCommandsAccess(deviceId, sensorId, signature) {
|
|
2912
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2913
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=${sensorIdReversed}${signature}`;
|
|
2914
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2915
|
+
}
|
|
2916
|
+
async function vdaRequestWriteAccess(deviceId, sensorId, signature) {
|
|
2917
|
+
const sensorIdReversed = getReversedSensorId(sensorId);
|
|
2918
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=02 DATA=${sensorIdReversed}${signature}`;
|
|
2919
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2920
|
+
}
|
|
2921
|
+
async function vdaSendWriteModePin(deviceId) {
|
|
2922
|
+
const scanMsg = stringToArrayBuffer("*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=FCCC71F6\r\n");
|
|
2923
|
+
await bluetooth.write(
|
|
2924
|
+
deviceId,
|
|
2925
|
+
flexiGaugeTpmsMeta.communication.serviceId,
|
|
2926
|
+
flexiGaugeTpmsMeta.communication.characteristicId,
|
|
2927
|
+
scanMsg
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
async function vdaReadConfiguration(deviceId) {
|
|
2931
|
+
const scanMsg = "*VDA_LF_SEND SID=A0 LID=DA PAR=17";
|
|
2932
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2933
|
+
}
|
|
2934
|
+
async function vdaSendConfiguration(deviceId, configuration) {
|
|
2935
|
+
if (configuration.length !== 60) throw new Error("Invalid sensor configuration");
|
|
2936
|
+
const crc = flexiGaugeTpmsSecurity.getCrc(configuration);
|
|
2937
|
+
const scanMsg = `*VDA_LF_SEND SID=A0 LID=EA PAR=17 DATA=${configuration}${crc}`;
|
|
2938
|
+
return await sendCommand(deviceId, scanMsg);
|
|
2939
|
+
}
|
|
2940
|
+
async function sendCommand(deviceId, command) {
|
|
2941
|
+
const message = `${command}\r
|
|
2942
|
+
`;
|
|
2943
|
+
const decimalArray = stringToDecimalArray(message);
|
|
2944
|
+
let result;
|
|
2945
|
+
try {
|
|
2946
|
+
result = await promiseQueue.enqueue(deviceId, flexiGaugeTpmsMeta.communication, decimalArray, "VDA");
|
|
2947
|
+
} catch (error) {
|
|
2948
|
+
flexiGaugeTpms.disconnect(deviceId, "lostConnection");
|
|
2949
|
+
throw error;
|
|
2950
|
+
}
|
|
2951
|
+
return result.map((num) => String.fromCharCode(num)).join("");
|
|
2952
|
+
}
|
|
2953
|
+
function processMessage(deviceId, payload) {
|
|
2954
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier$1, isComplete$1, isError$1);
|
|
2955
|
+
}
|
|
2956
|
+
function isComplete$1(message) {
|
|
2957
|
+
const completeCommand = message.at(-1) === 10;
|
|
2958
|
+
return completeCommand;
|
|
2959
|
+
}
|
|
2960
|
+
function isError$1(message) {
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
function getIdentifier$1(message) {
|
|
2964
|
+
const messageString = message.map((num) => String.fromCharCode(num)).join("");
|
|
2965
|
+
return messageString.slice(0, 3);
|
|
2966
|
+
}
|
|
2967
|
+
function getReversedSensorId(sensorId) {
|
|
2968
|
+
return sensorId.match(/.{1,2}/g).reverse().join("");
|
|
2969
|
+
}
|
|
2792
2970
|
|
|
2793
2971
|
let sensorReadings = [];
|
|
2794
2972
|
const capabilities$1 = [
|
|
2795
2973
|
{
|
|
2796
2974
|
id: "td",
|
|
2797
2975
|
processFn: processTreadDepth,
|
|
2798
|
-
regex:
|
|
2976
|
+
regex: /^\*?TD.*/
|
|
2977
|
+
// old fw sends * for some responses and new fw doesnt send * on any response
|
|
2799
2978
|
},
|
|
2800
2979
|
{
|
|
2801
2980
|
id: "button",
|
|
2802
2981
|
processFn: processButtonPress,
|
|
2803
|
-
regex:
|
|
2982
|
+
regex: /^\*?[UDLRC] $/
|
|
2804
2983
|
},
|
|
2805
2984
|
{
|
|
2806
2985
|
id: "tpms",
|
|
2807
2986
|
processFn: processTpms,
|
|
2808
|
-
regex:
|
|
2987
|
+
regex: /^\*?TPMS.*/
|
|
2809
2988
|
}
|
|
2810
2989
|
];
|
|
2811
2990
|
const flexiGaugeTpmsService = {
|
|
@@ -2833,7 +3012,9 @@ const flexiGaugeTpmsService = {
|
|
|
2833
3012
|
return capability.processFn(deviceId, stringFromBytes);
|
|
2834
3013
|
}
|
|
2835
3014
|
}
|
|
2836
|
-
|
|
3015
|
+
flexiGaugeTpmsCommands.processMessage(deviceId, message);
|
|
3016
|
+
},
|
|
3017
|
+
programSensor
|
|
2837
3018
|
};
|
|
2838
3019
|
function processTreadDepth(deviceId, value) {
|
|
2839
3020
|
const treadDepth = value.match(/\d+/)?.[0];
|
|
@@ -2872,6 +3053,44 @@ function processTpms(deviceId, value) {
|
|
|
2872
3053
|
simulatorSvc.triggerEvent("fg:tpms", deviceId, strongestReading);
|
|
2873
3054
|
}
|
|
2874
3055
|
}
|
|
3056
|
+
async function programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3057
|
+
await findSensor(deviceId, oldSensorId);
|
|
3058
|
+
const seed = await getSeed(deviceId, oldSensorId);
|
|
3059
|
+
const commandsSignature = await flexiGaugeTpmsSecurity.getCommandsSignature(seed);
|
|
3060
|
+
const writeSignature = await flexiGaugeTpmsSecurity.getWriteSignature(seed);
|
|
3061
|
+
await flexiGaugeTpmsCommands.vdaRequestCommandsAccess(deviceId, oldSensorId, commandsSignature);
|
|
3062
|
+
await flexiGaugeTpmsCommands.vdaRequestWriteAccess(deviceId, oldSensorId, writeSignature);
|
|
3063
|
+
const config = await getSensorConfiguration(deviceId);
|
|
3064
|
+
const newConfig = config.replace(/^.{8}/, newSensorId);
|
|
3065
|
+
await flexiGaugeTpmsCommands.vdaSendConfiguration(deviceId, newConfig);
|
|
3066
|
+
await findSensor(deviceId, newSensorId);
|
|
3067
|
+
}
|
|
3068
|
+
async function getSeed(deviceId, sensorId) {
|
|
3069
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSeed(deviceId, sensorId));
|
|
3070
|
+
return response[3];
|
|
3071
|
+
}
|
|
3072
|
+
async function findSensor(deviceId, sensorId) {
|
|
3073
|
+
let seen = 0;
|
|
3074
|
+
for (let i = 0; i < 13; i++) {
|
|
3075
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSensor(deviceId));
|
|
3076
|
+
if (response[2] === sensorId) seen++;
|
|
3077
|
+
if (seen === 3) return;
|
|
3078
|
+
}
|
|
3079
|
+
throw new Error(`Sensor ${sensorId} not found`);
|
|
3080
|
+
}
|
|
3081
|
+
async function getSensorConfiguration(deviceId) {
|
|
3082
|
+
const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaReadConfiguration(deviceId));
|
|
3083
|
+
return response[3].slice(2, -4);
|
|
3084
|
+
}
|
|
3085
|
+
async function vdaRepeat(fn) {
|
|
3086
|
+
for (let i = 0; i < 5; i++) {
|
|
3087
|
+
const response = await fn;
|
|
3088
|
+
const groupedValue = response.split(" ");
|
|
3089
|
+
if (groupedValue[1].includes("TIMEOUT")) continue;
|
|
3090
|
+
if (response) return groupedValue;
|
|
3091
|
+
}
|
|
3092
|
+
throw new Error("VDA Timeout");
|
|
3093
|
+
}
|
|
2875
3094
|
|
|
2876
3095
|
const flexiGaugeTpms = {
|
|
2877
3096
|
async connect(deviceId) {
|
|
@@ -2895,7 +3114,8 @@ const flexiGaugeTpms = {
|
|
|
2895
3114
|
onButtonPress: flexiGaugeTpmsService.onButton,
|
|
2896
3115
|
onTpms: flexiGaugeTpmsService.onTpms,
|
|
2897
3116
|
getBattery: flexiGaugeTpmsService.getBattery,
|
|
2898
|
-
startTpmsScan: flexiGaugeTpmsService.startTpmsScan
|
|
3117
|
+
startTpmsScan: flexiGaugeTpmsService.startTpmsScan,
|
|
3118
|
+
programSensor: flexiGaugeTpmsService.programSensor
|
|
2899
3119
|
};
|
|
2900
3120
|
|
|
2901
3121
|
const capabilities = [
|
|
@@ -2947,60 +3167,6 @@ const pressureStick = {
|
|
|
2947
3167
|
onPressure: pressureStickService.onPressure
|
|
2948
3168
|
};
|
|
2949
3169
|
|
|
2950
|
-
const devicePromiseQueue = {};
|
|
2951
|
-
const deviceCurrentResolve = {};
|
|
2952
|
-
const deviceCurrentReject = {};
|
|
2953
|
-
const deviceResponseIdentifier = {};
|
|
2954
|
-
const deviceCurrentPartialMessage = {};
|
|
2955
|
-
const torqueWrenchPromiseQueue = {
|
|
2956
|
-
clearQueue(deviceId, message) {
|
|
2957
|
-
if (deviceCurrentReject[deviceId]) {
|
|
2958
|
-
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2959
|
-
}
|
|
2960
|
-
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2961
|
-
deviceResponseIdentifier[deviceId] = void 0;
|
|
2962
|
-
},
|
|
2963
|
-
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2964
|
-
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2965
|
-
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2966
|
-
const promise = new Promise((resolve, reject) => {
|
|
2967
|
-
deviceCurrentResolve[deviceId] = resolve;
|
|
2968
|
-
deviceCurrentReject[deviceId] = reject;
|
|
2969
|
-
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2970
|
-
deviceCurrentPartialMessage[deviceId] = [];
|
|
2971
|
-
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2972
|
-
const chunks = [];
|
|
2973
|
-
for (let i = 0; i < payload.length; i += mtu) {
|
|
2974
|
-
chunks.push(payload.slice(i, i + mtu));
|
|
2975
|
-
}
|
|
2976
|
-
for (const chunk of chunks) {
|
|
2977
|
-
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2978
|
-
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2979
|
-
}
|
|
2980
|
-
});
|
|
2981
|
-
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2982
|
-
});
|
|
2983
|
-
return devicePromiseQueue[deviceId];
|
|
2984
|
-
},
|
|
2985
|
-
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2986
|
-
const numberArray = Array.from(new Uint8Array(payload));
|
|
2987
|
-
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2988
|
-
deviceCurrentPartialMessage[deviceId] = _.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2989
|
-
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2990
|
-
return;
|
|
2991
|
-
}
|
|
2992
|
-
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
2993
|
-
return deviceCurrentReject[deviceId](
|
|
2994
|
-
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
2995
|
-
);
|
|
2996
|
-
}
|
|
2997
|
-
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
2998
|
-
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
2999
|
-
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
};
|
|
3003
|
-
|
|
3004
3170
|
const commandStart = "$9";
|
|
3005
3171
|
const commandEnd = "*";
|
|
3006
3172
|
const commandIds = {
|
|
@@ -3060,16 +3226,15 @@ const torqueWrenchCommands = {
|
|
|
3060
3226
|
const decimalArray = Array.from(array);
|
|
3061
3227
|
let result;
|
|
3062
3228
|
try {
|
|
3063
|
-
result = await
|
|
3229
|
+
result = await promiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
|
|
3064
3230
|
} catch (error) {
|
|
3065
3231
|
const formattedError = formatError(error);
|
|
3066
|
-
torqueWrench.disconnect(deviceId, "lostConnection");
|
|
3067
3232
|
throw new Error(formattedError);
|
|
3068
3233
|
}
|
|
3069
3234
|
return result.map((num) => String.fromCharCode(num)).join("");
|
|
3070
3235
|
},
|
|
3071
3236
|
processMessage(deviceId, payload) {
|
|
3072
|
-
|
|
3237
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
|
|
3073
3238
|
}
|
|
3074
3239
|
};
|
|
3075
3240
|
function isComplete(message) {
|
|
@@ -3092,7 +3257,7 @@ function getChecksum(message) {
|
|
|
3092
3257
|
return decimalToHex(checksum).toUpperCase();
|
|
3093
3258
|
}
|
|
3094
3259
|
function formatError(error) {
|
|
3095
|
-
if (!error.cause) throw
|
|
3260
|
+
if (!error.cause) throw error;
|
|
3096
3261
|
const message = error.cause.slice(6, 8);
|
|
3097
3262
|
const errorId = message.map((num) => String.fromCharCode(num)).join("");
|
|
3098
3263
|
return errors[errorId] || "Unknown error";
|
|
@@ -3204,8 +3369,7 @@ const torqueWrenchService = {
|
|
|
3204
3369
|
},
|
|
3205
3370
|
async getTime(deviceId) {
|
|
3206
3371
|
const message = await torqueWrenchCommands.sendCommand(deviceId, "getTime");
|
|
3207
|
-
const match = message.match(/209,([^*]*)\*/);
|
|
3208
|
-
console.log("\u{1F680} ~ getTime ~ match:", match);
|
|
3372
|
+
const match = message.match(/209,([^*]*)\*/) ?? [];
|
|
3209
3373
|
if (!match[1]) throw new Error("Invalid time response");
|
|
3210
3374
|
const hh = match[1].substring(0, 2);
|
|
3211
3375
|
const mm = match[1].substring(2, 4);
|
|
@@ -3254,7 +3418,7 @@ const torqueWrench = {
|
|
|
3254
3418
|
async connect(deviceId) {
|
|
3255
3419
|
const twMeta = deviceMeta.torqueWrench;
|
|
3256
3420
|
await bluetooth.connect(deviceId, this.disconnect);
|
|
3257
|
-
await
|
|
3421
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3258
3422
|
await ble.startNotification(
|
|
3259
3423
|
deviceId,
|
|
3260
3424
|
twMeta.communication.serviceId,
|
|
@@ -3274,7 +3438,7 @@ const torqueWrench = {
|
|
|
3274
3438
|
await torqueWrenchService.stopJob(deviceId);
|
|
3275
3439
|
} catch {
|
|
3276
3440
|
}
|
|
3277
|
-
await
|
|
3441
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3278
3442
|
await bluetooth.disconnect(deviceId);
|
|
3279
3443
|
store.setState(deviceId, void 0, reason ?? "manualDisconnection");
|
|
3280
3444
|
},
|
|
@@ -3625,6 +3789,9 @@ const flexiGaugeTpmsSimulator = {
|
|
|
3625
3789
|
await toolsSvc.delay(200);
|
|
3626
3790
|
return fg.simulatorData.battery;
|
|
3627
3791
|
},
|
|
3792
|
+
programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3793
|
+
return new Promise((resolve) => resolve());
|
|
3794
|
+
},
|
|
3628
3795
|
onButtonPress(callback) {
|
|
3629
3796
|
},
|
|
3630
3797
|
onTpms(callback) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tirecheck-device-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
4
4
|
"description": "SDK for working with various devices produced by Tirecheck via Bluetooth (CAN Bridge, Routers, Sensors, FlexiGauge, PressureStick, etc)",
|
|
5
5
|
"author": "Leonid Buneev <leonid.buneev@tirecheck.com>",
|
|
6
6
|
"license": "ISC",
|