tirecheck-device-sdk 0.2.27 → 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 +268 -102
- 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 +268 -102
- 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;
|
|
@@ -1468,7 +1471,7 @@ const bridgeSecurity = {
|
|
|
1468
1471
|
const { randomAdvNumber, configVersion } = device?.advertisingData || {};
|
|
1469
1472
|
if (!randomAdvNumber || !_.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
|
|
1470
1473
|
const rand = _.clone(randomAdvNumber);
|
|
1471
|
-
const security_level =
|
|
1474
|
+
const security_level = decimalToHex(command[0]);
|
|
1472
1475
|
const keyIndex = Number(security_level[0]);
|
|
1473
1476
|
const keyShift = Number(security_level[1]);
|
|
1474
1477
|
let paddedCommand = [];
|
|
@@ -1483,8 +1486,8 @@ const bridgeSecurity = {
|
|
|
1483
1486
|
const key = store.securityKeys.bridge.signatureKeys[configVersion][keyIndex];
|
|
1484
1487
|
const startArray = key.slice(keyShift);
|
|
1485
1488
|
const endArray = key.slice(0, keyShift);
|
|
1486
|
-
const shiftedKey = startArray.concat(endArray).map((n) =>
|
|
1487
|
-
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("");
|
|
1488
1491
|
const keyWA = CryptoJs.enc.Hex.parse(shiftedKey);
|
|
1489
1492
|
const dataInWA = CryptoJs.enc.Hex.parse(dataIn);
|
|
1490
1493
|
const encrypted = CryptoJs.AES.encrypt(dataInWA, keyWA, {
|
|
@@ -1502,8 +1505,8 @@ const bridgeSecurity = {
|
|
|
1502
1505
|
for (let index2 = 0; index2 < block.length; index2++) {
|
|
1503
1506
|
xorBlock.push(block[index2] ^ blockVector[index2]);
|
|
1504
1507
|
}
|
|
1505
|
-
const vector = blockVector.map((x) =>
|
|
1506
|
-
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(""));
|
|
1507
1510
|
const testo = CryptoJs.AES.encrypt(dataWa, keyWA, {
|
|
1508
1511
|
iv: CryptoJs.enc.Hex.parse(vector),
|
|
1509
1512
|
mode: CryptoJs.mode.ECB,
|
|
@@ -1520,8 +1523,8 @@ const bridgeSecurity = {
|
|
|
1520
1523
|
if (deviceData?.type !== "bridge") throw new Error("Device data missing 1 ");
|
|
1521
1524
|
const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
|
|
1522
1525
|
if (!randomAdvNumber || !macAddress || !_.isNumber(configVersion)) throw new Error("Cannot compute pin");
|
|
1523
|
-
const mac = macAddress.map((n) =>
|
|
1524
|
-
const rand = randomAdvNumber.map((n) =>
|
|
1526
|
+
const mac = macAddress.map((n) => decimalToHex(n)).join("");
|
|
1527
|
+
const rand = randomAdvNumber.map((n) => decimalToHex(n)).join("");
|
|
1525
1528
|
const message = `${mac}0000${rand}`;
|
|
1526
1529
|
if (!store.securityKeys?.bridge?.pinKeys?.[configVersion]) throw new Error("No pin key found");
|
|
1527
1530
|
const key = store.securityKeys?.bridge?.pinKeys[configVersion];
|
|
@@ -1533,9 +1536,7 @@ const bridgeSecurity = {
|
|
|
1533
1536
|
const keyHex = CryptoJs.enc.Hex.parse(key);
|
|
1534
1537
|
const encrypted = CryptoJs.AES.encrypt(messageHex, keyHex, {
|
|
1535
1538
|
mode: CryptoJs.mode.ECB,
|
|
1536
|
-
// You can use other modes like CBC or CTR for additional security
|
|
1537
1539
|
padding: CryptoJs.pad.ZeroPadding
|
|
1538
|
-
// You can use other padding modes as well
|
|
1539
1540
|
});
|
|
1540
1541
|
const encryptedByteString = CryptoJs.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1541
1542
|
const hexArray = this.encryptedStringToNum(encryptedByteString);
|
|
@@ -1562,7 +1563,7 @@ const bridgeSecurity = {
|
|
|
1562
1563
|
},
|
|
1563
1564
|
getCrcArray(msg) {
|
|
1564
1565
|
const crc = this.crc32mpeg2(msg);
|
|
1565
|
-
const crcHex =
|
|
1566
|
+
const crcHex = decimalToHex(crc).padStart(8, "0");
|
|
1566
1567
|
return bridgeTools.hexToDecimalArray(crcHex).reverse();
|
|
1567
1568
|
},
|
|
1568
1569
|
uint32ToLittleEndian(value) {
|
|
@@ -1580,7 +1581,7 @@ const devicePromiseQueue$1 = {};
|
|
|
1580
1581
|
const deviceCurrentResolve$1 = {};
|
|
1581
1582
|
const deviceCurrentReject$1 = {};
|
|
1582
1583
|
const deviceResponseIdentifier$1 = {};
|
|
1583
|
-
const promiseQueue = {
|
|
1584
|
+
const promiseQueue$1 = {
|
|
1584
1585
|
clearQueue(deviceId, message) {
|
|
1585
1586
|
if (deviceCurrentReject$1[deviceId]) {
|
|
1586
1587
|
deviceCurrentReject$1[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
@@ -1621,7 +1622,7 @@ const promiseQueue = {
|
|
|
1621
1622
|
);
|
|
1622
1623
|
}
|
|
1623
1624
|
});
|
|
1624
|
-
return withTimeout(promise,
|
|
1625
|
+
return withTimeout(promise, 8e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
|
|
1625
1626
|
});
|
|
1626
1627
|
return devicePromiseQueue$1[device.id];
|
|
1627
1628
|
},
|
|
@@ -1994,7 +1995,7 @@ const bridgeCommands = {
|
|
|
1994
1995
|
async writeCommand(device, commandId, subCommandId, payload) {
|
|
1995
1996
|
const commandHead = this.getCommandHeader(device);
|
|
1996
1997
|
const macAddress = [0, 0, 0, 0, 0, 0];
|
|
1997
|
-
const timestamp =
|
|
1998
|
+
const timestamp = decimalToHex(Math.floor(Date.now() / 1e3));
|
|
1998
1999
|
const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16)).reverse();
|
|
1999
2000
|
if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
|
|
2000
2001
|
const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
|
|
@@ -2032,7 +2033,7 @@ const bridgeCommands = {
|
|
|
2032
2033
|
}
|
|
2033
2034
|
this.sendKeepAliveCommand(device);
|
|
2034
2035
|
}, 8e3);
|
|
2035
|
-
return promiseQueue.enqueue(device, writeCommand);
|
|
2036
|
+
return promiseQueue$1.enqueue(device, writeCommand);
|
|
2036
2037
|
}
|
|
2037
2038
|
};
|
|
2038
2039
|
|
|
@@ -2223,7 +2224,7 @@ function getVehicleAxlesTypes(tcVehicle) {
|
|
|
2223
2224
|
if (axle?.isDrive) {
|
|
2224
2225
|
binaryRepresentation |= 16;
|
|
2225
2226
|
}
|
|
2226
|
-
return
|
|
2227
|
+
return decimalToHex(binaryRepresentation);
|
|
2227
2228
|
});
|
|
2228
2229
|
return axleTypesBinary;
|
|
2229
2230
|
}
|
|
@@ -2378,7 +2379,7 @@ async function setVehicle(deviceId, tcVehicle) {
|
|
|
2378
2379
|
}
|
|
2379
2380
|
async function getSensorReading(deviceId, positionId) {
|
|
2380
2381
|
const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
|
|
2381
|
-
const sensorId = value.slice(5, 9).reverse().map((s) =>
|
|
2382
|
+
const sensorId = value.slice(5, 9).reverse().map((s) => decimalToHex(s).toUpperCase()).join("");
|
|
2382
2383
|
const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
|
|
2383
2384
|
return {
|
|
2384
2385
|
date: Date.now() - value[14] * 2e3,
|
|
@@ -2420,7 +2421,7 @@ async function getAutolearnStatuses(deviceId, tcVehicle) {
|
|
|
2420
2421
|
let autolearnedSensorId;
|
|
2421
2422
|
if (["1", "4", "9"].includes(tyreStatus)) {
|
|
2422
2423
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
|
|
2423
|
-
const axleData = axleInfo.map((i) =>
|
|
2424
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2424
2425
|
const joinedAxleData = Array.from(
|
|
2425
2426
|
{ length: axleData.length / 4 },
|
|
2426
2427
|
(_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
|
|
@@ -2630,7 +2631,7 @@ async function assignTyres(deviceId, tcVehicle) {
|
|
|
2630
2631
|
for (let index = 0; index < tcVehicle.axles.length; index++) {
|
|
2631
2632
|
const axle = tcVehicle.axles[index];
|
|
2632
2633
|
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
|
|
2633
|
-
const axleData = axleInfo.map((i) =>
|
|
2634
|
+
const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
|
|
2634
2635
|
const joinedAxleData = Array.from(
|
|
2635
2636
|
{ length: axleData.length / 4 },
|
|
2636
2637
|
(_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
|
|
@@ -2706,7 +2707,7 @@ const bridge = {
|
|
|
2706
2707
|
};
|
|
2707
2708
|
async function connect(deviceId, accessLevel) {
|
|
2708
2709
|
if (canCommunicateWith(deviceId)) return;
|
|
2709
|
-
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2710
|
+
await promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2710
2711
|
const bridgeMeta = deviceMeta.bridge;
|
|
2711
2712
|
await bluetooth.connect(deviceId, disconnect);
|
|
2712
2713
|
if (store.deviceState[deviceId] === "paired") {
|
|
@@ -2721,7 +2722,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2721
2722
|
_deviceId,
|
|
2722
2723
|
bridgeMeta.communication.serviceId,
|
|
2723
2724
|
bridgeMeta.communication.characteristicId,
|
|
2724
|
-
(notification) => promiseQueue.processMessage(deviceId, notification),
|
|
2725
|
+
(notification) => promiseQueue$1.processMessage(deviceId, notification),
|
|
2725
2726
|
(error) => console.error("startNotification Error", error)
|
|
2726
2727
|
);
|
|
2727
2728
|
store.deviceAccessLevel[deviceId] = accessLevel ?? "driver";
|
|
@@ -2735,7 +2736,7 @@ async function connect(deviceId, accessLevel) {
|
|
|
2735
2736
|
}
|
|
2736
2737
|
async function disconnect(deviceId, reason) {
|
|
2737
2738
|
store.setState(deviceId, "disconnecting");
|
|
2738
|
-
promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2739
|
+
promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
|
|
2739
2740
|
await bluetooth.disconnect(deviceId);
|
|
2740
2741
|
delete store.devices[deviceId];
|
|
2741
2742
|
deviceUnreachableCallback?.(deviceId);
|
|
@@ -2766,10 +2767,118 @@ const simulatorSvc = {
|
|
|
2766
2767
|
}
|
|
2767
2768
|
};
|
|
2768
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
|
+
|
|
2769
2870
|
const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
|
|
2770
2871
|
const flexiGaugeTpmsCommands = {
|
|
2771
2872
|
startTpmsScan,
|
|
2772
|
-
getBattery
|
|
2873
|
+
getBattery,
|
|
2874
|
+
vdaRequestSensor,
|
|
2875
|
+
vdaRequestSeed,
|
|
2876
|
+
vdaSendWriteModePin,
|
|
2877
|
+
vdaReadConfiguration,
|
|
2878
|
+
vdaSendConfiguration,
|
|
2879
|
+
vdaRequestCommandsAccess,
|
|
2880
|
+
vdaRequestWriteAccess,
|
|
2881
|
+
processMessage
|
|
2773
2882
|
};
|
|
2774
2883
|
async function startTpmsScan(deviceId) {
|
|
2775
2884
|
if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
|
|
@@ -2790,23 +2899,92 @@ async function getBattery(deviceId) {
|
|
|
2790
2899
|
const battery = Array.from(new Uint8Array(batteryValue))[0];
|
|
2791
2900
|
return battery;
|
|
2792
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
|
+
}
|
|
2793
2970
|
|
|
2794
2971
|
let sensorReadings = [];
|
|
2795
2972
|
const capabilities$1 = [
|
|
2796
2973
|
{
|
|
2797
2974
|
id: "td",
|
|
2798
2975
|
processFn: processTreadDepth,
|
|
2799
|
-
regex:
|
|
2976
|
+
regex: /^\*?TD.*/
|
|
2977
|
+
// old fw sends * for some responses and new fw doesnt send * on any response
|
|
2800
2978
|
},
|
|
2801
2979
|
{
|
|
2802
2980
|
id: "button",
|
|
2803
2981
|
processFn: processButtonPress,
|
|
2804
|
-
regex:
|
|
2982
|
+
regex: /^\*?[UDLRC] $/
|
|
2805
2983
|
},
|
|
2806
2984
|
{
|
|
2807
2985
|
id: "tpms",
|
|
2808
2986
|
processFn: processTpms,
|
|
2809
|
-
regex:
|
|
2987
|
+
regex: /^\*?TPMS.*/
|
|
2810
2988
|
}
|
|
2811
2989
|
];
|
|
2812
2990
|
const flexiGaugeTpmsService = {
|
|
@@ -2834,7 +3012,9 @@ const flexiGaugeTpmsService = {
|
|
|
2834
3012
|
return capability.processFn(deviceId, stringFromBytes);
|
|
2835
3013
|
}
|
|
2836
3014
|
}
|
|
2837
|
-
|
|
3015
|
+
flexiGaugeTpmsCommands.processMessage(deviceId, message);
|
|
3016
|
+
},
|
|
3017
|
+
programSensor
|
|
2838
3018
|
};
|
|
2839
3019
|
function processTreadDepth(deviceId, value) {
|
|
2840
3020
|
const treadDepth = value.match(/\d+/)?.[0];
|
|
@@ -2873,6 +3053,44 @@ function processTpms(deviceId, value) {
|
|
|
2873
3053
|
simulatorSvc.triggerEvent("fg:tpms", deviceId, strongestReading);
|
|
2874
3054
|
}
|
|
2875
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
|
+
}
|
|
2876
3094
|
|
|
2877
3095
|
const flexiGaugeTpms = {
|
|
2878
3096
|
async connect(deviceId) {
|
|
@@ -2896,7 +3114,8 @@ const flexiGaugeTpms = {
|
|
|
2896
3114
|
onButtonPress: flexiGaugeTpmsService.onButton,
|
|
2897
3115
|
onTpms: flexiGaugeTpmsService.onTpms,
|
|
2898
3116
|
getBattery: flexiGaugeTpmsService.getBattery,
|
|
2899
|
-
startTpmsScan: flexiGaugeTpmsService.startTpmsScan
|
|
3117
|
+
startTpmsScan: flexiGaugeTpmsService.startTpmsScan,
|
|
3118
|
+
programSensor: flexiGaugeTpmsService.programSensor
|
|
2900
3119
|
};
|
|
2901
3120
|
|
|
2902
3121
|
const capabilities = [
|
|
@@ -2948,60 +3167,6 @@ const pressureStick = {
|
|
|
2948
3167
|
onPressure: pressureStickService.onPressure
|
|
2949
3168
|
};
|
|
2950
3169
|
|
|
2951
|
-
const devicePromiseQueue = {};
|
|
2952
|
-
const deviceCurrentResolve = {};
|
|
2953
|
-
const deviceCurrentReject = {};
|
|
2954
|
-
const deviceResponseIdentifier = {};
|
|
2955
|
-
const deviceCurrentPartialMessage = {};
|
|
2956
|
-
const torqueWrenchPromiseQueue = {
|
|
2957
|
-
clearQueue(deviceId, message) {
|
|
2958
|
-
if (deviceCurrentReject[deviceId]) {
|
|
2959
|
-
deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
|
|
2960
|
-
}
|
|
2961
|
-
devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2962
|
-
deviceResponseIdentifier[deviceId] = void 0;
|
|
2963
|
-
},
|
|
2964
|
-
async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
|
|
2965
|
-
if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
|
|
2966
|
-
devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
|
|
2967
|
-
const promise = new Promise((resolve, reject) => {
|
|
2968
|
-
deviceCurrentResolve[deviceId] = resolve;
|
|
2969
|
-
deviceCurrentReject[deviceId] = reject;
|
|
2970
|
-
deviceResponseIdentifier[deviceId] = responseIdentifier;
|
|
2971
|
-
deviceCurrentPartialMessage[deviceId] = [];
|
|
2972
|
-
if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
|
|
2973
|
-
const chunks = [];
|
|
2974
|
-
for (let i = 0; i < payload.length; i += mtu) {
|
|
2975
|
-
chunks.push(payload.slice(i, i + mtu));
|
|
2976
|
-
}
|
|
2977
|
-
for (const chunk of chunks) {
|
|
2978
|
-
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
2979
|
-
bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
|
|
2980
|
-
}
|
|
2981
|
-
});
|
|
2982
|
-
return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
|
|
2983
|
-
});
|
|
2984
|
-
return devicePromiseQueue[deviceId];
|
|
2985
|
-
},
|
|
2986
|
-
async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
|
|
2987
|
-
const numberArray = Array.from(new Uint8Array(payload));
|
|
2988
|
-
if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
|
|
2989
|
-
deviceCurrentPartialMessage[deviceId] = _.concat(deviceCurrentPartialMessage[deviceId], numberArray);
|
|
2990
|
-
if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
|
|
2991
|
-
return;
|
|
2992
|
-
}
|
|
2993
|
-
if (isError(deviceCurrentPartialMessage[deviceId])) {
|
|
2994
|
-
return deviceCurrentReject[deviceId](
|
|
2995
|
-
new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
|
|
2996
|
-
);
|
|
2997
|
-
}
|
|
2998
|
-
const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
|
|
2999
|
-
if (deviceResponseIdentifier[deviceId] === identifier) {
|
|
3000
|
-
deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
};
|
|
3004
|
-
|
|
3005
3170
|
const commandStart = "$9";
|
|
3006
3171
|
const commandEnd = "*";
|
|
3007
3172
|
const commandIds = {
|
|
@@ -3061,16 +3226,15 @@ const torqueWrenchCommands = {
|
|
|
3061
3226
|
const decimalArray = Array.from(array);
|
|
3062
3227
|
let result;
|
|
3063
3228
|
try {
|
|
3064
|
-
result = await
|
|
3229
|
+
result = await promiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
|
|
3065
3230
|
} catch (error) {
|
|
3066
3231
|
const formattedError = formatError(error);
|
|
3067
|
-
torqueWrench.disconnect(deviceId, "lostConnection");
|
|
3068
3232
|
throw new Error(formattedError);
|
|
3069
3233
|
}
|
|
3070
3234
|
return result.map((num) => String.fromCharCode(num)).join("");
|
|
3071
3235
|
},
|
|
3072
3236
|
processMessage(deviceId, payload) {
|
|
3073
|
-
|
|
3237
|
+
promiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
|
|
3074
3238
|
}
|
|
3075
3239
|
};
|
|
3076
3240
|
function isComplete(message) {
|
|
@@ -3093,7 +3257,7 @@ function getChecksum(message) {
|
|
|
3093
3257
|
return decimalToHex(checksum).toUpperCase();
|
|
3094
3258
|
}
|
|
3095
3259
|
function formatError(error) {
|
|
3096
|
-
if (!error.cause) throw
|
|
3260
|
+
if (!error.cause) throw error;
|
|
3097
3261
|
const message = error.cause.slice(6, 8);
|
|
3098
3262
|
const errorId = message.map((num) => String.fromCharCode(num)).join("");
|
|
3099
3263
|
return errors[errorId] || "Unknown error";
|
|
@@ -3205,8 +3369,7 @@ const torqueWrenchService = {
|
|
|
3205
3369
|
},
|
|
3206
3370
|
async getTime(deviceId) {
|
|
3207
3371
|
const message = await torqueWrenchCommands.sendCommand(deviceId, "getTime");
|
|
3208
|
-
const match = message.match(/209,([^*]*)\*/);
|
|
3209
|
-
console.log("\u{1F680} ~ getTime ~ match:", match);
|
|
3372
|
+
const match = message.match(/209,([^*]*)\*/) ?? [];
|
|
3210
3373
|
if (!match[1]) throw new Error("Invalid time response");
|
|
3211
3374
|
const hh = match[1].substring(0, 2);
|
|
3212
3375
|
const mm = match[1].substring(2, 4);
|
|
@@ -3255,7 +3418,7 @@ const torqueWrench = {
|
|
|
3255
3418
|
async connect(deviceId) {
|
|
3256
3419
|
const twMeta = deviceMeta.torqueWrench;
|
|
3257
3420
|
await bluetooth.connect(deviceId, this.disconnect);
|
|
3258
|
-
await
|
|
3421
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3259
3422
|
await ble.startNotification(
|
|
3260
3423
|
deviceId,
|
|
3261
3424
|
twMeta.communication.serviceId,
|
|
@@ -3275,7 +3438,7 @@ const torqueWrench = {
|
|
|
3275
3438
|
await torqueWrenchService.stopJob(deviceId);
|
|
3276
3439
|
} catch {
|
|
3277
3440
|
}
|
|
3278
|
-
await
|
|
3441
|
+
await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
|
|
3279
3442
|
await bluetooth.disconnect(deviceId);
|
|
3280
3443
|
store.setState(deviceId, void 0, reason ?? "manualDisconnection");
|
|
3281
3444
|
},
|
|
@@ -3626,6 +3789,9 @@ const flexiGaugeTpmsSimulator = {
|
|
|
3626
3789
|
await toolsSvc.delay(200);
|
|
3627
3790
|
return fg.simulatorData.battery;
|
|
3628
3791
|
},
|
|
3792
|
+
programSensor(deviceId, oldSensorId, newSensorId) {
|
|
3793
|
+
return new Promise((resolve) => resolve());
|
|
3794
|
+
},
|
|
3629
3795
|
onButtonPress(callback) {
|
|
3630
3796
|
},
|
|
3631
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",
|