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.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("Device data missing");
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) => this.decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
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) => this.decimalToHex(dec).split("").reverse().join("")).join("");
195
+ return _reversedData.map((dec) => decimalToHex(dec).split("").reverse().join("")).join("");
189
196
  }
190
- return _reversedData.map((dec) => this.decimalToHex(dec)).join("");
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(this.decimalToHex(_data)).reverse();
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 = this.decimalToHex(current);
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) => bridgeTools.decimalToHex(n).toUpperCase()).reverse() || [];
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) => bridgeTools.decimalToHex(x).toUpperCase());
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) => bridgeTools.decimalToHex(x).toUpperCase());
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 = bridgeTools.decimalToHex(command[0]);
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) => bridgeTools.decimalToHex(n)).join("");
1487
- const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => bridgeTools.decimalToHex(x)).join("");
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) => bridgeTools.decimalToHex(x)).join("");
1506
- const dataWa = CryptoJs.enc.Hex.parse(xorBlock.map((x) => bridgeTools.decimalToHex(x)).join(""));
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) => bridgeTools.decimalToHex(n)).join("");
1524
- const rand = randomAdvNumber.map((n) => bridgeTools.decimalToHex(n)).join("");
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 = bridgeTools.decimalToHex(crc).padStart(8, "0");
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, 5e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
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 = bridgeTools.decimalToHex(Math.floor(Date.now() / 1e3));
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 bridgeTools.decimalToHex(binaryRepresentation);
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) => bridgeTools.decimalToHex(s).toUpperCase()).join("");
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) => bridgeTools.decimalToHex(i).toUpperCase());
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) => bridgeTools.decimalToHex(i).toUpperCase());
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: /^\*TD.*/
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: /^\*[UDLRC] $/
2982
+ regex: /^\*?[UDLRC] $/
2805
2983
  },
2806
2984
  {
2807
2985
  id: "tpms",
2808
2986
  processFn: processTpms,
2809
- regex: /^\*TPMS.*/
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 torqueWrenchPromiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
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
- torqueWrenchPromiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
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 new Error(error);
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 torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
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 torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
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.27",
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",