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 CHANGED
@@ -106,6 +106,13 @@ function stringToArrayBuffer(string) {
106
106
  }
107
107
  return array.buffer;
108
108
  }
109
+ function stringToDecimalArray(string) {
110
+ const array = new Uint8Array(string.length);
111
+ for (let i = 0, l = string.length; i < l; i++) {
112
+ array[i] = string.charCodeAt(i);
113
+ }
114
+ return Array.from(array);
115
+ }
109
116
  function decimalToHex(decimal, padStart = 2) {
110
117
  const hex = decimal.toString(16);
111
118
  return hex.padStart(padStart, "0");
@@ -127,7 +134,7 @@ const tyreLabels = getDefaultTyreLabels();
127
134
  const bridgeTools = {
128
135
  getBridgeFromStore(deviceId) {
129
136
  const deviceData = store.devices[deviceId];
130
- if (!deviceData) throw new Error("Device data missing");
137
+ if (!deviceData) throw new Error(`No device data found: ${deviceId}`);
131
138
  if (deviceData.type !== "bridge") throw new Error("Device is not bridge");
132
139
  return deviceData;
133
140
  },
@@ -189,12 +196,12 @@ const bridgeTools = {
189
196
  }
190
197
  const _reversedData = _data.reverse();
191
198
  if (displayUnits === "decimal") {
192
- return Number(`0x${_reversedData.map((dec) => this.decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
199
+ return Number(`0x${_reversedData.map((dec) => decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
193
200
  }
194
201
  if (displayUnits === "reverseHex") {
195
- return _reversedData.map((dec) => this.decimalToHex(dec).split("").reverse().join("")).join("");
202
+ return _reversedData.map((dec) => decimalToHex(dec).split("").reverse().join("")).join("");
196
203
  }
197
- return _reversedData.map((dec) => this.decimalToHex(dec)).join("");
204
+ return _reversedData.map((dec) => decimalToHex(dec)).join("");
198
205
  },
199
206
  encodeData(data, displayUnits) {
200
207
  const _data = ___default.clone(data);
@@ -202,7 +209,7 @@ const bridgeTools = {
202
209
  return this.asciiToDecimalArray(_data);
203
210
  }
204
211
  if (displayUnits === "decimal") {
205
- return this.hexToDecimalArray(this.decimalToHex(_data)).reverse();
212
+ return this.hexToDecimalArray(decimalToHex(_data)).reverse();
206
213
  }
207
214
  if (displayUnits === "reverseHex") {
208
215
  const byteArray = _data.match(/.{1,2}/g) || [];
@@ -218,10 +225,6 @@ const bridgeTools = {
218
225
  }
219
226
  return array;
220
227
  },
221
- decimalToHex(decimal) {
222
- const hex = decimal.toString(16);
223
- return hex.padStart(2, "0");
224
- },
225
228
  asciiToDecimalArray(ascii) {
226
229
  return ascii.split("").map((x) => x.charCodeAt(0));
227
230
  },
@@ -234,7 +237,7 @@ const bridgeTools = {
234
237
  return "";
235
238
  }
236
239
  return payload.reduce((acc, current) => {
237
- const value = this.decimalToHex(current);
240
+ const value = decimalToHex(current);
238
241
  acc.push(value[0] === "0" ? value[1] : value);
239
242
  return acc;
240
243
  }, []).join(".");
@@ -356,7 +359,7 @@ const bridgeAdvertisingParser = {
356
359
  if (adversitingType !== "connectable") return;
357
360
  const advertisingData = getAdvertisingData({ advertising: device.advertising, deviceName: device.name });
358
361
  if (!advertisingData) return;
359
- const macArray = advertisingData?.macAddress?.map((n) => bridgeTools.decimalToHex(n).toUpperCase()).reverse() || [];
362
+ const macArray = advertisingData?.macAddress?.map((n) => decimalToHex(n).toUpperCase()).reverse() || [];
360
363
  const bridgeId = macArray.join("");
361
364
  const vin = String.fromCharCode(...advertisingData?.vinNum || []).split("\0").join("");
362
365
  const macString = macArray.join(":");
@@ -472,7 +475,7 @@ const bridgeOtaAdvertisingParser = {
472
475
  let processedByteCount = 0;
473
476
  if (store.platform === "ios") {
474
477
  if (!device.advertising.kCBAdvDataLeBluetoothDeviceAddress) return void 0;
475
- const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) => bridgeTools.decimalToHex(x).toUpperCase());
478
+ const macAddress = Array.from(new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)).slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
476
479
  deviceId = macAddress.join(":");
477
480
  bridgeId = macAddress.join("");
478
481
  } else {
@@ -485,7 +488,7 @@ const bridgeOtaAdvertisingParser = {
485
488
  }
486
489
  processedByteCount += length + 1;
487
490
  if (identificator === 27) {
488
- const macAddress = packet.slice(-6).reverse().map((x) => bridgeTools.decimalToHex(x).toUpperCase());
491
+ const macAddress = packet.slice(-6).reverse().map((x) => decimalToHex(x).toUpperCase());
489
492
  deviceId = macAddress.join(":");
490
493
  bridgeId = macAddress.join("");
491
494
  processedByteCount = adv.length;
@@ -1475,7 +1478,7 @@ const bridgeSecurity = {
1475
1478
  const { randomAdvNumber, configVersion } = device?.advertisingData || {};
1476
1479
  if (!randomAdvNumber || !___default.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
1477
1480
  const rand = ___default.clone(randomAdvNumber);
1478
- const security_level = bridgeTools.decimalToHex(command[0]);
1481
+ const security_level = decimalToHex(command[0]);
1479
1482
  const keyIndex = Number(security_level[0]);
1480
1483
  const keyShift = Number(security_level[1]);
1481
1484
  let paddedCommand = [];
@@ -1490,8 +1493,8 @@ const bridgeSecurity = {
1490
1493
  const key = store.securityKeys.bridge.signatureKeys[configVersion][keyIndex];
1491
1494
  const startArray = key.slice(keyShift);
1492
1495
  const endArray = key.slice(0, keyShift);
1493
- const shiftedKey = startArray.concat(endArray).map((n) => bridgeTools.decimalToHex(n)).join("");
1494
- const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => bridgeTools.decimalToHex(x)).join("");
1496
+ const shiftedKey = startArray.concat(endArray).map((n) => decimalToHex(n)).join("");
1497
+ const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => decimalToHex(x)).join("");
1495
1498
  const keyWA = CryptoJs__default.enc.Hex.parse(shiftedKey);
1496
1499
  const dataInWA = CryptoJs__default.enc.Hex.parse(dataIn);
1497
1500
  const encrypted = CryptoJs__default.AES.encrypt(dataInWA, keyWA, {
@@ -1509,8 +1512,8 @@ const bridgeSecurity = {
1509
1512
  for (let index2 = 0; index2 < block.length; index2++) {
1510
1513
  xorBlock.push(block[index2] ^ blockVector[index2]);
1511
1514
  }
1512
- const vector = blockVector.map((x) => bridgeTools.decimalToHex(x)).join("");
1513
- const dataWa = CryptoJs__default.enc.Hex.parse(xorBlock.map((x) => bridgeTools.decimalToHex(x)).join(""));
1515
+ const vector = blockVector.map((x) => decimalToHex(x)).join("");
1516
+ const dataWa = CryptoJs__default.enc.Hex.parse(xorBlock.map((x) => decimalToHex(x)).join(""));
1514
1517
  const testo = CryptoJs__default.AES.encrypt(dataWa, keyWA, {
1515
1518
  iv: CryptoJs__default.enc.Hex.parse(vector),
1516
1519
  mode: CryptoJs__default.mode.ECB,
@@ -1527,8 +1530,8 @@ const bridgeSecurity = {
1527
1530
  if (deviceData?.type !== "bridge") throw new Error("Device data missing 1 ");
1528
1531
  const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
1529
1532
  if (!randomAdvNumber || !macAddress || !___default.isNumber(configVersion)) throw new Error("Cannot compute pin");
1530
- const mac = macAddress.map((n) => bridgeTools.decimalToHex(n)).join("");
1531
- const rand = randomAdvNumber.map((n) => bridgeTools.decimalToHex(n)).join("");
1533
+ const mac = macAddress.map((n) => decimalToHex(n)).join("");
1534
+ const rand = randomAdvNumber.map((n) => decimalToHex(n)).join("");
1532
1535
  const message = `${mac}0000${rand}`;
1533
1536
  if (!store.securityKeys?.bridge?.pinKeys?.[configVersion]) throw new Error("No pin key found");
1534
1537
  const key = store.securityKeys?.bridge?.pinKeys[configVersion];
@@ -1540,9 +1543,7 @@ const bridgeSecurity = {
1540
1543
  const keyHex = CryptoJs__default.enc.Hex.parse(key);
1541
1544
  const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
1542
1545
  mode: CryptoJs__default.mode.ECB,
1543
- // You can use other modes like CBC or CTR for additional security
1544
1546
  padding: CryptoJs__default.pad.ZeroPadding
1545
- // You can use other padding modes as well
1546
1547
  });
1547
1548
  const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
1548
1549
  const hexArray = this.encryptedStringToNum(encryptedByteString);
@@ -1569,7 +1570,7 @@ const bridgeSecurity = {
1569
1570
  },
1570
1571
  getCrcArray(msg) {
1571
1572
  const crc = this.crc32mpeg2(msg);
1572
- const crcHex = bridgeTools.decimalToHex(crc).padStart(8, "0");
1573
+ const crcHex = decimalToHex(crc).padStart(8, "0");
1573
1574
  return bridgeTools.hexToDecimalArray(crcHex).reverse();
1574
1575
  },
1575
1576
  uint32ToLittleEndian(value) {
@@ -1587,7 +1588,7 @@ const devicePromiseQueue$1 = {};
1587
1588
  const deviceCurrentResolve$1 = {};
1588
1589
  const deviceCurrentReject$1 = {};
1589
1590
  const deviceResponseIdentifier$1 = {};
1590
- const promiseQueue = {
1591
+ const promiseQueue$1 = {
1591
1592
  clearQueue(deviceId, message) {
1592
1593
  if (deviceCurrentReject$1[deviceId]) {
1593
1594
  deviceCurrentReject$1[deviceId](new Error(message ?? "Stopped sending commands"));
@@ -1628,7 +1629,7 @@ const promiseQueue = {
1628
1629
  );
1629
1630
  }
1630
1631
  });
1631
- return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
1632
+ return withTimeout(promise, 8e3, `Command timed out ${deviceResponseIdentifier$1[device.id]}, ${device.id}`);
1632
1633
  });
1633
1634
  return devicePromiseQueue$1[device.id];
1634
1635
  },
@@ -2001,7 +2002,7 @@ const bridgeCommands = {
2001
2002
  async writeCommand(device, commandId, subCommandId, payload) {
2002
2003
  const commandHead = this.getCommandHeader(device);
2003
2004
  const macAddress = [0, 0, 0, 0, 0, 0];
2004
- const timestamp = bridgeTools.decimalToHex(Math.floor(Date.now() / 1e3));
2005
+ const timestamp = decimalToHex(Math.floor(Date.now() / 1e3));
2005
2006
  const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16)).reverse();
2006
2007
  if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
2007
2008
  const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
@@ -2039,7 +2040,7 @@ const bridgeCommands = {
2039
2040
  }
2040
2041
  this.sendKeepAliveCommand(device);
2041
2042
  }, 8e3);
2042
- return promiseQueue.enqueue(device, writeCommand);
2043
+ return promiseQueue$1.enqueue(device, writeCommand);
2043
2044
  }
2044
2045
  };
2045
2046
 
@@ -2230,7 +2231,7 @@ function getVehicleAxlesTypes(tcVehicle) {
2230
2231
  if (axle?.isDrive) {
2231
2232
  binaryRepresentation |= 16;
2232
2233
  }
2233
- return bridgeTools.decimalToHex(binaryRepresentation);
2234
+ return decimalToHex(binaryRepresentation);
2234
2235
  });
2235
2236
  return axleTypesBinary;
2236
2237
  }
@@ -2385,7 +2386,7 @@ async function setVehicle(deviceId, tcVehicle) {
2385
2386
  }
2386
2387
  async function getSensorReading(deviceId, positionId) {
2387
2388
  const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
2388
- const sensorId = value.slice(5, 9).reverse().map((s) => bridgeTools.decimalToHex(s).toUpperCase()).join("");
2389
+ const sensorId = value.slice(5, 9).reverse().map((s) => decimalToHex(s).toUpperCase()).join("");
2389
2390
  const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
2390
2391
  return {
2391
2392
  date: Date.now() - value[14] * 2e3,
@@ -2427,7 +2428,7 @@ async function getAutolearnStatuses(deviceId, tcVehicle) {
2427
2428
  let autolearnedSensorId;
2428
2429
  if (["1", "4", "9"].includes(tyreStatus)) {
2429
2430
  const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
2430
- const axleData = axleInfo.map((i) => bridgeTools.decimalToHex(i).toUpperCase());
2431
+ const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
2431
2432
  const joinedAxleData = Array.from(
2432
2433
  { length: axleData.length / 4 },
2433
2434
  (_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
@@ -2637,7 +2638,7 @@ async function assignTyres(deviceId, tcVehicle) {
2637
2638
  for (let index = 0; index < tcVehicle.axles.length; index++) {
2638
2639
  const axle = tcVehicle.axles[index];
2639
2640
  const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
2640
- const axleData = axleInfo.map((i) => bridgeTools.decimalToHex(i).toUpperCase());
2641
+ const axleData = axleInfo.map((i) => decimalToHex(i).toUpperCase());
2641
2642
  const joinedAxleData = Array.from(
2642
2643
  { length: axleData.length / 4 },
2643
2644
  (_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
@@ -2713,7 +2714,7 @@ const bridge = {
2713
2714
  };
2714
2715
  async function connect(deviceId, accessLevel) {
2715
2716
  if (canCommunicateWith(deviceId)) return;
2716
- await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
2717
+ await promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
2717
2718
  const bridgeMeta = deviceMeta.bridge;
2718
2719
  await bluetooth.connect(deviceId, disconnect);
2719
2720
  if (store.deviceState[deviceId] === "paired") {
@@ -2728,7 +2729,7 @@ async function connect(deviceId, accessLevel) {
2728
2729
  _deviceId,
2729
2730
  bridgeMeta.communication.serviceId,
2730
2731
  bridgeMeta.communication.characteristicId,
2731
- (notification) => promiseQueue.processMessage(deviceId, notification),
2732
+ (notification) => promiseQueue$1.processMessage(deviceId, notification),
2732
2733
  (error) => console.error("startNotification Error", error)
2733
2734
  );
2734
2735
  store.deviceAccessLevel[deviceId] = accessLevel ?? "driver";
@@ -2742,7 +2743,7 @@ async function connect(deviceId, accessLevel) {
2742
2743
  }
2743
2744
  async function disconnect(deviceId, reason) {
2744
2745
  store.setState(deviceId, "disconnecting");
2745
- promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
2746
+ promiseQueue$1.clearQueue(deviceId, "Previous pending commands aborted");
2746
2747
  await bluetooth.disconnect(deviceId);
2747
2748
  delete store.devices[deviceId];
2748
2749
  deviceUnreachableCallback?.(deviceId);
@@ -2773,10 +2774,118 @@ const simulatorSvc = {
2773
2774
  }
2774
2775
  };
2775
2776
 
2777
+ const devicePromiseQueue = {};
2778
+ const deviceCurrentResolve = {};
2779
+ const deviceCurrentReject = {};
2780
+ const deviceResponseIdentifier = {};
2781
+ const deviceCurrentPartialMessage = {};
2782
+ const promiseQueue = {
2783
+ clearQueue(deviceId, message) {
2784
+ if (deviceCurrentReject[deviceId]) {
2785
+ deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
2786
+ }
2787
+ devicePromiseQueue[deviceId] = Promise.resolve();
2788
+ deviceResponseIdentifier[deviceId] = void 0;
2789
+ },
2790
+ async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
2791
+ if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
2792
+ devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
2793
+ const promise = new Promise((resolve, reject) => {
2794
+ deviceCurrentResolve[deviceId] = resolve;
2795
+ deviceCurrentReject[deviceId] = reject;
2796
+ deviceResponseIdentifier[deviceId] = responseIdentifier;
2797
+ deviceCurrentPartialMessage[deviceId] = [];
2798
+ if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
2799
+ const chunks = [];
2800
+ for (let i = 0; i < payload.length; i += mtu) {
2801
+ chunks.push(payload.slice(i, i + mtu));
2802
+ }
2803
+ for (const chunk of chunks) {
2804
+ const convertedChunk = new Uint8Array(chunk).buffer;
2805
+ bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
2806
+ }
2807
+ });
2808
+ return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
2809
+ });
2810
+ return devicePromiseQueue[deviceId];
2811
+ },
2812
+ async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
2813
+ const numberArray = Array.from(new Uint8Array(payload));
2814
+ if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
2815
+ deviceCurrentPartialMessage[deviceId] = ___default.concat(deviceCurrentPartialMessage[deviceId], numberArray);
2816
+ if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
2817
+ return;
2818
+ }
2819
+ if (isError(deviceCurrentPartialMessage[deviceId])) {
2820
+ return deviceCurrentReject[deviceId](
2821
+ new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
2822
+ );
2823
+ }
2824
+ const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
2825
+ if (deviceResponseIdentifier[deviceId] === identifier) {
2826
+ deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
2827
+ }
2828
+ }
2829
+ };
2830
+
2831
+ const flexiGaugeTpmsSecurity = {
2832
+ getCommandsSignature(seed) {
2833
+ const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.allLFCommands;
2834
+ if (!key) throw new Error("No Command key found");
2835
+ return aesToCrc32(seed, key);
2836
+ },
2837
+ getWriteSignature(seed) {
2838
+ const key = store.securityKeys?.flexiGaugeTpms?.signatureKeys.writeConfiguration;
2839
+ if (!key) throw new Error("No Write key found");
2840
+ return aesToCrc32(seed, key);
2841
+ },
2842
+ getCrc(message) {
2843
+ const messageHex = message.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? [];
2844
+ const crc = crc16CCITT(messageHex);
2845
+ return decimalToHex(crc, 4).toUpperCase();
2846
+ }
2847
+ };
2848
+ function aesToCrc32(message, key) {
2849
+ const keyString = key.map((n) => decimalToHex(n)).join("");
2850
+ const messageHex = CryptoJs__default.enc.Hex.parse(message);
2851
+ const keyHex = CryptoJs__default.enc.Hex.parse(keyString);
2852
+ const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
2853
+ mode: CryptoJs__default.mode.ECB,
2854
+ padding: CryptoJs__default.pad.ZeroPadding
2855
+ });
2856
+ const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 8).toUpperCase();
2857
+ return encryptedByteString;
2858
+ }
2859
+ function crc16CCITT(msg) {
2860
+ let crc = 65535;
2861
+ const polynomial = 4129;
2862
+ for (let i = 0; i < msg.length; i++) {
2863
+ const b = msg[i];
2864
+ crc ^= b << 8;
2865
+ for (let j = 0; j < 8; j++) {
2866
+ if (crc & 32768) {
2867
+ crc = crc << 1 ^ polynomial;
2868
+ } else {
2869
+ crc <<= 1;
2870
+ }
2871
+ crc &= 65535;
2872
+ }
2873
+ }
2874
+ return crc;
2875
+ }
2876
+
2776
2877
  const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
2777
2878
  const flexiGaugeTpmsCommands = {
2778
2879
  startTpmsScan,
2779
- getBattery
2880
+ getBattery,
2881
+ vdaRequestSensor,
2882
+ vdaRequestSeed,
2883
+ vdaSendWriteModePin,
2884
+ vdaReadConfiguration,
2885
+ vdaSendConfiguration,
2886
+ vdaRequestCommandsAccess,
2887
+ vdaRequestWriteAccess,
2888
+ processMessage
2780
2889
  };
2781
2890
  async function startTpmsScan(deviceId) {
2782
2891
  if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
@@ -2797,23 +2906,92 @@ async function getBattery(deviceId) {
2797
2906
  const battery = Array.from(new Uint8Array(batteryValue))[0];
2798
2907
  return battery;
2799
2908
  }
2909
+ async function vdaRequestSensor(deviceId) {
2910
+ const scanMsg = "*VDA_LF_SEND SID=A0 LID=1A";
2911
+ return await sendCommand(deviceId, scanMsg);
2912
+ }
2913
+ async function vdaRequestSeed(deviceId, sensorId) {
2914
+ const sensorIdReversed = getReversedSensorId(sensorId);
2915
+ const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=04 DATA=${sensorIdReversed}CE9A5713`;
2916
+ return await sendCommand(deviceId, scanMsg);
2917
+ }
2918
+ async function vdaRequestCommandsAccess(deviceId, sensorId, signature) {
2919
+ const sensorIdReversed = getReversedSensorId(sensorId);
2920
+ const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=${sensorIdReversed}${signature}`;
2921
+ return await sendCommand(deviceId, scanMsg);
2922
+ }
2923
+ async function vdaRequestWriteAccess(deviceId, sensorId, signature) {
2924
+ const sensorIdReversed = getReversedSensorId(sensorId);
2925
+ const scanMsg = `*VDA_LF_SEND SID=A0 LID=AA PAR=02 DATA=${sensorIdReversed}${signature}`;
2926
+ return await sendCommand(deviceId, scanMsg);
2927
+ }
2928
+ async function vdaSendWriteModePin(deviceId) {
2929
+ const scanMsg = stringToArrayBuffer("*VDA_LF_SEND SID=A0 LID=AA PAR=00 DATA=FCCC71F6\r\n");
2930
+ await bluetooth.write(
2931
+ deviceId,
2932
+ flexiGaugeTpmsMeta.communication.serviceId,
2933
+ flexiGaugeTpmsMeta.communication.characteristicId,
2934
+ scanMsg
2935
+ );
2936
+ }
2937
+ async function vdaReadConfiguration(deviceId) {
2938
+ const scanMsg = "*VDA_LF_SEND SID=A0 LID=DA PAR=17";
2939
+ return await sendCommand(deviceId, scanMsg);
2940
+ }
2941
+ async function vdaSendConfiguration(deviceId, configuration) {
2942
+ if (configuration.length !== 60) throw new Error("Invalid sensor configuration");
2943
+ const crc = flexiGaugeTpmsSecurity.getCrc(configuration);
2944
+ const scanMsg = `*VDA_LF_SEND SID=A0 LID=EA PAR=17 DATA=${configuration}${crc}`;
2945
+ return await sendCommand(deviceId, scanMsg);
2946
+ }
2947
+ async function sendCommand(deviceId, command) {
2948
+ const message = `${command}\r
2949
+ `;
2950
+ const decimalArray = stringToDecimalArray(message);
2951
+ let result;
2952
+ try {
2953
+ result = await promiseQueue.enqueue(deviceId, flexiGaugeTpmsMeta.communication, decimalArray, "VDA");
2954
+ } catch (error) {
2955
+ flexiGaugeTpms.disconnect(deviceId, "lostConnection");
2956
+ throw error;
2957
+ }
2958
+ return result.map((num) => String.fromCharCode(num)).join("");
2959
+ }
2960
+ function processMessage(deviceId, payload) {
2961
+ promiseQueue.processMessage(deviceId, payload, getIdentifier$1, isComplete$1, isError$1);
2962
+ }
2963
+ function isComplete$1(message) {
2964
+ const completeCommand = message.at(-1) === 10;
2965
+ return completeCommand;
2966
+ }
2967
+ function isError$1(message) {
2968
+ return false;
2969
+ }
2970
+ function getIdentifier$1(message) {
2971
+ const messageString = message.map((num) => String.fromCharCode(num)).join("");
2972
+ return messageString.slice(0, 3);
2973
+ }
2974
+ function getReversedSensorId(sensorId) {
2975
+ return sensorId.match(/.{1,2}/g).reverse().join("");
2976
+ }
2800
2977
 
2801
2978
  let sensorReadings = [];
2802
2979
  const capabilities$1 = [
2803
2980
  {
2804
2981
  id: "td",
2805
2982
  processFn: processTreadDepth,
2806
- regex: /^\*TD.*/
2983
+ regex: /^\*?TD.*/
2984
+ // old fw sends * for some responses and new fw doesnt send * on any response
2807
2985
  },
2808
2986
  {
2809
2987
  id: "button",
2810
2988
  processFn: processButtonPress,
2811
- regex: /^\*[UDLRC] $/
2989
+ regex: /^\*?[UDLRC] $/
2812
2990
  },
2813
2991
  {
2814
2992
  id: "tpms",
2815
2993
  processFn: processTpms,
2816
- regex: /^\*TPMS.*/
2994
+ regex: /^\*?TPMS.*/
2817
2995
  }
2818
2996
  ];
2819
2997
  const flexiGaugeTpmsService = {
@@ -2841,7 +3019,9 @@ const flexiGaugeTpmsService = {
2841
3019
  return capability.processFn(deviceId, stringFromBytes);
2842
3020
  }
2843
3021
  }
2844
- }
3022
+ flexiGaugeTpmsCommands.processMessage(deviceId, message);
3023
+ },
3024
+ programSensor
2845
3025
  };
2846
3026
  function processTreadDepth(deviceId, value) {
2847
3027
  const treadDepth = value.match(/\d+/)?.[0];
@@ -2880,6 +3060,44 @@ function processTpms(deviceId, value) {
2880
3060
  simulatorSvc.triggerEvent("fg:tpms", deviceId, strongestReading);
2881
3061
  }
2882
3062
  }
3063
+ async function programSensor(deviceId, oldSensorId, newSensorId) {
3064
+ await findSensor(deviceId, oldSensorId);
3065
+ const seed = await getSeed(deviceId, oldSensorId);
3066
+ const commandsSignature = await flexiGaugeTpmsSecurity.getCommandsSignature(seed);
3067
+ const writeSignature = await flexiGaugeTpmsSecurity.getWriteSignature(seed);
3068
+ await flexiGaugeTpmsCommands.vdaRequestCommandsAccess(deviceId, oldSensorId, commandsSignature);
3069
+ await flexiGaugeTpmsCommands.vdaRequestWriteAccess(deviceId, oldSensorId, writeSignature);
3070
+ const config = await getSensorConfiguration(deviceId);
3071
+ const newConfig = config.replace(/^.{8}/, newSensorId);
3072
+ await flexiGaugeTpmsCommands.vdaSendConfiguration(deviceId, newConfig);
3073
+ await findSensor(deviceId, newSensorId);
3074
+ }
3075
+ async function getSeed(deviceId, sensorId) {
3076
+ const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSeed(deviceId, sensorId));
3077
+ return response[3];
3078
+ }
3079
+ async function findSensor(deviceId, sensorId) {
3080
+ let seen = 0;
3081
+ for (let i = 0; i < 13; i++) {
3082
+ const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaRequestSensor(deviceId));
3083
+ if (response[2] === sensorId) seen++;
3084
+ if (seen === 3) return;
3085
+ }
3086
+ throw new Error(`Sensor ${sensorId} not found`);
3087
+ }
3088
+ async function getSensorConfiguration(deviceId) {
3089
+ const response = await vdaRepeat(flexiGaugeTpmsCommands.vdaReadConfiguration(deviceId));
3090
+ return response[3].slice(2, -4);
3091
+ }
3092
+ async function vdaRepeat(fn) {
3093
+ for (let i = 0; i < 5; i++) {
3094
+ const response = await fn;
3095
+ const groupedValue = response.split(" ");
3096
+ if (groupedValue[1].includes("TIMEOUT")) continue;
3097
+ if (response) return groupedValue;
3098
+ }
3099
+ throw new Error("VDA Timeout");
3100
+ }
2883
3101
 
2884
3102
  const flexiGaugeTpms = {
2885
3103
  async connect(deviceId) {
@@ -2903,7 +3121,8 @@ const flexiGaugeTpms = {
2903
3121
  onButtonPress: flexiGaugeTpmsService.onButton,
2904
3122
  onTpms: flexiGaugeTpmsService.onTpms,
2905
3123
  getBattery: flexiGaugeTpmsService.getBattery,
2906
- startTpmsScan: flexiGaugeTpmsService.startTpmsScan
3124
+ startTpmsScan: flexiGaugeTpmsService.startTpmsScan,
3125
+ programSensor: flexiGaugeTpmsService.programSensor
2907
3126
  };
2908
3127
 
2909
3128
  const capabilities = [
@@ -2955,60 +3174,6 @@ const pressureStick = {
2955
3174
  onPressure: pressureStickService.onPressure
2956
3175
  };
2957
3176
 
2958
- const devicePromiseQueue = {};
2959
- const deviceCurrentResolve = {};
2960
- const deviceCurrentReject = {};
2961
- const deviceResponseIdentifier = {};
2962
- const deviceCurrentPartialMessage = {};
2963
- const torqueWrenchPromiseQueue = {
2964
- clearQueue(deviceId, message) {
2965
- if (deviceCurrentReject[deviceId]) {
2966
- deviceCurrentReject[deviceId](new Error(message ?? "Stopped sending commands"));
2967
- }
2968
- devicePromiseQueue[deviceId] = Promise.resolve();
2969
- deviceResponseIdentifier[deviceId] = void 0;
2970
- },
2971
- async enqueue(deviceId, communication, payload, responseIdentifier, mtu = 20) {
2972
- if (devicePromiseQueue[deviceId] === void 0) devicePromiseQueue[deviceId] = Promise.resolve();
2973
- devicePromiseQueue[deviceId] = devicePromiseQueue[deviceId].then(() => {
2974
- const promise = new Promise((resolve, reject) => {
2975
- deviceCurrentResolve[deviceId] = resolve;
2976
- deviceCurrentReject[deviceId] = reject;
2977
- deviceResponseIdentifier[deviceId] = responseIdentifier;
2978
- deviceCurrentPartialMessage[deviceId] = [];
2979
- if (!toolsSvc.canCommunicateWith(deviceId)) return reject(new Error("Torque wrench not connected"));
2980
- const chunks = [];
2981
- for (let i = 0; i < payload.length; i += mtu) {
2982
- chunks.push(payload.slice(i, i + mtu));
2983
- }
2984
- for (const chunk of chunks) {
2985
- const convertedChunk = new Uint8Array(chunk).buffer;
2986
- bluetooth.write(deviceId, communication.serviceId, communication.characteristicId, convertedChunk);
2987
- }
2988
- });
2989
- return withTimeout(promise, 5e3, `Command timed out ${deviceResponseIdentifier[deviceId]}, ${deviceId}`);
2990
- });
2991
- return devicePromiseQueue[deviceId];
2992
- },
2993
- async processMessage(deviceId, payload, getIdentifier, isComplete, isError) {
2994
- const numberArray = Array.from(new Uint8Array(payload));
2995
- if (!deviceCurrentPartialMessage[deviceId]) deviceCurrentPartialMessage[deviceId] = [];
2996
- deviceCurrentPartialMessage[deviceId] = ___default.concat(deviceCurrentPartialMessage[deviceId], numberArray);
2997
- if (!isComplete(deviceCurrentPartialMessage[deviceId])) {
2998
- return;
2999
- }
3000
- if (isError(deviceCurrentPartialMessage[deviceId])) {
3001
- return deviceCurrentReject[deviceId](
3002
- new Error("Error response", { cause: deviceCurrentPartialMessage[deviceId] })
3003
- );
3004
- }
3005
- const identifier = getIdentifier(deviceCurrentPartialMessage[deviceId]);
3006
- if (deviceResponseIdentifier[deviceId] === identifier) {
3007
- deviceCurrentResolve[deviceId](deviceCurrentPartialMessage[deviceId]);
3008
- }
3009
- }
3010
- };
3011
-
3012
3177
  const commandStart = "$9";
3013
3178
  const commandEnd = "*";
3014
3179
  const commandIds = {
@@ -3068,16 +3233,15 @@ const torqueWrenchCommands = {
3068
3233
  const decimalArray = Array.from(array);
3069
3234
  let result;
3070
3235
  try {
3071
- result = await torqueWrenchPromiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
3236
+ result = await promiseQueue.enqueue(deviceId, twMeta.communication, decimalArray, commandIds[command]);
3072
3237
  } catch (error) {
3073
3238
  const formattedError = formatError(error);
3074
- torqueWrench.disconnect(deviceId, "lostConnection");
3075
3239
  throw new Error(formattedError);
3076
3240
  }
3077
3241
  return result.map((num) => String.fromCharCode(num)).join("");
3078
3242
  },
3079
3243
  processMessage(deviceId, payload) {
3080
- torqueWrenchPromiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
3244
+ promiseQueue.processMessage(deviceId, payload, getIdentifier, isComplete, isError);
3081
3245
  }
3082
3246
  };
3083
3247
  function isComplete(message) {
@@ -3100,7 +3264,7 @@ function getChecksum(message) {
3100
3264
  return decimalToHex(checksum).toUpperCase();
3101
3265
  }
3102
3266
  function formatError(error) {
3103
- if (!error.cause) throw new Error(error);
3267
+ if (!error.cause) throw error;
3104
3268
  const message = error.cause.slice(6, 8);
3105
3269
  const errorId = message.map((num) => String.fromCharCode(num)).join("");
3106
3270
  return errors[errorId] || "Unknown error";
@@ -3212,8 +3376,7 @@ const torqueWrenchService = {
3212
3376
  },
3213
3377
  async getTime(deviceId) {
3214
3378
  const message = await torqueWrenchCommands.sendCommand(deviceId, "getTime");
3215
- const match = message.match(/209,([^*]*)\*/);
3216
- console.log("\u{1F680} ~ getTime ~ match:", match);
3379
+ const match = message.match(/209,([^*]*)\*/) ?? [];
3217
3380
  if (!match[1]) throw new Error("Invalid time response");
3218
3381
  const hh = match[1].substring(0, 2);
3219
3382
  const mm = match[1].substring(2, 4);
@@ -3262,7 +3425,7 @@ const torqueWrench = {
3262
3425
  async connect(deviceId) {
3263
3426
  const twMeta = deviceMeta.torqueWrench;
3264
3427
  await bluetooth.connect(deviceId, this.disconnect);
3265
- await torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
3428
+ await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
3266
3429
  await ble.startNotification(
3267
3430
  deviceId,
3268
3431
  twMeta.communication.serviceId,
@@ -3282,7 +3445,7 @@ const torqueWrench = {
3282
3445
  await torqueWrenchService.stopJob(deviceId);
3283
3446
  } catch {
3284
3447
  }
3285
- await torqueWrenchPromiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
3448
+ await promiseQueue.clearQueue(deviceId, "Previous pending commands aborted");
3286
3449
  await bluetooth.disconnect(deviceId);
3287
3450
  store.setState(deviceId, void 0, reason ?? "manualDisconnection");
3288
3451
  },
@@ -3633,6 +3796,9 @@ const flexiGaugeTpmsSimulator = {
3633
3796
  await toolsSvc.delay(200);
3634
3797
  return fg.simulatorData.battery;
3635
3798
  },
3799
+ programSensor(deviceId, oldSensorId, newSensorId) {
3800
+ return new Promise((resolve) => resolve());
3801
+ },
3636
3802
  onButtonPress(callback) {
3637
3803
  },
3638
3804
  onTpms(callback) {
package/dist/index.d.cts CHANGED
@@ -828,6 +828,12 @@ interface BleBridgeAdvertisingData {
828
828
  timeFromStart?: number;
829
829
  }
830
830
  interface BleSecurityKeys {
831
+ flexiGaugeTpms?: {
832
+ signatureKeys: {
833
+ allLFCommands: number[];
834
+ writeConfiguration: number[];
835
+ };
836
+ };
831
837
  bridge?: {
832
838
  signatureKeys: {
833
839
  [index: number]: number[][];
@@ -922,6 +928,7 @@ declare function createTirecheckDeviceSdk(platform: DevicePlatform, bleImplement
922
928
  onTpms: (callback: (deviceId: string, value: FgSensorReading | undefined) => void) => void;
923
929
  getBattery: (deviceId: string) => Promise<number>;
924
930
  startTpmsScan: (deviceId: string) => void;
931
+ programSensor: (deviceId: string, oldSensorId: string, newSensorId: string) => Promise<void>;
925
932
  };
926
933
  /** Methods for working with Tirecheck Pressure Stick */
927
934
  pressureStick: {
@@ -944,9 +951,9 @@ declare function createTirecheckDeviceSdk(platform: DevicePlatform, bleImplement
944
951
  connect(deviceId: string): Promise<void>;
945
952
  disconnect(deviceId: string, reason?: StateReason): Promise<void>;
946
953
  getProperties: (deviceId: string) => Promise<TorqueWrenchProperties>;
947
- setTime: (deviceId: string, date: Date) => Promise<any>;
954
+ setTime: (deviceId: string, date: Date) => Promise<string>;
948
955
  startJob: (deviceId: string, params: TorqueWrenchStartJobParams) => Promise<void>;
949
- stopJob: (deviceId: string) => Promise<any>;
956
+ stopJob: (deviceId: string) => Promise<string>;
950
957
  getNumberOfReadings: (deviceId: string) => Promise<{
951
958
  count: number;
952
959
  jobId: string;