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.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;
@@ -536,7 +539,8 @@ const deviceMeta = {
536
539
  // },
537
540
  // },
538
541
  flexiGaugeTpms: {
539
- nameRegex: /^Flexi.*TPMS.*/,
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 = bridgeTools.decimalToHex(command[0]);
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) => bridgeTools.decimalToHex(n)).join("");
1486
- 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("");
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) => bridgeTools.decimalToHex(x)).join("");
1505
- 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(""));
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) => bridgeTools.decimalToHex(n)).join("");
1523
- 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("");
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 = bridgeTools.decimalToHex(crc).padStart(8, "0");
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, 5e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
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 = bridgeTools.decimalToHex(Math.floor(Date.now() / 1e3));
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 bridgeTools.decimalToHex(binaryRepresentation);
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) => bridgeTools.decimalToHex(s).toUpperCase()).join("");
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) => bridgeTools.decimalToHex(i).toUpperCase());
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) => bridgeTools.decimalToHex(i).toUpperCase());
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: /^\*TD.*/
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: /^\*[UDLRC] $/
2982
+ regex: /^\*?[UDLRC] $/
2804
2983
  },
2805
2984
  {
2806
2985
  id: "tpms",
2807
2986
  processFn: processTpms,
2808
- regex: /^\*TPMS.*/
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 torqueWrenchPromiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
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
- torqueWrenchPromiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
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 new Error(error);
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 torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
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 torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
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.26",
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",