tirecheck-device-sdk 0.1.2 → 0.1.4
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 +2758 -221
- package/dist/index.d.cts +201 -31
- package/dist/index.d.mts +201 -31
- package/dist/index.d.ts +201 -31
- package/dist/index.mjs +2757 -222
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,80 +1,160 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
|
+
const CryptoJs = require('crypto-js');
|
|
4
5
|
|
|
5
6
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
7
|
|
|
7
8
|
const ___default = /*#__PURE__*/_interopDefaultCompat(_);
|
|
9
|
+
const CryptoJs__default = /*#__PURE__*/_interopDefaultCompat(CryptoJs);
|
|
8
10
|
|
|
9
11
|
let ble;
|
|
10
12
|
function setBleImplementation(bleImplementation) {
|
|
11
13
|
ble = bleImplementation;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
const store = {
|
|
17
|
+
platform: "web",
|
|
18
|
+
devices: {},
|
|
19
|
+
/** unknown - no information but since we get advertisings it is most likely disconnected, connecting - trying to connect via bluetooth, connected - connected via bluetooth (can send write and read), paired - all additional connection steps completed (setMtu, sendPin, etc) */
|
|
20
|
+
deviceState: {},
|
|
21
|
+
// unknown => connecting => connected => paired => disconnecting => disconnected
|
|
22
|
+
bridgesReboot: {}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function delay(delayMs) {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
27
|
+
}
|
|
28
|
+
function setIntervalImmediate(fn, intervalMs) {
|
|
29
|
+
fn();
|
|
30
|
+
return setInterval(fn, intervalMs);
|
|
31
|
+
}
|
|
32
|
+
function withTimeout(promise, timeoutMs, error) {
|
|
33
|
+
return Promise.race([
|
|
34
|
+
promise,
|
|
35
|
+
new Promise((resolve, reject) => setTimeout(() => reject(normalizeError(error)), timeoutMs))
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
function waitUntil(callback, timeout = 5e3) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const interval = setInterval(() => {
|
|
41
|
+
if (callback()) {
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
resolve();
|
|
44
|
+
}
|
|
45
|
+
}, 500);
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
clearInterval(interval);
|
|
48
|
+
resolve();
|
|
49
|
+
}, timeout);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function canCommunicateWith(deviceId) {
|
|
53
|
+
const state = store.deviceState[deviceId];
|
|
54
|
+
return ["connected", "paired"].includes(state);
|
|
55
|
+
}
|
|
56
|
+
function getDeviceData(deviceId) {
|
|
57
|
+
const deviceData = store.devices[deviceId];
|
|
58
|
+
if (!deviceData) throw new Error("Device data missing");
|
|
59
|
+
return deviceData;
|
|
60
|
+
}
|
|
61
|
+
function isVersionGreaterThan(deviceId, version) {
|
|
62
|
+
if (version?.length !== 5) {
|
|
63
|
+
throw new Error("Invalid version format");
|
|
64
|
+
}
|
|
65
|
+
if (store.devices[deviceId]?.advertisingData?.fwVersion) {
|
|
66
|
+
return store.devices[deviceId]?.advertisingData?.fwVersion > version;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function getPositionId(axlePosition, tyrePosition, axleTyresCount, isSpare) {
|
|
71
|
+
let i2sTyrePosition = tyrePosition;
|
|
72
|
+
if (tyrePosition === 4 && axleTyresCount === 2) i2sTyrePosition = 2;
|
|
73
|
+
if (tyrePosition != null) return axlePosition * 100 + i2sTyrePosition * 10 + (isSpare ? 0 : axleTyresCount);
|
|
74
|
+
if (axlePosition != null) return axlePosition;
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
function stringToArrayBuffer(string) {
|
|
78
|
+
const array = new Uint8Array(string.length);
|
|
79
|
+
for (let i = 0, l = string.length; i < l; i++) {
|
|
80
|
+
array[i] = string.charCodeAt(i);
|
|
81
|
+
}
|
|
82
|
+
return array.buffer;
|
|
83
|
+
}
|
|
84
|
+
const toolsSvc = {
|
|
85
|
+
delay,
|
|
86
|
+
setIntervalImmediate,
|
|
87
|
+
waitUntil,
|
|
88
|
+
withTimeout,
|
|
89
|
+
canCommunicateWith,
|
|
90
|
+
getDeviceData
|
|
91
|
+
};
|
|
92
|
+
function normalizeError(error) {
|
|
93
|
+
if (!error) return new Error("Operation timed out");
|
|
94
|
+
if (typeof error === "string") return new Error(error);
|
|
95
|
+
return error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tyreLabels = getDefaultTyreLabels();
|
|
14
99
|
const bridgeTools = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// if (displayUnits === 'decimal') {
|
|
74
|
-
// return this.hexToDecimalArray(this.decimalToHex(_data as number)).reverse()
|
|
75
|
-
// }
|
|
76
|
-
// return this.hexToDecimalArray(_data as string).reverse()
|
|
77
|
-
// },
|
|
100
|
+
convertBytesToStructure(objStructure, payload) {
|
|
101
|
+
const numArray = ___default.clone(payload);
|
|
102
|
+
const keys = Object.keys(objStructure);
|
|
103
|
+
const sumWithInitial = keys.reduce((accumulator, currentValue) => accumulator + objStructure[currentValue].size, 0);
|
|
104
|
+
if (numArray.length !== sumWithInitial) {
|
|
105
|
+
throw new Error("Cannot convert bytes to object");
|
|
106
|
+
}
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const key of keys) {
|
|
109
|
+
const encodedData = numArray.splice(0, objStructure[key].size);
|
|
110
|
+
result[key] = this.decodeData(encodedData, objStructure[key].display);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
},
|
|
114
|
+
convertStructureToBytes(objStructure, structurizedObj) {
|
|
115
|
+
const keys = Object.keys(objStructure);
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const key of keys) {
|
|
118
|
+
const encoded = this.encodeData(structurizedObj[key], objStructure[key].display);
|
|
119
|
+
if (encoded.length < objStructure[key].size) {
|
|
120
|
+
const _padArray = Array.from({ length: objStructure[key].size - encoded.length }, () => 0);
|
|
121
|
+
encoded.push(..._padArray);
|
|
122
|
+
}
|
|
123
|
+
result.push(...encoded);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
},
|
|
127
|
+
convertAutoLearnObjectToBinary(data) {
|
|
128
|
+
const result = {};
|
|
129
|
+
for (const key in data) {
|
|
130
|
+
if (!key.includes("axle")) continue;
|
|
131
|
+
const value = Number.parseInt(data[key], 16);
|
|
132
|
+
const binaryString = value.toString(2).padStart(8, "0");
|
|
133
|
+
result[key] = binaryString;
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
},
|
|
137
|
+
decodeData(data, displayUnits) {
|
|
138
|
+
const _data = ___default.clone(data);
|
|
139
|
+
if (displayUnits === "ascii") {
|
|
140
|
+
return _data.filter((d) => d).map((x) => String.fromCharCode(x)).join("");
|
|
141
|
+
}
|
|
142
|
+
const _reversedData = _data.reverse();
|
|
143
|
+
if (displayUnits === "decimal") {
|
|
144
|
+
return Number(`0x${_reversedData.map((dec) => this.decimalToHex(dec)).join("")}`.replace(/\r\n/g, ""));
|
|
145
|
+
}
|
|
146
|
+
return _reversedData.map((dec) => this.decimalToHex(dec)).join("");
|
|
147
|
+
},
|
|
148
|
+
encodeData(data, displayUnits) {
|
|
149
|
+
const _data = ___default.clone(data);
|
|
150
|
+
if (displayUnits === "ascii") {
|
|
151
|
+
return _data.split("").map((v, i) => _data.charCodeAt(i));
|
|
152
|
+
}
|
|
153
|
+
if (displayUnits === "decimal") {
|
|
154
|
+
return this.hexToDecimalArray(this.decimalToHex(_data)).reverse();
|
|
155
|
+
}
|
|
156
|
+
return this.hexToDecimalArray(_data).reverse();
|
|
157
|
+
},
|
|
78
158
|
// getBridgeId(device: BluetoothDeviceBridge) {
|
|
79
159
|
// // [244,177, 0, 34,123, 155] => F4B100227B9B
|
|
80
160
|
// return (
|
|
@@ -85,20 +165,20 @@ const bridgeTools = {
|
|
|
85
165
|
// .toUpperCase() || ''
|
|
86
166
|
// )
|
|
87
167
|
// },
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
168
|
+
pkcs(array, length) {
|
|
169
|
+
const pkcsValue = length - array.length;
|
|
170
|
+
if (pkcsValue > 0) {
|
|
171
|
+
return [...array, ...new Array(pkcsValue).fill(pkcsValue)];
|
|
172
|
+
}
|
|
173
|
+
return array;
|
|
174
|
+
},
|
|
95
175
|
decimalToHex(decimal) {
|
|
96
176
|
const hex = decimal.toString(16);
|
|
97
177
|
return hex.padStart(2, "0");
|
|
98
178
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
179
|
+
hexToDecimalArray(hex) {
|
|
180
|
+
return hex.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) || [];
|
|
181
|
+
},
|
|
102
182
|
getFwVersion(payload) {
|
|
103
183
|
if (payload?.length !== 3) {
|
|
104
184
|
console.warn("Could not process FwVersion ", payload);
|
|
@@ -109,46 +189,125 @@ const bridgeTools = {
|
|
|
109
189
|
acc.push(value[0] === "0" ? value[1] : value);
|
|
110
190
|
return acc;
|
|
111
191
|
}, []).join(".");
|
|
192
|
+
},
|
|
193
|
+
barToKpaByte(value) {
|
|
194
|
+
if (!value) return 0;
|
|
195
|
+
return ___default.round((value + 1) * 100 / 5.0625);
|
|
196
|
+
},
|
|
197
|
+
convertSensorIdForBridge(sensorId) {
|
|
198
|
+
if (sensorId.startsWith("2") && sensorId.length === 10) {
|
|
199
|
+
return Number.parseInt(sensorId).toString(16);
|
|
200
|
+
}
|
|
201
|
+
return sensorId;
|
|
202
|
+
},
|
|
203
|
+
kpaByteToBar(value, decrementValue = 1) {
|
|
204
|
+
if (!___default.isNumber(value)) {
|
|
205
|
+
throw new TypeError("Value has to be a number");
|
|
206
|
+
}
|
|
207
|
+
const rawBar = value * 5.0625 / 100;
|
|
208
|
+
return Math.max(rawBar - decrementValue, 0);
|
|
209
|
+
},
|
|
210
|
+
isVersionGreaterThan(device, version) {
|
|
211
|
+
if (version?.length !== 5) {
|
|
212
|
+
throw new Error("Invalid version format");
|
|
213
|
+
}
|
|
214
|
+
if (device.advertisingData.fwVersion) {
|
|
215
|
+
return device.advertisingData?.fwVersion > version;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
},
|
|
219
|
+
convertSensorIdFromBridge(sensorId) {
|
|
220
|
+
const sensorIdDecimal = Number.parseInt(sensorId, 16).toString();
|
|
221
|
+
if (sensorId.startsWith("B") && sensorIdDecimal.startsWith("2") && sensorIdDecimal.length === 10) {
|
|
222
|
+
return sensorIdDecimal;
|
|
223
|
+
}
|
|
224
|
+
return sensorId;
|
|
225
|
+
},
|
|
226
|
+
getPositionInfo(positionId) {
|
|
227
|
+
if (positionId === 0)
|
|
228
|
+
return {
|
|
229
|
+
axlePosition: null,
|
|
230
|
+
tyrePosition: null,
|
|
231
|
+
name: "Vehicle"
|
|
232
|
+
};
|
|
233
|
+
if (positionId < 100)
|
|
234
|
+
return {
|
|
235
|
+
axlePosition: positionId,
|
|
236
|
+
tyrePosition: null,
|
|
237
|
+
name: `Axle ${positionId.toString()}`
|
|
238
|
+
};
|
|
239
|
+
const axleTyresCount = positionId % 10;
|
|
240
|
+
const axlePosition = Math.floor(positionId / 100);
|
|
241
|
+
const tyrePosition = Math.floor(positionId / 10) % 10;
|
|
242
|
+
const isSpare = String(positionId).endsWith("0");
|
|
243
|
+
return {
|
|
244
|
+
positionId,
|
|
245
|
+
axlePosition,
|
|
246
|
+
tyrePosition: isSpare ? 1 : tyrePosition,
|
|
247
|
+
axleTyresCount,
|
|
248
|
+
name: this.getPositionNameInner(axlePosition, tyrePosition, axleTyresCount),
|
|
249
|
+
description: this.getPositionDescriptionInner(axlePosition, tyrePosition, axleTyresCount),
|
|
250
|
+
isSpareTyre: axleTyresCount === 0 || isSpare,
|
|
251
|
+
isLeftTyre: axleTyresCount === 2 && tyrePosition === 1 || axleTyresCount === 4 && tyrePosition <= 2,
|
|
252
|
+
isRightTyre: axleTyresCount === 2 && tyrePosition === 2 || axleTyresCount === 4 && tyrePosition > 2,
|
|
253
|
+
isCenterTyre: axleTyresCount === 1,
|
|
254
|
+
isTwinTyre: axleTyresCount === 4,
|
|
255
|
+
isOuterTwinTyre: axleTyresCount === 4 && [1, 4].includes(tyrePosition),
|
|
256
|
+
isInnerTwinTyre: axleTyresCount === 4 && [2, 3].includes(tyrePosition)
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
getPositionNameInner(axlePosition, tyrePosition, axleTyresCount) {
|
|
260
|
+
const l = tyreLabels[tyrePosition * 10 + axleTyresCount];
|
|
261
|
+
const tyreLabel = l?.replace("_", axlePosition.toString());
|
|
262
|
+
if (tyreLabel) return tyreLabel;
|
|
263
|
+
if (axleTyresCount < 2) return axlePosition.toString();
|
|
264
|
+
return tyrePosition.toString();
|
|
265
|
+
},
|
|
266
|
+
getPositionDescriptionInner(axlePosition, tyrePosition, axleTyresCount) {
|
|
267
|
+
const axle = `Axle ${axlePosition.toString()}`;
|
|
268
|
+
if (axleTyresCount === 4) {
|
|
269
|
+
const side = tyrePosition === 1 || tyrePosition === 2 ? "Left" : "Right";
|
|
270
|
+
const pos = tyrePosition === 1 || tyrePosition === axleTyresCount ? "Outer" : "Inner";
|
|
271
|
+
return `${axle}, ${side} ${pos}`;
|
|
272
|
+
}
|
|
273
|
+
if (axleTyresCount === 2) {
|
|
274
|
+
const side = tyrePosition === 1 ? "Left" : "Right";
|
|
275
|
+
return `${axle}, ${side}`;
|
|
276
|
+
}
|
|
277
|
+
if (axleTyresCount === 1) {
|
|
278
|
+
return `${axle}, ${"Center"}`;
|
|
279
|
+
}
|
|
280
|
+
return `${axle}, ${"Spare"}`;
|
|
281
|
+
},
|
|
282
|
+
getSpareTyres(tcVehicle) {
|
|
283
|
+
const spareTyres = tcVehicle.tcTyres?.filter((tyre) => String(tyre.mountedOn?.positionId).endsWith("0"));
|
|
284
|
+
return spareTyres;
|
|
112
285
|
}
|
|
113
|
-
// barToKpaByte(value?: number) {
|
|
114
|
-
// if (!value) return 0
|
|
115
|
-
// return _.round(((value + 1) * 100) / 5.0625)
|
|
116
|
-
// },
|
|
117
|
-
// kpaByteToBar(value?: number, decrementValue = 1) {
|
|
118
|
-
// if (!_.isNumber(value)) {
|
|
119
|
-
// throw new TypeError('Value has to be a number')
|
|
120
|
-
// }
|
|
121
|
-
// const rawBar = (value * 5.0625) / 100
|
|
122
|
-
// return Math.max(rawBar - decrementValue, 0)
|
|
123
|
-
// },
|
|
124
|
-
// getMacAddressIfOtaAndIos(device: BluetoothDeviceBridge) {
|
|
125
|
-
// const uint8 = new Uint8Array(device.advertising.kCBAdvDataLeBluetoothDeviceAddress)
|
|
126
|
-
// const macAddressArray = Array.from(uint8)
|
|
127
|
-
// .reverse()
|
|
128
|
-
// .slice(0, 6)
|
|
129
|
-
// .map(x => this.decimalToHex(x).toUpperCase())
|
|
130
|
-
// return macAddressArray.join('')
|
|
131
|
-
// },
|
|
132
|
-
// isVersionGreaterThan(version: string) {
|
|
133
|
-
// if (version?.length !== 5) {
|
|
134
|
-
// throw new Error('Invalid version format'.t())
|
|
135
|
-
// }
|
|
136
|
-
// if (bluetoothStore?.connectedBridge?.advertisingData?.fwVersion) {
|
|
137
|
-
// return bluetoothStore?.connectedBridge?.advertisingData?.fwVersion > version
|
|
138
|
-
// }
|
|
139
|
-
// return false
|
|
140
|
-
// },
|
|
141
286
|
};
|
|
287
|
+
function getDefaultTyreLabels() {
|
|
288
|
+
const tyreLabelsStr = "_L;_R;_LO;_LI;_RI;_RO;_";
|
|
289
|
+
const tyreLabelsStrLoc = tyreLabelsStr;
|
|
290
|
+
const tls = tyreLabelsStrLoc.split(";");
|
|
291
|
+
return {
|
|
292
|
+
12: tls[0],
|
|
293
|
+
22: tls[1],
|
|
294
|
+
14: tls[2],
|
|
295
|
+
24: tls[3],
|
|
296
|
+
34: tls[4],
|
|
297
|
+
44: tls[5],
|
|
298
|
+
10: ___default.last(tls) || "",
|
|
299
|
+
20: ___default.last(tls) || ""
|
|
300
|
+
};
|
|
301
|
+
}
|
|
142
302
|
|
|
143
303
|
const bridgeAdvertisingParser = {
|
|
144
304
|
getDeviceInfoFromAdvertising(device) {
|
|
145
305
|
const adversitingType = getAdvertisingType(device);
|
|
146
|
-
if (adversitingType !== "connectable")
|
|
147
|
-
return void 0;
|
|
306
|
+
if (adversitingType !== "connectable") return void 0;
|
|
148
307
|
const advertisingData = getAdvertisingData({ advertising: device.advertising, deviceName: device.name });
|
|
149
308
|
const bridgeId = advertisingData?.macAddress?.map((n) => bridgeTools.decimalToHex(n)).reverse().join("").toUpperCase() || "";
|
|
150
309
|
const vin = String.fromCharCode(...advertisingData?.vinNum || []).split("\0").join("");
|
|
151
|
-
return { id: device.id, name: device.name, bridgeId, vin, advertisingData, device };
|
|
310
|
+
return { id: device.id, name: device.name, bridgeId, vin, advertisingData, type: "bridge", rssi: device.rssi };
|
|
152
311
|
}
|
|
153
312
|
};
|
|
154
313
|
function getAdvertisingType(device) {
|
|
@@ -173,7 +332,7 @@ function getAdvertisingData({ advertising, deviceName }) {
|
|
|
173
332
|
advertising.kCBAdvDataIsConnectable ? advertising.kCBAdvDataManufacturerData : advertising
|
|
174
333
|
);
|
|
175
334
|
const adv = Array.from(uint8);
|
|
176
|
-
const advertisingData = advertising.kCBAdvDataIsConnectable ? processIosAdvertising(adv) : processAndroidAdvertising(adv, isKrone);
|
|
335
|
+
const advertisingData = advertising.kCBAdvDataIsConnectable ? processIosAdvertising(adv, isKrone) : processAndroidAdvertising(adv, isKrone);
|
|
177
336
|
if (!advertisingData || !advertisingData.macAddress.length || !advertisingData.randomAdvNumber.length || !advertisingData.vinNum.length) {
|
|
178
337
|
throw new Error("Device not recognized");
|
|
179
338
|
}
|
|
@@ -213,145 +372,206 @@ function processAndroidAdvertising(adv, isKrone) {
|
|
|
213
372
|
return advertisingData;
|
|
214
373
|
}
|
|
215
374
|
function processIosAdvertising(adv, isKrone) {
|
|
216
|
-
|
|
217
|
-
|
|
375
|
+
const advertisingData = {
|
|
376
|
+
configVersion: isKrone ? 17 : 1,
|
|
377
|
+
macAddress: [],
|
|
378
|
+
randomAdvNumber: [],
|
|
379
|
+
vinNum: [],
|
|
380
|
+
fwVersion: void 0,
|
|
381
|
+
timeFromStart: void 0
|
|
382
|
+
};
|
|
383
|
+
console.log("adv.lenght", adv.length);
|
|
384
|
+
if (adv.length < 34) return void 0;
|
|
218
385
|
if (adv.length === 35) {
|
|
219
|
-
adv.slice(3, 11);
|
|
220
|
-
adv.slice(12, 18);
|
|
221
|
-
adv.slice(18, 35);
|
|
386
|
+
advertisingData.randomAdvNumber = adv.slice(3, 11);
|
|
387
|
+
advertisingData.macAddress = adv.slice(12, 18);
|
|
388
|
+
advertisingData.vinNum = adv.slice(18, 35);
|
|
222
389
|
}
|
|
223
390
|
if (adv.length === 40) {
|
|
224
|
-
adv.slice(3, 11);
|
|
225
|
-
bridgeTools.getFwVersion(adv.slice(11, 14));
|
|
226
|
-
adv[14];
|
|
227
|
-
adv[15];
|
|
228
|
-
adv.slice(17, 23);
|
|
229
|
-
adv.slice(23, 40);
|
|
391
|
+
advertisingData.randomAdvNumber = adv.slice(3, 11);
|
|
392
|
+
advertisingData.fwVersion = bridgeTools.getFwVersion(adv.slice(11, 14));
|
|
393
|
+
advertisingData.configVersion = adv[14];
|
|
394
|
+
advertisingData.timeFromStart = adv[15];
|
|
395
|
+
advertisingData.macAddress = adv.slice(17, 23);
|
|
396
|
+
advertisingData.vinNum = adv.slice(23, 40);
|
|
230
397
|
}
|
|
398
|
+
return advertisingData;
|
|
231
399
|
}
|
|
232
400
|
|
|
233
401
|
const deviceMeta = {
|
|
234
402
|
bridge: {
|
|
235
403
|
nameRegex: /(030303|030321)/,
|
|
236
|
-
|
|
404
|
+
communication: {
|
|
237
405
|
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
238
406
|
// message from device
|
|
239
407
|
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d7"
|
|
240
408
|
},
|
|
241
|
-
capabilities: [],
|
|
242
|
-
manufacturerId: 2978,
|
|
243
409
|
mtu: 256,
|
|
244
410
|
getDeviceInfoFromAdvertising: bridgeAdvertisingParser.getDeviceInfoFromAdvertising
|
|
245
411
|
},
|
|
246
412
|
bridgeOta: {
|
|
247
|
-
nameRegex: /CAN BLE BRDG OTA
|
|
413
|
+
nameRegex: /(CAN BLE BRDG OTA.*|0303.{2}_B)/,
|
|
248
414
|
characteristic: {
|
|
249
415
|
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
250
416
|
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d7"
|
|
251
417
|
},
|
|
252
|
-
|
|
253
|
-
|
|
418
|
+
getDeviceInfoFromAdvertising: (device) => {
|
|
419
|
+
const bleDevice = {
|
|
420
|
+
...device,
|
|
421
|
+
type: "bridgeOta"
|
|
422
|
+
};
|
|
423
|
+
return bleDevice;
|
|
254
424
|
}
|
|
255
425
|
},
|
|
256
426
|
flexiGaugeTpms: {
|
|
257
427
|
nameRegex: /Flexi.*/,
|
|
258
|
-
|
|
428
|
+
communication: {
|
|
259
429
|
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
260
|
-
// message from device
|
|
261
430
|
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d6"
|
|
262
431
|
},
|
|
263
|
-
|
|
432
|
+
battery: {
|
|
433
|
+
serviceId: "180f",
|
|
434
|
+
characteristicId: "2a19"
|
|
435
|
+
},
|
|
436
|
+
getDeviceInfoFromAdvertising: (device) => {
|
|
437
|
+
const bleDevice = {
|
|
438
|
+
...device,
|
|
439
|
+
type: "flexiGaugeTpms"
|
|
440
|
+
};
|
|
441
|
+
return bleDevice;
|
|
264
442
|
},
|
|
265
|
-
// Do we need it here?
|
|
266
|
-
capabilities: [
|
|
267
|
-
{
|
|
268
|
-
id: "td",
|
|
269
|
-
// processFn: processTreadDepth,
|
|
270
|
-
regex: /^\*TD.*/
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
id: "button",
|
|
274
|
-
// processFn: processButtonPress,
|
|
275
|
-
regex: /^\*[UDLRC] $/
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
id: "tpms",
|
|
279
|
-
// processFn: processTpms,
|
|
280
|
-
regex: /^\*TPMS.*/
|
|
281
|
-
}
|
|
282
|
-
],
|
|
283
443
|
reconnect: true
|
|
284
444
|
// Do we need it here?
|
|
285
|
-
},
|
|
286
|
-
pressureStick: {
|
|
287
|
-
nameRegex: /Pressure Stick.*/,
|
|
288
|
-
characteristic: {
|
|
289
|
-
serviceId: "4880c12c-fdcb-4077-8920-a450d7f9b907",
|
|
290
|
-
characteristicId: "fec26ec4-6d71-4442-9f81-55bc21d658d6"
|
|
291
|
-
},
|
|
292
|
-
getDeviceInfoFromAdvertising: () => {
|
|
293
|
-
},
|
|
294
|
-
capabilities: [
|
|
295
|
-
{
|
|
296
|
-
id: "pressure",
|
|
297
|
-
// processFn: processPressure,
|
|
298
|
-
regex: /P([0-9.]+)mBar/
|
|
299
|
-
}
|
|
300
|
-
// only pressure is needed for initial implementation, uncomment tpms functionality when needed
|
|
301
|
-
// {
|
|
302
|
-
// id: 'tpms',
|
|
303
|
-
// processFn: processTpms,
|
|
304
|
-
// regex: /^\*TPMS/,
|
|
305
|
-
// },
|
|
306
|
-
]
|
|
307
445
|
}
|
|
446
|
+
// pressureStick: {
|
|
447
|
+
// nameRegex: /Pressure Stick.*/,
|
|
448
|
+
// characteristic: {
|
|
449
|
+
// serviceId: '4880c12c-fdcb-4077-8920-a450d7f9b907',
|
|
450
|
+
// characteristicId: 'fec26ec4-6d71-4442-9f81-55bc21d658d6',
|
|
451
|
+
// },
|
|
452
|
+
// getDeviceInfoFromAdvertising: () => {},
|
|
453
|
+
// capabilities: [
|
|
454
|
+
// {
|
|
455
|
+
// id: 'pressure',
|
|
456
|
+
// // processFn: processPressure,
|
|
457
|
+
// regex: /P([0-9.]+)mBar/,
|
|
458
|
+
// },
|
|
459
|
+
// // only pressure is needed for initial implementation, uncomment tpms functionality when needed
|
|
460
|
+
// // {
|
|
461
|
+
// // id: 'tpms',
|
|
462
|
+
// // processFn: processTpms,
|
|
463
|
+
// // regex: /^\*TPMS/,
|
|
464
|
+
// // },
|
|
465
|
+
// ],
|
|
466
|
+
// },
|
|
308
467
|
};
|
|
309
468
|
|
|
310
469
|
const checkUnreachableDevicesTimeouts = {};
|
|
311
470
|
const checkConnectedStateIntervals = {};
|
|
312
471
|
let deviceAdvertisingCallback;
|
|
313
|
-
let deviceUpdateCallback;
|
|
314
472
|
let deviceUnreachableCallback;
|
|
473
|
+
let deviceLostConnectionCallback;
|
|
315
474
|
const bluetooth = {
|
|
316
475
|
/** Triggered when "scanDevices" detects device supported by SDK */
|
|
317
476
|
onDeviceAdvertising(callback) {
|
|
318
477
|
deviceAdvertisingCallback = callback;
|
|
319
478
|
},
|
|
320
|
-
onDeviceUpdate(callback) {
|
|
321
|
-
deviceUpdateCallback = callback;
|
|
322
|
-
},
|
|
323
479
|
onDeviceUnreachable(callback) {
|
|
324
480
|
deviceUnreachableCallback = callback;
|
|
325
481
|
},
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
},
|
|
338
|
-
(device) => {
|
|
339
|
-
const processedDevice = processDevice(device);
|
|
340
|
-
if (!processedDevice)
|
|
341
|
-
return;
|
|
342
|
-
deviceAdvertisingCallback?.(processedDevice);
|
|
343
|
-
},
|
|
344
|
-
(e) => console.error("ble.startScanWithOptions error:", e)
|
|
345
|
-
);
|
|
346
|
-
}
|
|
482
|
+
onDeviceLostConnection(callback) {
|
|
483
|
+
deviceLostConnectionCallback = callback;
|
|
484
|
+
},
|
|
485
|
+
scanDevices,
|
|
486
|
+
stopScan() {
|
|
487
|
+
ble.stopScan();
|
|
488
|
+
},
|
|
489
|
+
connect,
|
|
490
|
+
disconnect,
|
|
491
|
+
write,
|
|
492
|
+
read
|
|
347
493
|
};
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
494
|
+
async function scanDevices(services = [], duration) {
|
|
495
|
+
await ble.stopScan();
|
|
496
|
+
ble.startScanWithOptions(
|
|
497
|
+
services,
|
|
498
|
+
{
|
|
499
|
+
reportDuplicates: true,
|
|
500
|
+
matchMode: "aggressive",
|
|
501
|
+
scanMode: "lowLatency",
|
|
502
|
+
numOfMatches: "max",
|
|
503
|
+
callbackType: "all",
|
|
504
|
+
reportDelay: 0,
|
|
505
|
+
duration
|
|
506
|
+
},
|
|
507
|
+
(device) => {
|
|
508
|
+
const processedDevice = processDevice(device);
|
|
509
|
+
if (!processedDevice) return;
|
|
510
|
+
store.devices[processedDevice.id] = processedDevice;
|
|
511
|
+
deviceAdvertisingCallback?.(processedDevice);
|
|
512
|
+
},
|
|
513
|
+
(e) => console.error("ble.startScanWithOptions error:", e)
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
async function connect(deviceId, disconnectCallback) {
|
|
517
|
+
store.deviceState[deviceId] = "connecting";
|
|
518
|
+
let connectedDevice;
|
|
519
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
520
|
+
try {
|
|
521
|
+
connectedDevice = await connectInner(deviceId, disconnectCallback);
|
|
522
|
+
if (connectedDevice) break;
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.warn(`${attempt} connect fail`, e);
|
|
525
|
+
if (attempt === 3) throw new Error("Connection unsuccessful");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const isConnected = await ble.isConnected(deviceId);
|
|
529
|
+
if (!isConnected) {
|
|
530
|
+
store.deviceState[deviceId] = void 0;
|
|
531
|
+
throw new Error("Connect Error");
|
|
532
|
+
}
|
|
533
|
+
store.deviceState[deviceId] = "connected";
|
|
534
|
+
return connectedDevice;
|
|
535
|
+
}
|
|
536
|
+
function connectInner(deviceId, disconnectCallback) {
|
|
537
|
+
return new Promise(
|
|
538
|
+
(resolve, reject) => ble.connect(deviceId, resolve, (err) => {
|
|
539
|
+
if (toolsSvc.canCommunicateWith(deviceId)) {
|
|
540
|
+
deviceLostConnectionCallback?.(deviceId);
|
|
541
|
+
disconnectCallback(deviceId);
|
|
542
|
+
}
|
|
543
|
+
reject(err);
|
|
544
|
+
})
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
async function disconnect(deviceId) {
|
|
548
|
+
for (let attempts = 0; attempts < 3; attempts++) {
|
|
549
|
+
await withTimeout(ble.disconnect(deviceId), 1e3, "Disconnect timed out");
|
|
550
|
+
const isConnected = await ble.isConnected(deviceId);
|
|
551
|
+
if (!isConnected) return;
|
|
552
|
+
}
|
|
553
|
+
store.deviceState[deviceId] = void 0;
|
|
554
|
+
throw new Error("Disconnect unsuccessful");
|
|
555
|
+
}
|
|
556
|
+
async function write(deviceId, serviceUuid, characteristicUuid, value) {
|
|
557
|
+
if (!toolsSvc.canCommunicateWith(deviceId)) throw new Error("Write Error: Not connected to device");
|
|
558
|
+
try {
|
|
559
|
+
await ble.writeWithoutResponse(deviceId, serviceUuid, characteristicUuid, value);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
throw new Error(`Write Error: ${e}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async function read(deviceId, serviceUuid, characteristicUuid) {
|
|
565
|
+
if (!toolsSvc.canCommunicateWith(deviceId)) throw new Error("Read Error: Not connected to device");
|
|
566
|
+
try {
|
|
567
|
+
const value = await ble.read(deviceId, serviceUuid, characteristicUuid);
|
|
568
|
+
return Array.from(new Uint8Array(value));
|
|
569
|
+
} catch (e) {
|
|
570
|
+
throw new Error(`Read Error: ${e}`);
|
|
354
571
|
}
|
|
572
|
+
}
|
|
573
|
+
function processDevice(device) {
|
|
574
|
+
const name = getDeviceName(device);
|
|
355
575
|
const deviceType = getDeviceTypeFromName(name);
|
|
356
576
|
if (!deviceType) {
|
|
357
577
|
return;
|
|
@@ -359,14 +579,12 @@ function processDevice(device) {
|
|
|
359
579
|
let processedDevice;
|
|
360
580
|
try {
|
|
361
581
|
processedDevice = deviceMeta[deviceType].getDeviceInfoFromAdvertising({ ...device, name });
|
|
362
|
-
if (!processedDevice)
|
|
363
|
-
return;
|
|
582
|
+
if (!processedDevice) return;
|
|
364
583
|
} catch (e) {
|
|
365
584
|
console.warn("Error processing advertising", e);
|
|
366
585
|
return;
|
|
367
586
|
}
|
|
368
587
|
refreshUnreachableTimeouts(processedDevice.id);
|
|
369
|
-
monitorConnectedState(processedDevice.id);
|
|
370
588
|
return processedDevice;
|
|
371
589
|
}
|
|
372
590
|
function getDeviceTypeFromName(name) {
|
|
@@ -379,20 +597,6 @@ function getDeviceTypeFromName(name) {
|
|
|
379
597
|
}
|
|
380
598
|
return deviceType;
|
|
381
599
|
}
|
|
382
|
-
function monitorConnectedState(deviceId) {
|
|
383
|
-
if (checkConnectedStateIntervals[deviceId])
|
|
384
|
-
return;
|
|
385
|
-
checkConnectedStateIntervals[deviceId] = setInterval(async () => {
|
|
386
|
-
let isConnected = false;
|
|
387
|
-
try {
|
|
388
|
-
await ble.isConnected(deviceId);
|
|
389
|
-
isConnected = true;
|
|
390
|
-
} catch {
|
|
391
|
-
isConnected = false;
|
|
392
|
-
}
|
|
393
|
-
deviceUpdateCallback?.({ deviceId, isConnected });
|
|
394
|
-
}, 1e3);
|
|
395
|
-
}
|
|
396
600
|
function refreshUnreachableTimeouts(deviceId) {
|
|
397
601
|
if (checkUnreachableDevicesTimeouts[deviceId]) {
|
|
398
602
|
clearTimeout(checkUnreachableDevicesTimeouts[deviceId]);
|
|
@@ -405,21 +609,2354 @@ function refreshUnreachableTimeouts(deviceId) {
|
|
|
405
609
|
}
|
|
406
610
|
}, 6e4);
|
|
407
611
|
}
|
|
408
|
-
function
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (messageType === 9) {
|
|
413
|
-
return packet.map((x) => String.fromCharCode(x)).join("");
|
|
612
|
+
function getDeviceName(device) {
|
|
613
|
+
const isBridge = deviceMeta.bridge.nameRegex.test(device.name) || deviceMeta.bridgeOta.nameRegex.test(device.name);
|
|
614
|
+
if (isBridge && !device.advertising.kCBAdvDataLocalName) {
|
|
615
|
+
return getDeviceNameFromAdvertising(device.advertising);
|
|
414
616
|
}
|
|
415
|
-
return
|
|
617
|
+
return device.advertising.kCBAdvDataLocalName ?? device.name;
|
|
618
|
+
}
|
|
619
|
+
function getDeviceNameFromAdvertising(advertising) {
|
|
620
|
+
const adv = new Uint8Array(advertising);
|
|
621
|
+
const length = adv[3];
|
|
622
|
+
const type = adv[4];
|
|
623
|
+
if (type !== 9) {
|
|
624
|
+
console.warn("Bridge name not found in advertising");
|
|
625
|
+
return "";
|
|
626
|
+
}
|
|
627
|
+
const decoder = new TextDecoder("utf-8");
|
|
628
|
+
const deviceName = decoder.decode(adv.slice(5, 4 + length));
|
|
629
|
+
return deviceName;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const bridgeCommandStructures = {
|
|
633
|
+
bridgeConfiguration: {
|
|
634
|
+
id: [98, 1],
|
|
635
|
+
name: "bridgeConfiguration",
|
|
636
|
+
structure: {
|
|
637
|
+
configVersion: {
|
|
638
|
+
size: 4,
|
|
639
|
+
description: "Application configuration version details"
|
|
640
|
+
},
|
|
641
|
+
bleTxPower: {
|
|
642
|
+
size: 2,
|
|
643
|
+
description: "Used BLE Tx power (16 bit signed value; LSB = 0.1 LSB)",
|
|
644
|
+
display: "decimal"
|
|
645
|
+
},
|
|
646
|
+
bleTxChannels: {
|
|
647
|
+
size: 1,
|
|
648
|
+
description: "BLE channel configuration (0x07 = all channels)"
|
|
649
|
+
},
|
|
650
|
+
bleConnectableAdvPeriod: {
|
|
651
|
+
size: 2,
|
|
652
|
+
description: "Ble Connectable Advertisement Period [LSB = 0.625 ms]",
|
|
653
|
+
display: "decimal"
|
|
654
|
+
},
|
|
655
|
+
configurationFlags: {
|
|
656
|
+
size: 1,
|
|
657
|
+
description: "Configuration flags (1 = Enable inactivity timeout; 4 = Disable time lockout on false PINs)"
|
|
658
|
+
},
|
|
659
|
+
wrongPinUntilLockout: {
|
|
660
|
+
size: 1,
|
|
661
|
+
display: "decimal",
|
|
662
|
+
description: "Minimimal number of wrong pins allowed before the Lockout will be activated for a period"
|
|
663
|
+
},
|
|
664
|
+
maxLockoutPeriodIncreases: {
|
|
665
|
+
display: "decimal",
|
|
666
|
+
size: 1,
|
|
667
|
+
description: "How many times it is allowed to double the lockout period"
|
|
668
|
+
},
|
|
669
|
+
wrongPinInitialLockoutTime: {
|
|
670
|
+
size: 1,
|
|
671
|
+
display: "decimal",
|
|
672
|
+
description: "What is the initial lockout time duration after too many wrong PINs"
|
|
673
|
+
},
|
|
674
|
+
securityConfiguration: {
|
|
675
|
+
size: 1,
|
|
676
|
+
description: "Bluetooth security configuration (0x03 - legacy pairing enabled; 0x07 - legacy pairing disabled)"
|
|
677
|
+
},
|
|
678
|
+
canEnabled: {
|
|
679
|
+
size: 1,
|
|
680
|
+
display: "decimal",
|
|
681
|
+
description: "CAN enabled state"
|
|
682
|
+
},
|
|
683
|
+
bleEnabled: {
|
|
684
|
+
size: 1,
|
|
685
|
+
display: "decimal",
|
|
686
|
+
description: "BLE Advertisement enabled state"
|
|
687
|
+
},
|
|
688
|
+
canBitrate: {
|
|
689
|
+
size: 2,
|
|
690
|
+
display: "decimal",
|
|
691
|
+
description: "CAN Bitrate [kbps]"
|
|
692
|
+
},
|
|
693
|
+
movementDetectionLimit: {
|
|
694
|
+
size: 2,
|
|
695
|
+
display: "decimal",
|
|
696
|
+
description: "Movement detection limit [1/256 kmph per bit]"
|
|
697
|
+
},
|
|
698
|
+
sensorInactivityTimeout: {
|
|
699
|
+
size: 2,
|
|
700
|
+
display: "decimal",
|
|
701
|
+
description: "Timeout [s] counter after which inactive sensor is marked as erroneous (incremented when vehicle is moving; max. 512 seconds)"
|
|
702
|
+
},
|
|
703
|
+
manufacturerCode: {
|
|
704
|
+
size: 4,
|
|
705
|
+
display: "decimal",
|
|
706
|
+
description: "Manufacturer Code as per SAE J1939"
|
|
707
|
+
},
|
|
708
|
+
identityNumber: {
|
|
709
|
+
size: 4,
|
|
710
|
+
display: "decimal",
|
|
711
|
+
description: "Identity number for ECU (Max. 22 bits value = 0x1FFFFF)"
|
|
712
|
+
},
|
|
713
|
+
manufacturerName: {
|
|
714
|
+
size: 20,
|
|
715
|
+
description: "Manufacturer Name (Max. 20 bytes length)",
|
|
716
|
+
display: "ascii"
|
|
717
|
+
},
|
|
718
|
+
ecuPartNumber: {
|
|
719
|
+
size: 20,
|
|
720
|
+
description: "ECU Part Number (Max. 20 bytes length)",
|
|
721
|
+
display: "ascii"
|
|
722
|
+
},
|
|
723
|
+
softwareVersion: {
|
|
724
|
+
size: 20,
|
|
725
|
+
description: "Software Version (Max. 20 bytes length)",
|
|
726
|
+
display: "ascii"
|
|
727
|
+
},
|
|
728
|
+
reservedConfiguration: {
|
|
729
|
+
size: 4,
|
|
730
|
+
display: "decimal",
|
|
731
|
+
description: "Reserved configuration"
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
workshopCanSettings: {
|
|
736
|
+
id: [98, 96],
|
|
737
|
+
name: "workshopCanSettings",
|
|
738
|
+
structure: {
|
|
739
|
+
canTermination: {
|
|
740
|
+
size: 1,
|
|
741
|
+
description: "CAN Termination disabled / enabled"
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
customerCanSettings: {
|
|
746
|
+
id: [98, 80],
|
|
747
|
+
name: "customerCanSettings",
|
|
748
|
+
structure: {
|
|
749
|
+
canProtocol: {
|
|
750
|
+
size: 1,
|
|
751
|
+
description: "J1939 / ECE-R141 CAN Protocol Status"
|
|
752
|
+
},
|
|
753
|
+
transparentFilteredMode: {
|
|
754
|
+
size: 1,
|
|
755
|
+
description: "0th bit = CAN Transparent/Filtered Mode; 1st bit = BLE Transparent/Filtered Mode"
|
|
756
|
+
},
|
|
757
|
+
spnsPgnsBitmap: {
|
|
758
|
+
size: 4,
|
|
759
|
+
description: "SPNs/PGNs Bitmap Toggled State"
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
axleImbalanceThresholds: {
|
|
764
|
+
id: [98, 66],
|
|
765
|
+
name: "axleImbalanceThresholds",
|
|
766
|
+
structure: {
|
|
767
|
+
axle01: {
|
|
768
|
+
size: 1,
|
|
769
|
+
description: "Axle 01 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
770
|
+
},
|
|
771
|
+
axle02: {
|
|
772
|
+
size: 1,
|
|
773
|
+
description: "Axle 02 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
774
|
+
},
|
|
775
|
+
axle03: {
|
|
776
|
+
size: 1,
|
|
777
|
+
description: "Axle 03 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
778
|
+
},
|
|
779
|
+
axle04: {
|
|
780
|
+
size: 1,
|
|
781
|
+
description: "Axle 04 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
782
|
+
},
|
|
783
|
+
axle05: {
|
|
784
|
+
size: 1,
|
|
785
|
+
description: "Axle 05 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
786
|
+
},
|
|
787
|
+
axle06: {
|
|
788
|
+
size: 1,
|
|
789
|
+
description: "Axle 06 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
790
|
+
},
|
|
791
|
+
axle07: {
|
|
792
|
+
size: 1,
|
|
793
|
+
description: "Axle 07 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
794
|
+
},
|
|
795
|
+
axle08: {
|
|
796
|
+
size: 1,
|
|
797
|
+
description: "Axle 08 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
798
|
+
},
|
|
799
|
+
axle09: {
|
|
800
|
+
size: 1,
|
|
801
|
+
description: "Axle 09 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
802
|
+
},
|
|
803
|
+
axle10: {
|
|
804
|
+
size: 1,
|
|
805
|
+
description: "Axle 10 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
806
|
+
},
|
|
807
|
+
axle11: {
|
|
808
|
+
size: 1,
|
|
809
|
+
description: "Axle 11 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
810
|
+
},
|
|
811
|
+
axle12: {
|
|
812
|
+
size: 1,
|
|
813
|
+
description: "Axle 12 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
814
|
+
},
|
|
815
|
+
axle13: {
|
|
816
|
+
size: 1,
|
|
817
|
+
description: "Axle 13 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
818
|
+
},
|
|
819
|
+
axle14: {
|
|
820
|
+
size: 1,
|
|
821
|
+
description: "Axle 14 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
822
|
+
},
|
|
823
|
+
axle15: {
|
|
824
|
+
size: 1,
|
|
825
|
+
description: "Axle 15 imbalance limits (encoded pressure and temperature imbalance limits)"
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
axleTemperatureThresholds: {
|
|
830
|
+
id: [98, 65],
|
|
831
|
+
name: "axleTemperatureThresholds",
|
|
832
|
+
structure: {
|
|
833
|
+
axle01: {
|
|
834
|
+
size: 1,
|
|
835
|
+
description: "Axle 01 over-temperature limit"
|
|
836
|
+
},
|
|
837
|
+
axle02: {
|
|
838
|
+
size: 1,
|
|
839
|
+
description: "Axle 02 over-temperature limit"
|
|
840
|
+
},
|
|
841
|
+
axle03: {
|
|
842
|
+
size: 1,
|
|
843
|
+
description: "Axle 03 over-temperature limit"
|
|
844
|
+
},
|
|
845
|
+
axle04: {
|
|
846
|
+
size: 1,
|
|
847
|
+
description: "Axle 04 over-temperature limit"
|
|
848
|
+
},
|
|
849
|
+
axle05: {
|
|
850
|
+
size: 1,
|
|
851
|
+
description: "Axle 05 over-temperature limit"
|
|
852
|
+
},
|
|
853
|
+
axle06: {
|
|
854
|
+
size: 1,
|
|
855
|
+
description: "Axle 06 over-temperature limit"
|
|
856
|
+
},
|
|
857
|
+
axle07: {
|
|
858
|
+
size: 1,
|
|
859
|
+
description: "Axle 07 over-temperature limit"
|
|
860
|
+
},
|
|
861
|
+
axle08: {
|
|
862
|
+
size: 1,
|
|
863
|
+
description: "Axle 08 over-temperature limit"
|
|
864
|
+
},
|
|
865
|
+
axle09: {
|
|
866
|
+
size: 1,
|
|
867
|
+
description: "Axle 09 over-temperature limit"
|
|
868
|
+
},
|
|
869
|
+
axle10: {
|
|
870
|
+
size: 1,
|
|
871
|
+
description: "Axle 10 over-temperature limit"
|
|
872
|
+
},
|
|
873
|
+
axle11: {
|
|
874
|
+
size: 1,
|
|
875
|
+
description: "Axle 11 over-temperature limit"
|
|
876
|
+
},
|
|
877
|
+
axle12: {
|
|
878
|
+
size: 1,
|
|
879
|
+
description: "Axle 12 over-temperature limit"
|
|
880
|
+
},
|
|
881
|
+
axle13: {
|
|
882
|
+
size: 1,
|
|
883
|
+
description: "Axle 13 over-temperature limit"
|
|
884
|
+
},
|
|
885
|
+
axle14: {
|
|
886
|
+
size: 1,
|
|
887
|
+
description: "Axle 14 over-temperature limit"
|
|
888
|
+
},
|
|
889
|
+
axle15: {
|
|
890
|
+
size: 1,
|
|
891
|
+
description: "Axle 15 over-temperature limit"
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
axlePressureThresholds: {
|
|
896
|
+
id: [98, 64],
|
|
897
|
+
name: "axlePressureThresholds",
|
|
898
|
+
structure: {
|
|
899
|
+
axle01: {
|
|
900
|
+
size: 4,
|
|
901
|
+
description: "Axle 01 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
902
|
+
},
|
|
903
|
+
axle02: {
|
|
904
|
+
size: 4,
|
|
905
|
+
description: "Axle 02 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
906
|
+
},
|
|
907
|
+
axle03: {
|
|
908
|
+
size: 4,
|
|
909
|
+
description: "Axle 03 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
910
|
+
},
|
|
911
|
+
axle04: {
|
|
912
|
+
size: 4,
|
|
913
|
+
description: "Axle 04 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
914
|
+
},
|
|
915
|
+
axle05: {
|
|
916
|
+
size: 4,
|
|
917
|
+
description: "Axle 05 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
918
|
+
},
|
|
919
|
+
axle06: {
|
|
920
|
+
size: 4,
|
|
921
|
+
description: "Axle 06 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
922
|
+
},
|
|
923
|
+
axle07: {
|
|
924
|
+
size: 4,
|
|
925
|
+
description: "Axle 07 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
926
|
+
},
|
|
927
|
+
axle08: {
|
|
928
|
+
size: 4,
|
|
929
|
+
description: "Axle 08 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
930
|
+
},
|
|
931
|
+
axle09: {
|
|
932
|
+
size: 4,
|
|
933
|
+
description: "Axle 09 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
934
|
+
},
|
|
935
|
+
axle10: {
|
|
936
|
+
size: 4,
|
|
937
|
+
description: "Axle 10 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
938
|
+
},
|
|
939
|
+
axle11: {
|
|
940
|
+
size: 4,
|
|
941
|
+
description: "Axle 11 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
942
|
+
},
|
|
943
|
+
axle12: {
|
|
944
|
+
size: 4,
|
|
945
|
+
description: "Axle 12 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
946
|
+
},
|
|
947
|
+
axle13: {
|
|
948
|
+
size: 4,
|
|
949
|
+
description: "Axle 13 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
950
|
+
},
|
|
951
|
+
axle14: {
|
|
952
|
+
size: 4,
|
|
953
|
+
description: "Axle 14 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
954
|
+
},
|
|
955
|
+
axle15: {
|
|
956
|
+
size: 4,
|
|
957
|
+
description: "Axle 15 pressure delta limits (encoded extreme over-; over-; under-; extreme under-pressure limit)"
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
pressuresPerAxle: {
|
|
962
|
+
id: [98, 48],
|
|
963
|
+
lsb: true,
|
|
964
|
+
name: "pressuresPerAxle",
|
|
965
|
+
structure: {
|
|
966
|
+
axle01: {
|
|
967
|
+
size: 3,
|
|
968
|
+
description: "Axle 01 set pressure; min pressure; max pressure"
|
|
969
|
+
},
|
|
970
|
+
axle02: {
|
|
971
|
+
size: 3,
|
|
972
|
+
description: "Axle 02 set pressure; min pressure; max pressure"
|
|
973
|
+
},
|
|
974
|
+
axle03: {
|
|
975
|
+
size: 3,
|
|
976
|
+
description: "Axle 03 set pressure; min pressure; max pressure"
|
|
977
|
+
},
|
|
978
|
+
axle04: {
|
|
979
|
+
size: 3,
|
|
980
|
+
description: "Axle 04 set pressure; min pressure; max pressure"
|
|
981
|
+
},
|
|
982
|
+
axle05: {
|
|
983
|
+
size: 3,
|
|
984
|
+
description: "Axle 05 set pressure; min pressure; max pressure"
|
|
985
|
+
},
|
|
986
|
+
axle06: {
|
|
987
|
+
size: 3,
|
|
988
|
+
description: "Axle 06 set pressure; min pressure; max pressure"
|
|
989
|
+
},
|
|
990
|
+
axle07: {
|
|
991
|
+
size: 3,
|
|
992
|
+
description: "Axle 07 set pressure; min pressure; max pressure"
|
|
993
|
+
},
|
|
994
|
+
axle08: {
|
|
995
|
+
size: 3,
|
|
996
|
+
description: "Axle 08 set pressure; min pressure; max pressure"
|
|
997
|
+
},
|
|
998
|
+
axle09: {
|
|
999
|
+
size: 3,
|
|
1000
|
+
description: "Axle 09 set pressure; min pressure; max pressure"
|
|
1001
|
+
},
|
|
1002
|
+
axle10: {
|
|
1003
|
+
size: 3,
|
|
1004
|
+
description: "Axle 10 set pressure; min pressure; max pressure"
|
|
1005
|
+
},
|
|
1006
|
+
axle11: {
|
|
1007
|
+
size: 3,
|
|
1008
|
+
description: "Axle 11 set pressure; min pressure; max pressure"
|
|
1009
|
+
},
|
|
1010
|
+
axle12: {
|
|
1011
|
+
size: 3,
|
|
1012
|
+
description: "Axle 12 set pressure; min pressure; max pressure"
|
|
1013
|
+
},
|
|
1014
|
+
axle13: {
|
|
1015
|
+
size: 3,
|
|
1016
|
+
description: "Axle 13 set pressure; min pressure; max pressure"
|
|
1017
|
+
},
|
|
1018
|
+
axle14: {
|
|
1019
|
+
size: 3,
|
|
1020
|
+
description: "Axle 14 set pressure; min pressure; max pressure"
|
|
1021
|
+
},
|
|
1022
|
+
axle15: {
|
|
1023
|
+
size: 3,
|
|
1024
|
+
description: "Axle 15 set pressure; min pressure; max pressure"
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
autolearnSettings: {
|
|
1029
|
+
id: [98, 160],
|
|
1030
|
+
name: "autolearnSettings",
|
|
1031
|
+
structure: {
|
|
1032
|
+
axle01: {
|
|
1033
|
+
size: 1,
|
|
1034
|
+
description: "Axle 01 type, steer, liftable..."
|
|
1035
|
+
},
|
|
1036
|
+
axle02: {
|
|
1037
|
+
size: 1,
|
|
1038
|
+
description: "Axle 02 type, steer, liftable..."
|
|
1039
|
+
},
|
|
1040
|
+
axle03: {
|
|
1041
|
+
size: 1,
|
|
1042
|
+
description: "Axle 03 type, steer, liftable..."
|
|
1043
|
+
},
|
|
1044
|
+
axle04: {
|
|
1045
|
+
size: 1,
|
|
1046
|
+
description: "Axle 04 type, steer, liftable..."
|
|
1047
|
+
},
|
|
1048
|
+
axle05: {
|
|
1049
|
+
size: 1,
|
|
1050
|
+
description: "Axle 05 type, steer, liftable..."
|
|
1051
|
+
},
|
|
1052
|
+
axle06: {
|
|
1053
|
+
size: 1,
|
|
1054
|
+
description: "Axle 06 type, steer, liftable..."
|
|
1055
|
+
},
|
|
1056
|
+
axle07: {
|
|
1057
|
+
size: 1,
|
|
1058
|
+
description: "Axle 07 type, steer, liftable..."
|
|
1059
|
+
},
|
|
1060
|
+
axle08: {
|
|
1061
|
+
size: 1,
|
|
1062
|
+
description: "Axle 08 type, steer, liftable..."
|
|
1063
|
+
},
|
|
1064
|
+
axle09: {
|
|
1065
|
+
size: 1,
|
|
1066
|
+
description: "Axle 09 type, steer, liftable..."
|
|
1067
|
+
},
|
|
1068
|
+
axle10: {
|
|
1069
|
+
size: 1,
|
|
1070
|
+
description: "Axle 10 type, steer, liftable..."
|
|
1071
|
+
},
|
|
1072
|
+
axle11: {
|
|
1073
|
+
size: 1,
|
|
1074
|
+
description: "Axle 11 type, steer, liftable..."
|
|
1075
|
+
},
|
|
1076
|
+
axle12: {
|
|
1077
|
+
size: 1,
|
|
1078
|
+
description: "Axle 12 type, steer, liftable..."
|
|
1079
|
+
},
|
|
1080
|
+
axle13: {
|
|
1081
|
+
size: 1,
|
|
1082
|
+
description: "Axle 13 type, steer, liftable..."
|
|
1083
|
+
},
|
|
1084
|
+
axle14: {
|
|
1085
|
+
size: 1,
|
|
1086
|
+
description: "Axle 14 type, steer, liftable..."
|
|
1087
|
+
},
|
|
1088
|
+
axle15: {
|
|
1089
|
+
size: 1,
|
|
1090
|
+
description: "Axle 15 type, steer, liftable..."
|
|
1091
|
+
},
|
|
1092
|
+
isAutolearnEnabled: {
|
|
1093
|
+
size: 1,
|
|
1094
|
+
description: "Is autolearn enabled: 0 - disabled, 1 - enabled"
|
|
1095
|
+
},
|
|
1096
|
+
checkingPeriod: {
|
|
1097
|
+
size: 1,
|
|
1098
|
+
description: "Autolearn is evaluated within this period (when enabled)"
|
|
1099
|
+
},
|
|
1100
|
+
multiIdTimeout: {
|
|
1101
|
+
size: 1,
|
|
1102
|
+
description: "This timer (2 sec per bit) detects if multiple tyres / sensor IDs were changed and therefore autolearn cannot identify the tyre position of the new sensors"
|
|
1103
|
+
},
|
|
1104
|
+
temperatureLimit: {
|
|
1105
|
+
size: 1,
|
|
1106
|
+
description: "Autolearn temperature difference acceptation limit"
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
autolearnIdStatus: {
|
|
1111
|
+
id: [98, 176],
|
|
1112
|
+
name: "autolearnIdStatus",
|
|
1113
|
+
structure: {
|
|
1114
|
+
axle01: {
|
|
1115
|
+
size: 8,
|
|
1116
|
+
description: "Axle 01 autolearn statuses, each nibble signifies a single wheel."
|
|
1117
|
+
},
|
|
1118
|
+
axle02: {
|
|
1119
|
+
size: 8,
|
|
1120
|
+
description: "Axle 02 autolearn statuses, each nibble signifies a single wheel"
|
|
1121
|
+
},
|
|
1122
|
+
axle03: {
|
|
1123
|
+
size: 8,
|
|
1124
|
+
description: "Axle 03 autolearn statuses, each nibble signifies a single wheel"
|
|
1125
|
+
},
|
|
1126
|
+
axle04: {
|
|
1127
|
+
size: 8,
|
|
1128
|
+
description: "Axle 04 autolearn statuses, each nibble signifies a single wheel"
|
|
1129
|
+
},
|
|
1130
|
+
axle05: {
|
|
1131
|
+
size: 8,
|
|
1132
|
+
description: "Axle 05 autolearn statuses, each nibble signifies a single wheel"
|
|
1133
|
+
},
|
|
1134
|
+
axle06: {
|
|
1135
|
+
size: 8,
|
|
1136
|
+
description: "Axle 06 autolearn statuses, each nibble signifies a single wheel"
|
|
1137
|
+
},
|
|
1138
|
+
axle07: {
|
|
1139
|
+
size: 8,
|
|
1140
|
+
description: "Axle 07 autolearn statuses, each nibble signifies a single wheel"
|
|
1141
|
+
},
|
|
1142
|
+
axle08: {
|
|
1143
|
+
size: 8,
|
|
1144
|
+
description: "Axle 08 autolearn statuses, each nibble signifies a single wheel"
|
|
1145
|
+
},
|
|
1146
|
+
axle09: {
|
|
1147
|
+
size: 8,
|
|
1148
|
+
description: "Axle 09 autolearn statuses, each nibble signifies a single wheel"
|
|
1149
|
+
},
|
|
1150
|
+
axle10: {
|
|
1151
|
+
size: 8,
|
|
1152
|
+
description: "Axle 10 autolearn statuses, each nibble signifies a single wheel"
|
|
1153
|
+
},
|
|
1154
|
+
axle11: {
|
|
1155
|
+
size: 8,
|
|
1156
|
+
description: "Axle 11 autolearn statuses, each nibble signifies a single wheel"
|
|
1157
|
+
},
|
|
1158
|
+
axle12: {
|
|
1159
|
+
size: 8,
|
|
1160
|
+
description: "Axle 12 autolearn statuses, each nibble signifies a single wheel"
|
|
1161
|
+
},
|
|
1162
|
+
axle13: {
|
|
1163
|
+
size: 8,
|
|
1164
|
+
description: "Axle 13 autolearn statuses, each nibble signifies a single wheel"
|
|
1165
|
+
},
|
|
1166
|
+
axle14: {
|
|
1167
|
+
size: 8,
|
|
1168
|
+
description: "Axle 14 autolearn statuses, each nibble signifies a single wheel"
|
|
1169
|
+
},
|
|
1170
|
+
axle15: {
|
|
1171
|
+
size: 8,
|
|
1172
|
+
description: "Axle 15 autolearn statuses, each nibble signifies a single wheel"
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
autolearnUnknownSensors: {
|
|
1177
|
+
id: [98, 192],
|
|
1178
|
+
name: "autolearnUnknownSensors",
|
|
1179
|
+
structure: {
|
|
1180
|
+
sensor01: {
|
|
1181
|
+
size: 8,
|
|
1182
|
+
description: "Unknown sensor data"
|
|
1183
|
+
},
|
|
1184
|
+
sensor02: {
|
|
1185
|
+
size: 8,
|
|
1186
|
+
description: "Unknown sensor data"
|
|
1187
|
+
},
|
|
1188
|
+
sensor03: {
|
|
1189
|
+
size: 8,
|
|
1190
|
+
description: "Unknown sensor data"
|
|
1191
|
+
},
|
|
1192
|
+
sensor04: {
|
|
1193
|
+
size: 8,
|
|
1194
|
+
description: "Unknown sensor data"
|
|
1195
|
+
},
|
|
1196
|
+
sensor05: {
|
|
1197
|
+
size: 8,
|
|
1198
|
+
description: "Unknown sensor data"
|
|
1199
|
+
},
|
|
1200
|
+
sensor06: {
|
|
1201
|
+
size: 8,
|
|
1202
|
+
description: "Unknown sensor data"
|
|
1203
|
+
},
|
|
1204
|
+
sensor07: {
|
|
1205
|
+
size: 8,
|
|
1206
|
+
description: "Unknown sensor data"
|
|
1207
|
+
},
|
|
1208
|
+
sensor08: {
|
|
1209
|
+
size: 8,
|
|
1210
|
+
description: "Unknown sensor data"
|
|
1211
|
+
},
|
|
1212
|
+
sensor09: {
|
|
1213
|
+
size: 8,
|
|
1214
|
+
description: "Unknown sensor data"
|
|
1215
|
+
},
|
|
1216
|
+
sensor10: {
|
|
1217
|
+
size: 8,
|
|
1218
|
+
description: "Unknown sensor data"
|
|
1219
|
+
},
|
|
1220
|
+
sensor11: {
|
|
1221
|
+
size: 8,
|
|
1222
|
+
description: "Unknown sensor data"
|
|
1223
|
+
},
|
|
1224
|
+
sensor12: {
|
|
1225
|
+
size: 8,
|
|
1226
|
+
description: "Unknown sensor data"
|
|
1227
|
+
},
|
|
1228
|
+
sensor13: {
|
|
1229
|
+
size: 8,
|
|
1230
|
+
description: "Unknown sensor data"
|
|
1231
|
+
},
|
|
1232
|
+
sensor14: {
|
|
1233
|
+
size: 8,
|
|
1234
|
+
description: "Unknown sensor data"
|
|
1235
|
+
},
|
|
1236
|
+
sensor15: {
|
|
1237
|
+
size: 8,
|
|
1238
|
+
description: "Unknown sensor data"
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
const signature_keys = {
|
|
1245
|
+
0: [
|
|
1246
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1247
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31],
|
|
1248
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 47],
|
|
1249
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 63],
|
|
1250
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 79],
|
|
1251
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 95],
|
|
1252
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 111],
|
|
1253
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 127],
|
|
1254
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 143],
|
|
1255
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 159],
|
|
1256
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 175],
|
|
1257
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 191],
|
|
1258
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 207],
|
|
1259
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 223],
|
|
1260
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 239],
|
|
1261
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255]
|
|
1262
|
+
],
|
|
1263
|
+
1: [
|
|
1264
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1265
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31],
|
|
1266
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 47],
|
|
1267
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 63],
|
|
1268
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 79],
|
|
1269
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 95],
|
|
1270
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 111],
|
|
1271
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 127],
|
|
1272
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 143],
|
|
1273
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 159],
|
|
1274
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 175],
|
|
1275
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 191],
|
|
1276
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 207],
|
|
1277
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 223],
|
|
1278
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 239],
|
|
1279
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255]
|
|
1280
|
+
],
|
|
1281
|
+
2: [
|
|
1282
|
+
[121, 114, 34, 71, 3, 133, 92, 108, 162, 173, 161, 168, 164, 148, 34, 139],
|
|
1283
|
+
[136, 120, 146, 44, 228, 28, 205, 63, 205, 120, 170, 120, 251, 245, 227, 154],
|
|
1284
|
+
[37, 22, 60, 132, 154, 66, 59, 185, 36, 142, 163, 103, 39, 121, 227, 209],
|
|
1285
|
+
[15, 30, 54, 173, 21, 127, 65, 158, 168, 84, 192, 17, 51, 3, 90, 173],
|
|
1286
|
+
[136, 165, 72, 127, 72, 38, 200, 5, 4, 187, 203, 29, 6, 82, 130, 39],
|
|
1287
|
+
[106, 95, 111, 82, 191, 213, 104, 194, 106, 30, 199, 250, 69, 223, 79, 250],
|
|
1288
|
+
[150, 63, 177, 61, 50, 182, 227, 53, 157, 168, 245, 96, 90, 212, 17, 132],
|
|
1289
|
+
[233, 159, 182, 205, 3, 216, 203, 73, 193, 142, 183, 16, 22, 208, 38, 143],
|
|
1290
|
+
[7, 45, 239, 102, 47, 8, 237, 115, 77, 232, 2, 167, 47, 65, 164, 251],
|
|
1291
|
+
[7, 159, 18, 71, 3, 132, 84, 47, 60, 167, 134, 251, 114, 207, 254, 190],
|
|
1292
|
+
[203, 129, 34, 153, 32, 169, 153, 45, 215, 172, 76, 214, 5, 21, 210, 65],
|
|
1293
|
+
[29, 194, 255, 4, 195, 144, 178, 174, 255, 66, 15, 19, 26, 212, 61, 251],
|
|
1294
|
+
[42, 13, 136, 252, 143, 234, 120, 242, 164, 36, 21, 135, 252, 145, 101, 124],
|
|
1295
|
+
[24, 24, 207, 236, 159, 24, 180, 141, 80, 119, 86, 239, 192, 32, 51, 34],
|
|
1296
|
+
[187, 89, 217, 2, 16, 125, 121, 46, 159, 66, 85, 169, 9, 155, 2, 178],
|
|
1297
|
+
[212, 230, 71, 216, 35, 20, 241, 0, 204, 163, 80, 198, 132, 210, 166, 173]
|
|
1298
|
+
],
|
|
1299
|
+
16: [
|
|
1300
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
1301
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31],
|
|
1302
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 47],
|
|
1303
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 63],
|
|
1304
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 79],
|
|
1305
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 95],
|
|
1306
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 111],
|
|
1307
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 127],
|
|
1308
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 143],
|
|
1309
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 159],
|
|
1310
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 175],
|
|
1311
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 191],
|
|
1312
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 207],
|
|
1313
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 223],
|
|
1314
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 239],
|
|
1315
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255]
|
|
1316
|
+
],
|
|
1317
|
+
17: [
|
|
1318
|
+
[75, 40, 61, 20, 254, 102, 105, 160, 44, 236, 79, 39, 84, 27, 235, 100],
|
|
1319
|
+
[185, 139, 202, 38, 158, 109, 211, 6, 38, 73, 28, 120, 208, 168, 159, 108],
|
|
1320
|
+
[252, 100, 192, 127, 135, 127, 228, 125, 190, 210, 248, 146, 45, 229, 218, 159],
|
|
1321
|
+
[217, 39, 58, 200, 22, 212, 105, 55, 15, 45, 82, 96, 216, 134, 69, 123],
|
|
1322
|
+
[192, 34, 199, 199, 200, 112, 61, 98, 174, 62, 7, 200, 149, 168, 175, 110],
|
|
1323
|
+
[60, 41, 125, 116, 134, 191, 241, 188, 171, 127, 209, 10, 114, 52, 120, 164],
|
|
1324
|
+
[255, 19, 220, 184, 141, 224, 242, 117, 230, 143, 20, 75, 44, 50, 70, 113],
|
|
1325
|
+
[55, 112, 171, 176, 239, 50, 111, 3, 175, 145, 123, 38, 12, 44, 18, 79],
|
|
1326
|
+
[211, 119, 219, 6, 27, 41, 170, 199, 206, 66, 89, 89, 154, 128, 110, 70],
|
|
1327
|
+
[187, 184, 42, 154, 108, 182, 238, 223, 192, 10, 144, 139, 130, 22, 220, 66],
|
|
1328
|
+
[91, 248, 195, 246, 212, 104, 246, 252, 231, 120, 153, 211, 39, 250, 221, 133],
|
|
1329
|
+
[184, 148, 72, 207, 106, 143, 21, 239, 133, 206, 1, 234, 1, 75, 92, 43],
|
|
1330
|
+
[46, 35, 117, 127, 103, 37, 155, 16, 177, 106, 195, 129, 168, 235, 159, 162],
|
|
1331
|
+
[0, 7, 114, 77, 77, 66, 224, 223, 92, 193, 241, 44, 242, 104, 67, 163],
|
|
1332
|
+
[116, 194, 13, 179, 75, 181, 245, 211, 19, 38, 45, 21, 36, 163, 252, 235],
|
|
1333
|
+
[6, 211, 31, 66, 81, 221, 137, 33, 23, 18, 160, 147, 159, 34, 121, 125]
|
|
1334
|
+
]
|
|
1335
|
+
};
|
|
1336
|
+
const pinKeys = {
|
|
1337
|
+
0: "00000000000000010000000000000002",
|
|
1338
|
+
1: "00000000000000010000000000000002",
|
|
1339
|
+
2: "B2D24304FC345F1418EB96835B3B7AE8",
|
|
1340
|
+
16: "00000000000000010000000000000002",
|
|
1341
|
+
17: "CB5A6AA23FD565A141C5CE5ABF209000"
|
|
1342
|
+
};
|
|
1343
|
+
const bridgeSecurity = {
|
|
1344
|
+
getSignedCommand(device, command) {
|
|
1345
|
+
const { randomAdvNumber, configVersion } = device?.advertisingData || {};
|
|
1346
|
+
if (!randomAdvNumber || !___default.isNumber(configVersion)) return console.error("random not present", randomAdvNumber);
|
|
1347
|
+
const rand = ___default.clone(randomAdvNumber);
|
|
1348
|
+
const security_level = bridgeTools.decimalToHex(command[0]);
|
|
1349
|
+
const keyIndex = Number(security_level[0]);
|
|
1350
|
+
const keyShift = Number(security_level[1]);
|
|
1351
|
+
let paddedCommand = [];
|
|
1352
|
+
if (command.length <= 16) {
|
|
1353
|
+
paddedCommand = bridgeTools.pkcs(command, 16);
|
|
1354
|
+
} else {
|
|
1355
|
+
let t = Math.floor(command.length / 16);
|
|
1356
|
+
const r = command.length % 16;
|
|
1357
|
+
paddedCommand = bridgeTools.pkcs(command, r ? ++t * 16 : t * 16);
|
|
1358
|
+
}
|
|
1359
|
+
const key = signature_keys[configVersion][keyIndex];
|
|
1360
|
+
const startArray = key.slice(keyShift);
|
|
1361
|
+
const endArray = key.slice(0, keyShift);
|
|
1362
|
+
const shiftedKey = startArray.concat(endArray).map((n) => bridgeTools.decimalToHex(n)).join("");
|
|
1363
|
+
const dataIn = [...rand.reverse(), ...Array.from({ length: 8 }).fill(8)].map((x) => bridgeTools.decimalToHex(x)).join("");
|
|
1364
|
+
const keyWA = CryptoJs__default.enc.Hex.parse(shiftedKey);
|
|
1365
|
+
const dataInWA = CryptoJs__default.enc.Hex.parse(dataIn);
|
|
1366
|
+
const encrypted = CryptoJs__default.AES.encrypt(dataInWA, keyWA, {
|
|
1367
|
+
mode: CryptoJs__default.mode.ECB,
|
|
1368
|
+
// ECB mode is used here for simplicity. Consider more secure modes.
|
|
1369
|
+
padding: CryptoJs__default.pad.Pkcs7
|
|
1370
|
+
// We handle padding manually
|
|
1371
|
+
});
|
|
1372
|
+
const iv = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1373
|
+
const nBlocks = paddedCommand.length / 16 + 1;
|
|
1374
|
+
let blockVector = this.encryptedStringToNum(iv);
|
|
1375
|
+
for (let index = 1; index < nBlocks; index++) {
|
|
1376
|
+
const block = paddedCommand.slice((index - 1) * 16, index * 16);
|
|
1377
|
+
const xorBlock = [];
|
|
1378
|
+
for (let index2 = 0; index2 < block.length; index2++) {
|
|
1379
|
+
xorBlock.push(block[index2] ^ blockVector[index2]);
|
|
1380
|
+
}
|
|
1381
|
+
const vector = blockVector.map((x) => bridgeTools.decimalToHex(x)).join("");
|
|
1382
|
+
const dataWa = CryptoJs__default.enc.Hex.parse(xorBlock.map((x) => bridgeTools.decimalToHex(x)).join(""));
|
|
1383
|
+
const testo = CryptoJs__default.AES.encrypt(dataWa, keyWA, {
|
|
1384
|
+
iv: CryptoJs__default.enc.Hex.parse(vector),
|
|
1385
|
+
mode: CryptoJs__default.mode.ECB,
|
|
1386
|
+
padding: CryptoJs__default.pad.Pkcs7
|
|
1387
|
+
});
|
|
1388
|
+
const result = CryptoJs__default.enc.Hex.stringify(testo.ciphertext);
|
|
1389
|
+
blockVector = this.encryptedStringToNum(result.slice(0, 32));
|
|
1390
|
+
}
|
|
1391
|
+
const signature = blockVector.slice(0, 4).reverse();
|
|
1392
|
+
return [...command, ...signature];
|
|
1393
|
+
},
|
|
1394
|
+
async getPin(deviceId) {
|
|
1395
|
+
const deviceData = store.devices[deviceId];
|
|
1396
|
+
if (!deviceData) throw new Error("Device data missing");
|
|
1397
|
+
const { randomAdvNumber, macAddress, configVersion } = deviceData.advertisingData || {};
|
|
1398
|
+
if (!randomAdvNumber || !macAddress || !___default.isNumber(configVersion)) throw new Error("Cannot compute pin");
|
|
1399
|
+
const mac = macAddress.map((n) => bridgeTools.decimalToHex(n)).join("");
|
|
1400
|
+
const rand = randomAdvNumber.map((n) => bridgeTools.decimalToHex(n)).join("");
|
|
1401
|
+
const message = `${mac}0000${rand}`;
|
|
1402
|
+
const key = pinKeys[configVersion];
|
|
1403
|
+
const res = await this.aesToCrc32(message, key);
|
|
1404
|
+
return res;
|
|
1405
|
+
},
|
|
1406
|
+
aesToCrc32(message, key) {
|
|
1407
|
+
const messageHex = CryptoJs__default.enc.Hex.parse(message);
|
|
1408
|
+
const keyHex = CryptoJs__default.enc.Hex.parse(key);
|
|
1409
|
+
const encrypted = CryptoJs__default.AES.encrypt(messageHex, keyHex, {
|
|
1410
|
+
mode: CryptoJs__default.mode.ECB,
|
|
1411
|
+
// You can use other modes like CBC or CTR for additional security
|
|
1412
|
+
padding: CryptoJs__default.pad.ZeroPadding
|
|
1413
|
+
// You can use other padding modes as well
|
|
1414
|
+
});
|
|
1415
|
+
const encryptedByteString = CryptoJs__default.enc.Hex.stringify(encrypted.ciphertext).slice(0, 32);
|
|
1416
|
+
const hexArray = this.encryptedStringToNum(encryptedByteString);
|
|
1417
|
+
return hexArray;
|
|
1418
|
+
},
|
|
1419
|
+
encryptedStringToNum(encString) {
|
|
1420
|
+
const hexArray = [];
|
|
1421
|
+
for (let i = 0; i < encString.length; i += 2) {
|
|
1422
|
+
const slicedString = encString.slice(i, i + 2);
|
|
1423
|
+
hexArray.push(Number(`0x${slicedString}`));
|
|
1424
|
+
}
|
|
1425
|
+
return hexArray;
|
|
1426
|
+
},
|
|
1427
|
+
crc32mpeg2(msg) {
|
|
1428
|
+
let crc = 4294967295;
|
|
1429
|
+
for (let i = 0; i < msg.length; i++) {
|
|
1430
|
+
const b = msg[i];
|
|
1431
|
+
crc ^= b << 24;
|
|
1432
|
+
for (let j = 0; j < 8; j++) {
|
|
1433
|
+
crc = crc << 1 ^ (crc & 2147483648 ? 79764919 : 0);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return crc >>> 0;
|
|
1437
|
+
},
|
|
1438
|
+
getCrcArray(msg) {
|
|
1439
|
+
const crc = this.crc32mpeg2(msg);
|
|
1440
|
+
const crcHex = bridgeTools.decimalToHex(crc).padStart(8, "0");
|
|
1441
|
+
return bridgeTools.hexToDecimalArray(crcHex).reverse();
|
|
1442
|
+
},
|
|
1443
|
+
uint32ToLittleEndian(value) {
|
|
1444
|
+
const byteArray = new Uint8Array(4);
|
|
1445
|
+
byteArray[0] = value & 255;
|
|
1446
|
+
byteArray[1] = value >> 8 & 255;
|
|
1447
|
+
byteArray[2] = value >> 16 & 255;
|
|
1448
|
+
byteArray[3] = value >> 24 & 255;
|
|
1449
|
+
return byteArray;
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
const bridgeMeta = deviceMeta.bridge;
|
|
1454
|
+
let promiseQueue = Promise.resolve();
|
|
1455
|
+
let currentResolve = (_v) => {
|
|
1456
|
+
};
|
|
1457
|
+
let currentReject = (_v) => {
|
|
1458
|
+
};
|
|
1459
|
+
let responseIdentifier;
|
|
1460
|
+
const promiseQueue$1 = {
|
|
1461
|
+
clearQueue(message) {
|
|
1462
|
+
currentReject(new Error(message ?? "Stopped sending commands"));
|
|
1463
|
+
promiseQueue = Promise.resolve();
|
|
1464
|
+
responseIdentifier = [];
|
|
1465
|
+
},
|
|
1466
|
+
async enqueue(device, payload) {
|
|
1467
|
+
promiseQueue = promiseQueue.then(() => {
|
|
1468
|
+
const promise = new Promise((resolve, reject) => {
|
|
1469
|
+
currentResolve = resolve;
|
|
1470
|
+
currentReject = reject;
|
|
1471
|
+
const isKeepAlive = payload[3] === commandIds.keepAlive;
|
|
1472
|
+
responseIdentifier = [isKeepAlive ? 126 : 0, payload[4]];
|
|
1473
|
+
const signedData = bridgeSecurity.getSignedCommand(device, payload);
|
|
1474
|
+
if (!toolsSvc.canCommunicateWith(device.id)) return reject(new Error("Bridge not connected"));
|
|
1475
|
+
if (signedData.length > 110) {
|
|
1476
|
+
const chunks = [];
|
|
1477
|
+
chunks.push(signedData.slice(0, 110));
|
|
1478
|
+
chunks.push(signedData.slice(110));
|
|
1479
|
+
for (const chunk of chunks) {
|
|
1480
|
+
const convertedChunk = new Uint8Array(chunk).buffer;
|
|
1481
|
+
bluetooth.write(
|
|
1482
|
+
device.id,
|
|
1483
|
+
bridgeMeta.communication.serviceId,
|
|
1484
|
+
bridgeMeta.communication.characteristicId,
|
|
1485
|
+
convertedChunk
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
} else {
|
|
1489
|
+
const convertedData = new Uint8Array(signedData).buffer;
|
|
1490
|
+
bluetooth.write(
|
|
1491
|
+
device.id,
|
|
1492
|
+
bridgeMeta.communication.serviceId,
|
|
1493
|
+
bridgeMeta.communication.characteristicId,
|
|
1494
|
+
convertedData
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
return withTimeout(promise, 5e3, "Command timed out");
|
|
1499
|
+
});
|
|
1500
|
+
return promiseQueue;
|
|
1501
|
+
},
|
|
1502
|
+
async processMessage(deviceId, payload) {
|
|
1503
|
+
const numberArray = Array.from(new Uint8Array(payload));
|
|
1504
|
+
if (numberArray[3] === 127) {
|
|
1505
|
+
return currentReject(new Error(`Command not succesful: ${numberArray[4]}`));
|
|
1506
|
+
}
|
|
1507
|
+
if ([226, 81].includes(numberArray[3]) && numberArray[4] && toolsSvc.canCommunicateWith(deviceId)) {
|
|
1508
|
+
store.bridgesReboot[deviceId] = true;
|
|
1509
|
+
}
|
|
1510
|
+
if ((!responseIdentifier[0] || numberArray[3] === responseIdentifier[0]) && (!responseIdentifier[1] || numberArray[4] === responseIdentifier[1])) {
|
|
1511
|
+
return currentResolve(payload);
|
|
1512
|
+
}
|
|
1513
|
+
console.warn("message from the device not belonging to the pending promise: ", payload);
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
const securityLevelIds = {
|
|
1518
|
+
tirecheck: 1,
|
|
1519
|
+
vehicleManufacturer: 16,
|
|
1520
|
+
vehicleDealership: 32,
|
|
1521
|
+
workshop: 48,
|
|
1522
|
+
driver: 64
|
|
1523
|
+
};
|
|
1524
|
+
const messageTypeIds = {
|
|
1525
|
+
command: 0,
|
|
1526
|
+
response: 1
|
|
1527
|
+
};
|
|
1528
|
+
const commandIds = {
|
|
1529
|
+
readData: 34,
|
|
1530
|
+
writeData: 46,
|
|
1531
|
+
keepAlive: 62,
|
|
1532
|
+
keepAliveResponse: 126,
|
|
1533
|
+
sensorMeasurement: 189,
|
|
1534
|
+
sensorMeasurementResponse: 253,
|
|
1535
|
+
ecuReset: 17,
|
|
1536
|
+
reboot: 162,
|
|
1537
|
+
pin: 18,
|
|
1538
|
+
pinResponse: 90,
|
|
1539
|
+
otaRequest: 167
|
|
1540
|
+
};
|
|
1541
|
+
const subCommandIds = {
|
|
1542
|
+
ecuResetSubCommand: 0,
|
|
1543
|
+
rebootSubCommand: 4,
|
|
1544
|
+
bridgeConfiguration: 1,
|
|
1545
|
+
vehicleLayout: 16,
|
|
1546
|
+
/** 0x20 - 0x2F */
|
|
1547
|
+
idsPerWheel: (axleIndex) => 32 + axleIndex,
|
|
1548
|
+
/** only for commandIds.sensorMeasurement */
|
|
1549
|
+
sensorMeasurementPerWheel: (axlePosition, tyrePosition, isTwinTyre, isSpareAxle) => {
|
|
1550
|
+
let bridgeTyrePosition = 0;
|
|
1551
|
+
if (isTwinTyre && !isSpareAxle) {
|
|
1552
|
+
if (tyrePosition === 1) bridgeTyrePosition = 6;
|
|
1553
|
+
else if (tyrePosition === 2) bridgeTyrePosition = 7;
|
|
1554
|
+
else if (tyrePosition === 3) bridgeTyrePosition = 9;
|
|
1555
|
+
else if (tyrePosition === 4) bridgeTyrePosition = 10;
|
|
1556
|
+
} else if (!isTwinTyre && !isSpareAxle) {
|
|
1557
|
+
if (tyrePosition === 1) bridgeTyrePosition = 7;
|
|
1558
|
+
else if (tyrePosition === 2) bridgeTyrePosition = 9;
|
|
1559
|
+
} else if (isSpareAxle) {
|
|
1560
|
+
if (tyrePosition === 1) bridgeTyrePosition = 8;
|
|
1561
|
+
}
|
|
1562
|
+
return axlePosition * 16 + bridgeTyrePosition;
|
|
1563
|
+
},
|
|
1564
|
+
pressurePerAxle: 48,
|
|
1565
|
+
customerPressureThresholds: 64,
|
|
1566
|
+
customerTemperatureThresholds: 65,
|
|
1567
|
+
customerImbalanceThresholds: 66,
|
|
1568
|
+
customerCanSettings: 80,
|
|
1569
|
+
workshopCanSettings: 96,
|
|
1570
|
+
errorHistoryCounter: 112,
|
|
1571
|
+
eolStatus: 128,
|
|
1572
|
+
firmwareVersion: 144,
|
|
1573
|
+
autolearnSettings: 160,
|
|
1574
|
+
autolearnIdStatus: 176,
|
|
1575
|
+
autolearnUnknownSensors: 192
|
|
1576
|
+
};
|
|
1577
|
+
___default.invert(commandIds);
|
|
1578
|
+
___default.invert(subCommandIds);
|
|
1579
|
+
let keepAliveTimer;
|
|
1580
|
+
const bridgeCommands = {
|
|
1581
|
+
getCommandHeader(device) {
|
|
1582
|
+
return [securityLevelIds.tirecheck, messageTypeIds.command];
|
|
1583
|
+
},
|
|
1584
|
+
async setAxlesPressure(deviceId, data) {
|
|
1585
|
+
const deviceData = getDeviceData(deviceId);
|
|
1586
|
+
await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.pressurePerAxle, data);
|
|
1587
|
+
},
|
|
1588
|
+
async setVehicleLayout(deviceId, data) {
|
|
1589
|
+
const deviceData = getDeviceData(deviceId);
|
|
1590
|
+
await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.vehicleLayout, data);
|
|
1591
|
+
},
|
|
1592
|
+
async getCustomerCANSettings(deviceId) {
|
|
1593
|
+
const deviceData = getDeviceData(deviceId);
|
|
1594
|
+
const result = await this.readCommand(deviceData, subCommandIds.customerCanSettings);
|
|
1595
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.customerCanSettings.structure, result);
|
|
1596
|
+
},
|
|
1597
|
+
async getWorkshopCANSettings(deviceId) {
|
|
1598
|
+
const deviceData = getDeviceData(deviceId);
|
|
1599
|
+
const result = await this.readCommand(deviceData, subCommandIds.workshopCanSettings);
|
|
1600
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.workshopCanSettings.structure, result);
|
|
1601
|
+
},
|
|
1602
|
+
async getBridgeConfiguration(deviceId) {
|
|
1603
|
+
const deviceData = getDeviceData(deviceId);
|
|
1604
|
+
const result = await this.readCommand(deviceData, subCommandIds.bridgeConfiguration);
|
|
1605
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.bridgeConfiguration.structure, result);
|
|
1606
|
+
},
|
|
1607
|
+
async getCustomerPressureThresholds(deviceId) {
|
|
1608
|
+
const deviceData = getDeviceData(deviceId);
|
|
1609
|
+
const result = await this.readCommand(deviceData, subCommandIds.customerPressureThresholds);
|
|
1610
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.axlePressureThresholds.structure, result);
|
|
1611
|
+
},
|
|
1612
|
+
async getCustomerTemperatureThresholds(deviceId) {
|
|
1613
|
+
const deviceData = getDeviceData(deviceId);
|
|
1614
|
+
const result = await this.readCommand(deviceData, subCommandIds.customerTemperatureThresholds);
|
|
1615
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.axleTemperatureThresholds.structure, result);
|
|
1616
|
+
},
|
|
1617
|
+
async getCustomerImbalanceThresholds(deviceId) {
|
|
1618
|
+
const deviceData = getDeviceData(deviceId);
|
|
1619
|
+
const result = await this.readCommand(deviceData, subCommandIds.customerImbalanceThresholds);
|
|
1620
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.axleImbalanceThresholds.structure, result);
|
|
1621
|
+
},
|
|
1622
|
+
async sendKeepAliveCommand(device) {
|
|
1623
|
+
return this.promisify(device, [...this.getCommandHeader(device), 2, commandIds.keepAlive, 0]);
|
|
1624
|
+
},
|
|
1625
|
+
async sendOtaRequest(deviceId) {
|
|
1626
|
+
const deviceData = getDeviceData(deviceId);
|
|
1627
|
+
const command = [commandIds.otaRequest, 0];
|
|
1628
|
+
const result = await this.promisify(deviceData, [...this.getCommandHeader(deviceData), command.length, ...command]);
|
|
1629
|
+
return result;
|
|
1630
|
+
},
|
|
1631
|
+
async sendPinCommand(deviceId) {
|
|
1632
|
+
const deviceData = await getDeviceData(deviceId);
|
|
1633
|
+
const pin = await bridgeSecurity.getPin(deviceId);
|
|
1634
|
+
return this.promisify(deviceData, [...this.getCommandHeader(deviceData), 18, commandIds.pin, 0, ...pin]);
|
|
1635
|
+
},
|
|
1636
|
+
async setBridgeToRestart(deviceId) {
|
|
1637
|
+
const deviceData = await getDeviceData(deviceId);
|
|
1638
|
+
const useNewCommand = (deviceData.advertisingData.fwVersion || "") > "0.9.7";
|
|
1639
|
+
await this.promisify(deviceData, [
|
|
1640
|
+
...this.getCommandHeader(deviceData),
|
|
1641
|
+
2,
|
|
1642
|
+
useNewCommand ? commandIds.ecuReset : commandIds.reboot,
|
|
1643
|
+
useNewCommand ? subCommandIds.ecuResetSubCommand : subCommandIds.rebootSubCommand
|
|
1644
|
+
]);
|
|
1645
|
+
},
|
|
1646
|
+
async setCustomerCANSettings(deviceId, structurizedPayload) {
|
|
1647
|
+
const deviceData = getDeviceData(deviceId);
|
|
1648
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1649
|
+
bridgeCommandStructures.customerCanSettings.structure,
|
|
1650
|
+
structurizedPayload
|
|
1651
|
+
);
|
|
1652
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.customerCanSettings, payload);
|
|
1653
|
+
},
|
|
1654
|
+
async getCustomerCrcSettings(deviceId) {
|
|
1655
|
+
const result = await this.readCrcCommand(deviceId, subCommandIds.customerCanSettings);
|
|
1656
|
+
let factorySettings = false;
|
|
1657
|
+
if (!result?.length || result.every((n) => n === 0)) factorySettings = true;
|
|
1658
|
+
return factorySettings;
|
|
1659
|
+
},
|
|
1660
|
+
async readCrcCommand(deviceId, subCommandId) {
|
|
1661
|
+
const deviceData = getDeviceData(deviceId);
|
|
1662
|
+
const commandLength = 2;
|
|
1663
|
+
const result = await this.promisify(deviceData, [
|
|
1664
|
+
...this.getCommandHeader(deviceData),
|
|
1665
|
+
commandLength,
|
|
1666
|
+
commandIds.readData,
|
|
1667
|
+
subCommandId
|
|
1668
|
+
]);
|
|
1669
|
+
const crc = result.slice(result.length - 8, result.length - 4);
|
|
1670
|
+
return crc;
|
|
1671
|
+
},
|
|
1672
|
+
async setWorkshopCANSettings(deviceId, structurizedPayload) {
|
|
1673
|
+
const deviceData = getDeviceData(deviceId);
|
|
1674
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1675
|
+
bridgeCommandStructures.workshopCanSettings.structure,
|
|
1676
|
+
structurizedPayload
|
|
1677
|
+
);
|
|
1678
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.workshopCanSettings, payload);
|
|
1679
|
+
},
|
|
1680
|
+
async setBridgeConfiguration(deviceId, structurizedPayload) {
|
|
1681
|
+
const deviceData = getDeviceData(deviceId);
|
|
1682
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1683
|
+
bridgeCommandStructures.bridgeConfiguration.structure,
|
|
1684
|
+
structurizedPayload
|
|
1685
|
+
);
|
|
1686
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.bridgeConfiguration, payload);
|
|
1687
|
+
},
|
|
1688
|
+
async setCustomerPressureThresholds(deviceId, structurizedPayload) {
|
|
1689
|
+
const deviceData = getDeviceData(deviceId);
|
|
1690
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1691
|
+
bridgeCommandStructures.axlePressureThresholds.structure,
|
|
1692
|
+
structurizedPayload
|
|
1693
|
+
);
|
|
1694
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.customerPressureThresholds, payload);
|
|
1695
|
+
},
|
|
1696
|
+
async setCustomerTemperatureThresholds(deviceId, structurizedPayload) {
|
|
1697
|
+
const deviceData = getDeviceData(deviceId);
|
|
1698
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1699
|
+
bridgeCommandStructures.axleTemperatureThresholds.structure,
|
|
1700
|
+
structurizedPayload
|
|
1701
|
+
);
|
|
1702
|
+
return await this.writeCommand(
|
|
1703
|
+
deviceData,
|
|
1704
|
+
commandIds.writeData,
|
|
1705
|
+
subCommandIds.customerTemperatureThresholds,
|
|
1706
|
+
payload
|
|
1707
|
+
);
|
|
1708
|
+
},
|
|
1709
|
+
async setCustomerImbalanceThresholds(deviceId, structurizedPayload) {
|
|
1710
|
+
const deviceData = getDeviceData(deviceId);
|
|
1711
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1712
|
+
bridgeCommandStructures.axleImbalanceThresholds.structure,
|
|
1713
|
+
structurizedPayload
|
|
1714
|
+
);
|
|
1715
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.customerImbalanceThresholds, payload);
|
|
1716
|
+
},
|
|
1717
|
+
async getAutolearnSettings(deviceId) {
|
|
1718
|
+
const deviceData = getDeviceData(deviceId);
|
|
1719
|
+
const result = await this.readCommand(deviceData, subCommandIds.autolearnSettings);
|
|
1720
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.autolearnSettings.structure, result);
|
|
1721
|
+
},
|
|
1722
|
+
async getAutolearnIdStatus(deviceId) {
|
|
1723
|
+
const deviceData = getDeviceData(deviceId);
|
|
1724
|
+
const result = await this.readCommand(deviceData, subCommandIds.autolearnIdStatus);
|
|
1725
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.autolearnIdStatus.structure, result);
|
|
1726
|
+
},
|
|
1727
|
+
async setAutoLearnSettings(deviceId, structurizedPayload) {
|
|
1728
|
+
const deviceData = getDeviceData(deviceId);
|
|
1729
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1730
|
+
bridgeCommandStructures.autolearnSettings.structure,
|
|
1731
|
+
structurizedPayload
|
|
1732
|
+
);
|
|
1733
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.autolearnSettings, payload);
|
|
1734
|
+
},
|
|
1735
|
+
async setAutolearnIdStatus(deviceId, structurizedPayload) {
|
|
1736
|
+
const deviceData = getDeviceData(deviceId);
|
|
1737
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1738
|
+
bridgeCommandStructures.autolearnIdStatus.structure,
|
|
1739
|
+
structurizedPayload
|
|
1740
|
+
);
|
|
1741
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.autolearnIdStatus, payload);
|
|
1742
|
+
},
|
|
1743
|
+
async getAutolearnUnknownSensors(device) {
|
|
1744
|
+
const result = await this.readCommand(device, subCommandIds.autolearnUnknownSensors);
|
|
1745
|
+
return bridgeTools.convertBytesToStructure(bridgeCommandStructures.autolearnUnknownSensors.structure, result);
|
|
1746
|
+
},
|
|
1747
|
+
async getAxleInfo(deviceId, axleIndex) {
|
|
1748
|
+
const deviceData = getDeviceData(deviceId);
|
|
1749
|
+
if (!___default.inRange(axleIndex, 0, 16)) throw new Error("Error getting an axle");
|
|
1750
|
+
const result = await this.readCommand(deviceData, subCommandIds.idsPerWheel(axleIndex));
|
|
1751
|
+
return result;
|
|
1752
|
+
},
|
|
1753
|
+
// move logic to bridge svc
|
|
1754
|
+
async setAxleInfo(deviceId, axleIndex, data) {
|
|
1755
|
+
const deviceData = getDeviceData(deviceId);
|
|
1756
|
+
await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.idsPerWheel(axleIndex), data);
|
|
1757
|
+
},
|
|
1758
|
+
async getAxlesPressure(deviceId) {
|
|
1759
|
+
const deviceData = getDeviceData(deviceId);
|
|
1760
|
+
const result = await this.readCommand(deviceData, subCommandIds.pressurePerAxle);
|
|
1761
|
+
return result;
|
|
1762
|
+
},
|
|
1763
|
+
async getPressuresPerAxle(deviceId) {
|
|
1764
|
+
const deviceData = getDeviceData(deviceId);
|
|
1765
|
+
const result = await this.readCommand(deviceData, subCommandIds.pressurePerAxle);
|
|
1766
|
+
const structurizedData = bridgeTools.convertBytesToStructure(
|
|
1767
|
+
bridgeCommandStructures.pressuresPerAxle.structure,
|
|
1768
|
+
result
|
|
1769
|
+
);
|
|
1770
|
+
return structurizedData;
|
|
1771
|
+
},
|
|
1772
|
+
async setPressuresPerAxle(deviceId, structurizedPayload) {
|
|
1773
|
+
const deviceData = getDeviceData(deviceId);
|
|
1774
|
+
const payload = bridgeTools.convertStructureToBytes(
|
|
1775
|
+
bridgeCommandStructures.pressuresPerAxle.structure,
|
|
1776
|
+
structurizedPayload
|
|
1777
|
+
);
|
|
1778
|
+
return await this.writeCommand(deviceData, commandIds.writeData, subCommandIds.pressurePerAxle, payload);
|
|
1779
|
+
},
|
|
1780
|
+
// // async setPressureThresholds(device: BleBridge, rules: any) {
|
|
1781
|
+
// // // https://tirecheck.atlassian.net/wiki/spaces/HWPRG/pages/6547767302/Technical+Design+Document+BLE+CAN+Bridge+Krone#Example.1
|
|
1782
|
+
// // let numberArray = [
|
|
1783
|
+
// // rules?.underinflation?.valueCritical || null,
|
|
1784
|
+
// // rules?.underinflation?.valueWarning || null,
|
|
1785
|
+
// // rules?.overinflation?.valueWarning || null,
|
|
1786
|
+
// // rules?.overinflation?.valueCritical || null,
|
|
1787
|
+
// // ]
|
|
1788
|
+
// // if (numberArray.includes(null)) {
|
|
1789
|
+
// // const defaultRules = userStore.maybeTcAccount?.ruleset || { underinflation: {}, overinflation: {} }
|
|
1790
|
+
// // const defaultArray = [
|
|
1791
|
+
// // defaultRules?.underinflation?.valueCritical || 100,
|
|
1792
|
+
// // defaultRules?.underinflation?.valueWarning || 90,
|
|
1793
|
+
// // defaultRules?.overinflation?.valueWarning || 90,
|
|
1794
|
+
// // defaultRules?.overinflation?.valueCritical || 100, // default values when threshold is not set should be discussed with managenemt
|
|
1795
|
+
// // ]
|
|
1796
|
+
// // numberArray = numberArray.map((x, index) => (x === null ? defaultArray[index] : x))
|
|
1797
|
+
// // }
|
|
1798
|
+
// // const allAxles = Array(15).fill(numberArray).flat()
|
|
1799
|
+
// // await this.writeCommand(device, commandIds.writeData, subCommandIds.customerPressureThresholds, allAxles)
|
|
1800
|
+
// // },
|
|
1801
|
+
async getVehicleLayout(deviceId) {
|
|
1802
|
+
const deviceData = getDeviceData(deviceId);
|
|
1803
|
+
const result = await this.readCommand(deviceData, subCommandIds.vehicleLayout);
|
|
1804
|
+
return result;
|
|
1805
|
+
},
|
|
1806
|
+
async getSensorMeasurement(deviceId, positionId) {
|
|
1807
|
+
const deviceData = getDeviceData(deviceId);
|
|
1808
|
+
const isSpare = String(positionId).endsWith("0");
|
|
1809
|
+
const positionInfo = bridgeTools.getPositionInfo(positionId);
|
|
1810
|
+
const { axlePosition, tyrePosition, isTwinTyre } = positionInfo;
|
|
1811
|
+
if (axlePosition == null || isTwinTyre == null || tyrePosition == null) {
|
|
1812
|
+
throw new Error(`Invalid positionId ${positionId}`);
|
|
1813
|
+
}
|
|
1814
|
+
const result = await this.writeCommand(
|
|
1815
|
+
deviceData,
|
|
1816
|
+
commandIds.sensorMeasurement,
|
|
1817
|
+
subCommandIds.sensorMeasurementPerWheel(axlePosition, tyrePosition, isTwinTyre, isSpare),
|
|
1818
|
+
[]
|
|
1819
|
+
);
|
|
1820
|
+
return result;
|
|
1821
|
+
},
|
|
1822
|
+
async readCommand(device, subCommandId) {
|
|
1823
|
+
const commandLength = 2;
|
|
1824
|
+
const result = await this.promisify(device, [
|
|
1825
|
+
...this.getCommandHeader(device),
|
|
1826
|
+
commandLength,
|
|
1827
|
+
commandIds.readData,
|
|
1828
|
+
subCommandId
|
|
1829
|
+
]);
|
|
1830
|
+
const data = result.slice(19, result.length - 8);
|
|
1831
|
+
return data;
|
|
1832
|
+
},
|
|
1833
|
+
async writeCommand(device, commandId, subCommandId, payload) {
|
|
1834
|
+
const commandHead = this.getCommandHeader(device);
|
|
1835
|
+
const macAddress = [0, 0, 0, 0, 0, 0];
|
|
1836
|
+
const timestamp = bridgeTools.decimalToHex(Math.floor(Date.now() / 1e3));
|
|
1837
|
+
const parsedTimestamp = timestamp.match(/.{2}/g)?.map((x) => Number.parseInt(x, 16));
|
|
1838
|
+
if (parsedTimestamp?.length !== 4) throw new Error("Wrong timestamp format");
|
|
1839
|
+
const paddedTimestamp = [...parsedTimestamp, 0, 0, 0, 0];
|
|
1840
|
+
const crc = bridgeSecurity.getCrcArray([...macAddress, ...paddedTimestamp, ...payload]);
|
|
1841
|
+
if (crc.length !== 4) throw new Error("Wrong crc length");
|
|
1842
|
+
const commandLength = macAddress.length + paddedTimestamp.length + crc.length + payload.length + 2;
|
|
1843
|
+
const writeCommand = [
|
|
1844
|
+
...commandHead,
|
|
1845
|
+
commandLength,
|
|
1846
|
+
commandId,
|
|
1847
|
+
subCommandId,
|
|
1848
|
+
...macAddress,
|
|
1849
|
+
...paddedTimestamp,
|
|
1850
|
+
...payload,
|
|
1851
|
+
...crc
|
|
1852
|
+
];
|
|
1853
|
+
if ([subCommandIds.vehicleLayout, subCommandIds.customerCanSettings, subCommandIds.workshopCanSettings].includes(
|
|
1854
|
+
subCommandId
|
|
1855
|
+
)) {
|
|
1856
|
+
await this.setBridgeToRestart(device.id);
|
|
1857
|
+
}
|
|
1858
|
+
return await this.promisify(device, writeCommand);
|
|
1859
|
+
},
|
|
1860
|
+
async promisify(device, writeCommand) {
|
|
1861
|
+
if (!writeCommand.every((n) => ___default.isNumber(n))) {
|
|
1862
|
+
console.error("invallid command", writeCommand);
|
|
1863
|
+
throw new Error("Invalid command");
|
|
1864
|
+
}
|
|
1865
|
+
if (!canCommunicateWith(device.id)) throw new Error("Bridge not connected");
|
|
1866
|
+
clearTimeout(keepAliveTimer);
|
|
1867
|
+
keepAliveTimer = setTimeout(() => {
|
|
1868
|
+
if (!canCommunicateWith(device.id)) {
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
this.sendKeepAliveCommand(device);
|
|
1872
|
+
}, 1e4);
|
|
1873
|
+
return promiseQueue$1.enqueue(device, writeCommand);
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
const otaServiceUuid = "1d14d6ee-fd63-4fa1-bfa4-8f47b42119f0";
|
|
1878
|
+
const otaControlCharacteristicUuid = "f7bf3564-fb6d-4e53-88a4-5e37e0326063";
|
|
1879
|
+
const otaDataCharacteristicUuid = "984227f3-34fc-4045-a5d0-2c581f81a153";
|
|
1880
|
+
const bridgeOtaCommands = {
|
|
1881
|
+
async beginOta(deviceId) {
|
|
1882
|
+
await bluetooth.write(deviceId, otaServiceUuid, otaControlCharacteristicUuid, new Uint8Array([0]).buffer);
|
|
1883
|
+
},
|
|
1884
|
+
async uploadOtaChunk(deviceId, data) {
|
|
1885
|
+
await bluetooth.write(deviceId, otaServiceUuid, otaDataCharacteristicUuid, data);
|
|
1886
|
+
},
|
|
1887
|
+
async endOta(deviceId) {
|
|
1888
|
+
await bluetooth.write(deviceId, otaServiceUuid, otaControlCharacteristicUuid, new Uint8Array([3]).buffer);
|
|
1889
|
+
}
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
const mtu = 180;
|
|
1893
|
+
const bridgeOtaService = {
|
|
1894
|
+
async updateFirmware(deviceId, bootloader, firmware, progressCallback) {
|
|
1895
|
+
await delay(2e3);
|
|
1896
|
+
progressCallback("Connecting to the bridge...", 0.1);
|
|
1897
|
+
await bridgeOta.connect(deviceId);
|
|
1898
|
+
progressCallback("Uploading bootloader...", 0.12);
|
|
1899
|
+
await bridgeOtaCommands.beginOta(deviceId);
|
|
1900
|
+
await uploadOta(deviceId, bootloader, (str, percents) => progressCallback(str, 0.12 + percents * 0.2));
|
|
1901
|
+
await bridgeOtaCommands.endOta(deviceId);
|
|
1902
|
+
progressCallback("Uploading application...", 0.32);
|
|
1903
|
+
await bridgeOtaCommands.beginOta(deviceId);
|
|
1904
|
+
await uploadOta(deviceId, firmware, (str, percents) => progressCallback(str, 0.32 + percents * 0.5));
|
|
1905
|
+
await bridgeOtaCommands.endOta(deviceId);
|
|
1906
|
+
progressCallback("Upload completed, disconnecting...", 0.81);
|
|
1907
|
+
await bridgeOta.disconnect(deviceId);
|
|
1908
|
+
progressCallback("Disconnected...", 0.82);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
async function uploadOta(deviceId, firmwareBinary, reportStatus) {
|
|
1912
|
+
let uploadedBytes = 0;
|
|
1913
|
+
let chunkIndex = 0;
|
|
1914
|
+
while (uploadedBytes < firmwareBinary.byteLength) {
|
|
1915
|
+
if (chunkIndex % 100 === 0)
|
|
1916
|
+
reportStatus(
|
|
1917
|
+
`Uploading new firmware... (${___default.round(uploadedBytes / 1e3)} / ${___default.round(
|
|
1918
|
+
firmwareBinary.byteLength / 1e3
|
|
1919
|
+
)} KB)`,
|
|
1920
|
+
uploadedBytes / firmwareBinary.byteLength
|
|
1921
|
+
);
|
|
1922
|
+
const chunkSize = Math.min(mtu - 3, firmwareBinary.byteLength - uploadedBytes);
|
|
1923
|
+
const chunk = firmwareBinary.slice(uploadedBytes, uploadedBytes + chunkSize);
|
|
1924
|
+
await bridgeOtaCommands.uploadOtaChunk(deviceId, chunk);
|
|
1925
|
+
uploadedBytes += chunkSize;
|
|
1926
|
+
chunkIndex++;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
const bridgeOta = {
|
|
1931
|
+
async connect(deviceId) {
|
|
1932
|
+
await bluetooth.connect(deviceId, this.disconnect);
|
|
1933
|
+
store.deviceState[deviceId] = "paired";
|
|
1934
|
+
},
|
|
1935
|
+
async disconnect(deviceId) {
|
|
1936
|
+
store.deviceState[deviceId] = "disconnecting";
|
|
1937
|
+
await bluetooth.disconnect(deviceId);
|
|
1938
|
+
store.deviceState[deviceId] = "disconnected";
|
|
1939
|
+
},
|
|
1940
|
+
async updateFirmware(deviceId, bootloader, firmware, progressCallback) {
|
|
1941
|
+
return bridgeOtaService.updateFirmware(deviceId, bootloader, firmware, progressCallback);
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
const vehicleLayoutAxleTypes = {
|
|
1946
|
+
noAxle: [0, 0],
|
|
1947
|
+
twoTyresAxle: [128, 2],
|
|
1948
|
+
twoTyresAxleSpare: [128, 3],
|
|
1949
|
+
fourTyresAxle: [192, 6],
|
|
1950
|
+
fourTyresAxleSpare: [192, 7],
|
|
1951
|
+
spareTyreAxle: [1, 0]
|
|
1952
|
+
};
|
|
1953
|
+
const bridgeService = {
|
|
1954
|
+
updateFirmware,
|
|
1955
|
+
getVehicle,
|
|
1956
|
+
setVehicle,
|
|
1957
|
+
getConfiguration,
|
|
1958
|
+
setConfiguration,
|
|
1959
|
+
getSensorReading,
|
|
1960
|
+
getVehicleReadings,
|
|
1961
|
+
resetAutolearnStatuses,
|
|
1962
|
+
getAutolearnStatuses
|
|
1963
|
+
};
|
|
1964
|
+
async function updateFirmware(deviceId, bootloader, firmware, reportStatus) {
|
|
1965
|
+
reportStatus("Sending OTA Request...", 0.02);
|
|
1966
|
+
await bridgeCommands.sendOtaRequest(deviceId);
|
|
1967
|
+
await toolsSvc.delay(2e3);
|
|
1968
|
+
await bluetooth.scanDevices();
|
|
1969
|
+
reportStatus("Discovering OTA Device...", 0.02);
|
|
1970
|
+
await discoverOtaBridge(deviceId, (str, percents) => reportStatus(str, 0.02 + percents * 0.1));
|
|
1971
|
+
await bridgeOta.updateFirmware(deviceId, bootloader, firmware, reportStatus);
|
|
1972
|
+
reportStatus("Update completed", 1);
|
|
1973
|
+
await waitForBridgeToReconnect(deviceId, (str, percents) => reportStatus(str, 0.85 + percents * 0.15));
|
|
1974
|
+
reportStatus("Update finished!", 1);
|
|
1975
|
+
}
|
|
1976
|
+
async function setVehicleLayout(deviceId, tcVehicle) {
|
|
1977
|
+
const spareTyres = tcVehicle.tcTyres?.filter((tyre) => String(tyre.mountedOn?.positionId).endsWith("0"));
|
|
1978
|
+
let spareTyresCount = Math.min(spareTyres.length || 0, 2);
|
|
1979
|
+
let layout = [];
|
|
1980
|
+
for (let index = 0; index < 15; index++) {
|
|
1981
|
+
const tyresCount = tcVehicle.axles && tcVehicle.axles[index]?.tyresCount;
|
|
1982
|
+
if (tyresCount === 2) {
|
|
1983
|
+
let value = vehicleLayoutAxleTypes.twoTyresAxle;
|
|
1984
|
+
if (spareTyresCount) {
|
|
1985
|
+
value = vehicleLayoutAxleTypes.twoTyresAxleSpare;
|
|
1986
|
+
spareTyresCount -= 1;
|
|
1987
|
+
}
|
|
1988
|
+
layout = [...layout, ...value];
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
if (tyresCount === 4) {
|
|
1992
|
+
let value = vehicleLayoutAxleTypes.fourTyresAxle;
|
|
1993
|
+
if (spareTyresCount) {
|
|
1994
|
+
value = vehicleLayoutAxleTypes.fourTyresAxleSpare;
|
|
1995
|
+
spareTyresCount -= 1;
|
|
1996
|
+
}
|
|
1997
|
+
layout = [...layout, ...value];
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
layout = [...layout, ...vehicleLayoutAxleTypes.noAxle];
|
|
2001
|
+
}
|
|
2002
|
+
let vin = tcVehicle.vin ? tcVehicle.vin.split("").map((x) => x.charCodeAt(0)) : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
2003
|
+
if (vin.length !== 17) {
|
|
2004
|
+
throw new Error(`Incorrect VIN length: ${vin}`);
|
|
2005
|
+
}
|
|
2006
|
+
if (isVersionGreaterThan(deviceId, "1.0.2")) {
|
|
2007
|
+
vin = [...vin, 0];
|
|
2008
|
+
}
|
|
2009
|
+
await bridgeCommands.setVehicleLayout(deviceId, [...layout, ...vin]);
|
|
2010
|
+
}
|
|
2011
|
+
async function getConfiguration(deviceId) {
|
|
2012
|
+
const customerCANSettings = await bridgeCommands.getCustomerCANSettings(deviceId);
|
|
2013
|
+
const workshopCANSettings = await bridgeCommands.getWorkshopCANSettings(deviceId);
|
|
2014
|
+
const bridgeConfiguration = await bridgeCommands.getBridgeConfiguration(deviceId);
|
|
2015
|
+
const customerPressureThresholds = await bridgeCommands.getCustomerPressureThresholds(deviceId);
|
|
2016
|
+
const customerTemperatureThresholds = await bridgeCommands.getCustomerTemperatureThresholds(deviceId);
|
|
2017
|
+
const customerImbalanceThresholds = await bridgeCommands.getCustomerImbalanceThresholds(deviceId);
|
|
2018
|
+
const pressuresPerAxle = await bridgeCommands.getPressuresPerAxle(deviceId);
|
|
2019
|
+
let autolearnSettings;
|
|
2020
|
+
if (isVersionGreaterThan(deviceId, "0.9.7")) {
|
|
2021
|
+
autolearnSettings = await bridgeCommands.getAutolearnSettings(deviceId);
|
|
2022
|
+
}
|
|
2023
|
+
return {
|
|
2024
|
+
customerCANSettings,
|
|
2025
|
+
workshopCANSettings,
|
|
2026
|
+
bridgeConfiguration,
|
|
2027
|
+
customerPressureThresholds,
|
|
2028
|
+
customerTemperatureThresholds,
|
|
2029
|
+
customerImbalanceThresholds,
|
|
2030
|
+
pressuresPerAxle,
|
|
2031
|
+
autolearnSettings
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
function bridgeVehiclesDifference(original, change) {
|
|
2035
|
+
const differences = [];
|
|
2036
|
+
if (original.tcBridge?.id !== change.tcBridge?.id) differences.push("bridgeId");
|
|
2037
|
+
if (original.vin !== change.vin) differences.push("vin");
|
|
2038
|
+
const originalTyres = original.tcTyres.map((t) => {
|
|
2039
|
+
return { positionId: t.mountedOn?.positionId, sensorId: t.tcTpmsSensor?.id || null };
|
|
2040
|
+
});
|
|
2041
|
+
const changeTyres = change.tcTyres?.map((t) => {
|
|
2042
|
+
return { positionId: t.mountedOn?.positionId, sensorId: t.tcTpmsSensor?.id || null };
|
|
2043
|
+
});
|
|
2044
|
+
if (!___default.isEqual(___default.sortBy(changeTyres, "positionId"), ___default.sortBy(originalTyres, "positionId")))
|
|
2045
|
+
differences.push("sensorPosition");
|
|
2046
|
+
const originalAxlesPressure = original.axles.map((a) => {
|
|
2047
|
+
return {
|
|
2048
|
+
targetPressure: a.targetPressure,
|
|
2049
|
+
maxTargetPressure: a.maxTargetPressure,
|
|
2050
|
+
minTargetPressure: a.minTargetPressure
|
|
2051
|
+
};
|
|
2052
|
+
});
|
|
2053
|
+
const changeAxlesPressure = change.axles.map((a) => {
|
|
2054
|
+
return {
|
|
2055
|
+
targetPressure: a.targetPressure,
|
|
2056
|
+
maxTargetPressure: a.maxTargetPressure,
|
|
2057
|
+
minTargetPressure: a.minTargetPressure
|
|
2058
|
+
};
|
|
2059
|
+
});
|
|
2060
|
+
if (!___default.isEqual(changeAxlesPressure, originalAxlesPressure)) differences.push("axlePressure");
|
|
2061
|
+
const originalAxleType = original.axles.map((a) => {
|
|
2062
|
+
return {
|
|
2063
|
+
isSteer: a.isSteer,
|
|
2064
|
+
isSpare: a.isSpare,
|
|
2065
|
+
isLift: a.isLift
|
|
2066
|
+
};
|
|
2067
|
+
});
|
|
2068
|
+
const changeAxleType = change.axles.map((a) => {
|
|
2069
|
+
return {
|
|
2070
|
+
isSteer: a.isSteer,
|
|
2071
|
+
isSpare: a.isSpare,
|
|
2072
|
+
isLift: a.isLift
|
|
2073
|
+
};
|
|
2074
|
+
});
|
|
2075
|
+
if (!___default.isEqual(changeAxleType, originalAxleType)) differences.push("axleType");
|
|
2076
|
+
const originalTyresCount = original.axles.map((a) => a.tyresCount);
|
|
2077
|
+
const changeTyresCount = change.axles.map((a) => a.tyresCount);
|
|
2078
|
+
if (!___default.isEqual(originalTyresCount, changeTyresCount)) differences.push("vehicleLayout");
|
|
2079
|
+
return { isDifferent: !!differences.length, differences };
|
|
2080
|
+
}
|
|
2081
|
+
async function bridgeConfigurationDifference(original, change) {
|
|
2082
|
+
const propertiesToCheck = [
|
|
2083
|
+
"customerCANSettings",
|
|
2084
|
+
"workshopCANSettings",
|
|
2085
|
+
"customerPressureThresholds",
|
|
2086
|
+
"customerTemperatureThresholds",
|
|
2087
|
+
"customerImbalanceThresholds",
|
|
2088
|
+
"pressuresPerAxle"
|
|
2089
|
+
];
|
|
2090
|
+
const differentProps = [];
|
|
2091
|
+
for (const prop of propertiesToCheck) {
|
|
2092
|
+
for (const key in change[prop]) {
|
|
2093
|
+
if (original[prop][key] !== change[prop][key]) {
|
|
2094
|
+
differentProps.push(prop);
|
|
2095
|
+
break;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return { isDifferent: !!differentProps.length, differences: differentProps };
|
|
2100
|
+
}
|
|
2101
|
+
function getVehicleAxlesTypes(tcVehicle) {
|
|
2102
|
+
const filteredAxles = tcVehicle?.axles?.filter((axle) => !axle.isSpare);
|
|
2103
|
+
const axleTypesBinary = (filteredAxles || []).map((axle) => {
|
|
2104
|
+
let binaryRepresentation = 1;
|
|
2105
|
+
if (axle?.isLift) {
|
|
2106
|
+
binaryRepresentation |= 2;
|
|
2107
|
+
}
|
|
2108
|
+
if (axle?.isSteer) {
|
|
2109
|
+
binaryRepresentation |= 8;
|
|
2110
|
+
}
|
|
2111
|
+
if (axle?.isDrive) {
|
|
2112
|
+
binaryRepresentation |= 16;
|
|
2113
|
+
}
|
|
2114
|
+
return bridgeTools.decimalToHex(binaryRepresentation);
|
|
2115
|
+
});
|
|
2116
|
+
return axleTypesBinary;
|
|
2117
|
+
}
|
|
2118
|
+
function getEmptyAxles() {
|
|
2119
|
+
const defaultAxles = {};
|
|
2120
|
+
___default.range(1, 16).forEach((n) => {
|
|
2121
|
+
const key = `axle${___default.padStart(n.toString(), 2, "0")}`;
|
|
2122
|
+
defaultAxles[key] = "00";
|
|
2123
|
+
});
|
|
2124
|
+
return defaultAxles;
|
|
2125
|
+
}
|
|
2126
|
+
function assignAxleTypes(defaultAxles, axleTypes) {
|
|
2127
|
+
for (let i = 0; i < Object.keys(defaultAxles).length && i < axleTypes?.length; i++) {
|
|
2128
|
+
const key = Object.keys(defaultAxles)[i];
|
|
2129
|
+
defaultAxles[key] = axleTypes[i];
|
|
2130
|
+
}
|
|
2131
|
+
return defaultAxles;
|
|
2132
|
+
}
|
|
2133
|
+
async function getAxlesWithAutolearnSettings(deviceId) {
|
|
2134
|
+
const defaultAxles = getEmptyAxles();
|
|
2135
|
+
const autolearnSettings = await bridgeCommands.getAutolearnSettings(deviceId);
|
|
2136
|
+
const pickedProperties = ___default.pick(autolearnSettings, [
|
|
2137
|
+
"checkingPeriod",
|
|
2138
|
+
"isAutolearnEnabled",
|
|
2139
|
+
"multiIdTimeout",
|
|
2140
|
+
"temperatureLimit"
|
|
2141
|
+
]);
|
|
2142
|
+
Object.assign(defaultAxles, pickedProperties);
|
|
2143
|
+
return defaultAxles;
|
|
2144
|
+
}
|
|
2145
|
+
async function setAxleInfo(deviceId, tcVehicle) {
|
|
2146
|
+
if (!tcVehicle.axles?.length) throw new Error("Vehicle has no axles");
|
|
2147
|
+
const spareTyres = tcVehicle.tcTyres?.filter((t) => String(t.mountedOn?.positionId).endsWith("0")) || [];
|
|
2148
|
+
for (let axleIndex = 0; axleIndex < tcVehicle.axles.length; axleIndex++) {
|
|
2149
|
+
const tyres = [
|
|
2150
|
+
"00000000",
|
|
2151
|
+
"00000000",
|
|
2152
|
+
"00000000",
|
|
2153
|
+
"00000000",
|
|
2154
|
+
"00000000",
|
|
2155
|
+
"00000000",
|
|
2156
|
+
// LO
|
|
2157
|
+
"00000000",
|
|
2158
|
+
// LI
|
|
2159
|
+
"00000000",
|
|
2160
|
+
// null position
|
|
2161
|
+
"00000000",
|
|
2162
|
+
// RI
|
|
2163
|
+
"00000000",
|
|
2164
|
+
// RO
|
|
2165
|
+
"00000000",
|
|
2166
|
+
"00000000",
|
|
2167
|
+
"00000000",
|
|
2168
|
+
"00000000",
|
|
2169
|
+
"00000000"
|
|
2170
|
+
];
|
|
2171
|
+
const axle = tcVehicle.axles[axleIndex];
|
|
2172
|
+
const spareTyre = spareTyres[axleIndex];
|
|
2173
|
+
if (spareTyre && spareTyre.tcTpmsSensor?.id) {
|
|
2174
|
+
tyres[7] = bridgeTools.convertSensorIdForBridge(spareTyre.tcTpmsSensor?.id);
|
|
2175
|
+
}
|
|
2176
|
+
const axleTyres = tcVehicle.tcTyres?.filter(
|
|
2177
|
+
(t) => t.mountedOn?.positionId ? bridgeTools.getPositionInfo(t.mountedOn?.positionId).axlePosition === axleIndex + 1 && spareTyre?.mountedOn?.positionId !== t.mountedOn?.positionId : false
|
|
2178
|
+
) || [];
|
|
2179
|
+
for (let tyreIndex = 0; tyreIndex < axleTyres.length; tyreIndex++) {
|
|
2180
|
+
const sensorId = axleTyres[tyreIndex].tcTpmsSensor?.id;
|
|
2181
|
+
if (!sensorId) continue;
|
|
2182
|
+
if (axle.isSpare) continue;
|
|
2183
|
+
const isTwinTyre = axle.tyresCount === 4;
|
|
2184
|
+
let bridgeTyreIndex = 0;
|
|
2185
|
+
if (isTwinTyre && tyreIndex === 0) bridgeTyreIndex = 5;
|
|
2186
|
+
if (isTwinTyre && tyreIndex === 1 || !isTwinTyre && tyreIndex === 0) bridgeTyreIndex = 6;
|
|
2187
|
+
if (isTwinTyre && tyreIndex === 3) bridgeTyreIndex = 9;
|
|
2188
|
+
if (isTwinTyre && tyreIndex === 2 || !isTwinTyre && tyreIndex === 1) bridgeTyreIndex = 8;
|
|
2189
|
+
tyres[bridgeTyreIndex] = bridgeTools.convertSensorIdForBridge(sensorId);
|
|
2190
|
+
}
|
|
2191
|
+
if (!___default.inRange(axleIndex, 0, 16)) return;
|
|
2192
|
+
const numberArray = tyres.map((x) => x.match(/.{2}/g)?.reverse()).flat().map((n) => n ? Number.parseInt(n, 16) : 0);
|
|
2193
|
+
await bridgeCommands.setAxleInfo(deviceId, axleIndex, numberArray);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async function setAxlesPressure(deviceId, axles) {
|
|
2197
|
+
let data = [];
|
|
2198
|
+
let bridgeAxlesPressureData;
|
|
2199
|
+
for (let index = 0; index < 15; index++) {
|
|
2200
|
+
let { targetPressure, maxTargetPressure, minTargetPressure, isSpare } = axles[index] || {};
|
|
2201
|
+
maxTargetPressure = bridgeTools.barToKpaByte(maxTargetPressure);
|
|
2202
|
+
minTargetPressure = bridgeTools.barToKpaByte(minTargetPressure);
|
|
2203
|
+
if (isSpare && axles[0]) {
|
|
2204
|
+
targetPressure = bridgeTools.barToKpaByte(axles[0].targetPressure);
|
|
2205
|
+
} else {
|
|
2206
|
+
targetPressure = bridgeTools.barToKpaByte(targetPressure);
|
|
2207
|
+
}
|
|
2208
|
+
if (!maxTargetPressure) {
|
|
2209
|
+
if (!bridgeAxlesPressureData) {
|
|
2210
|
+
bridgeAxlesPressureData = await bridgeCommands.getAxlesPressure(deviceId);
|
|
2211
|
+
}
|
|
2212
|
+
maxTargetPressure = bridgeAxlesPressureData[index * 3] || 255;
|
|
2213
|
+
}
|
|
2214
|
+
if (targetPressure > maxTargetPressure) {
|
|
2215
|
+
targetPressure = maxTargetPressure;
|
|
2216
|
+
} else if (targetPressure < minTargetPressure) {
|
|
2217
|
+
targetPressure = minTargetPressure;
|
|
2218
|
+
}
|
|
2219
|
+
data = data.concat([maxTargetPressure, minTargetPressure, targetPressure]);
|
|
2220
|
+
}
|
|
2221
|
+
await bridgeCommands.setAxlesPressure(deviceId, data);
|
|
2222
|
+
}
|
|
2223
|
+
async function setConfiguration(deviceId, bridgeConfiguration) {
|
|
2224
|
+
const currentBridgeConfiguration = await getConfiguration(deviceId);
|
|
2225
|
+
const configurationValidation = await bridgeConfigurationDifference(currentBridgeConfiguration, bridgeConfiguration);
|
|
2226
|
+
if (configurationValidation.differences.includes("workshopCANSettings")) {
|
|
2227
|
+
await bridgeCommands.setWorkshopCANSettings(deviceId, bridgeConfiguration.workshopCANSettings);
|
|
2228
|
+
}
|
|
2229
|
+
if (bridgeConfiguration.customerCANSettings) {
|
|
2230
|
+
const isFactory = await bridgeCommands.getCustomerCrcSettings(deviceId);
|
|
2231
|
+
if (configurationValidation.differences.includes("customerCANSettings") || isFactory) {
|
|
2232
|
+
await bridgeCommands.setCustomerCANSettings(deviceId, bridgeConfiguration.customerCANSettings);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if (configurationValidation.differences.includes("customerPressureThresholds")) {
|
|
2236
|
+
await bridgeCommands.setCustomerPressureThresholds(deviceId, bridgeConfiguration.customerPressureThresholds);
|
|
2237
|
+
}
|
|
2238
|
+
if (configurationValidation.differences.includes("customerTemperatureThresholds")) {
|
|
2239
|
+
await bridgeCommands.setCustomerTemperatureThresholds(deviceId, bridgeConfiguration.customerTemperatureThresholds);
|
|
2240
|
+
}
|
|
2241
|
+
if (configurationValidation.differences.includes("customerImbalanceThresholds")) {
|
|
2242
|
+
await bridgeCommands.setCustomerImbalanceThresholds(deviceId, bridgeConfiguration.customerImbalanceThresholds);
|
|
2243
|
+
}
|
|
2244
|
+
if (configurationValidation.differences.includes("pressuresPerAxle")) {
|
|
2245
|
+
await bridgeCommands.setPressuresPerAxle(deviceId, bridgeConfiguration.pressuresPerAxle);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
async function setVehicle(deviceId, tcVehicle) {
|
|
2249
|
+
const currentTcVehicle = await getVehicle(deviceId);
|
|
2250
|
+
const vehicleValidation = bridgeVehiclesDifference(currentTcVehicle, tcVehicle);
|
|
2251
|
+
if (["bridgeId", "vehicleLayout", "vin"].some((n) => vehicleValidation.differences.includes(n))) {
|
|
2252
|
+
await setVehicleLayout(deviceId, tcVehicle);
|
|
2253
|
+
}
|
|
2254
|
+
if (["bridgeId", "sensorPosition"].some((n) => vehicleValidation.differences.includes(n))) {
|
|
2255
|
+
await setAxleInfo(deviceId, tcVehicle);
|
|
2256
|
+
}
|
|
2257
|
+
if (["bridgeId", "axlePressure"].some((n) => vehicleValidation.differences.includes(n))) {
|
|
2258
|
+
await setAxlesPressure(deviceId, tcVehicle.axles);
|
|
2259
|
+
}
|
|
2260
|
+
if (isVersionGreaterThan(deviceId, "0.9.7")) {
|
|
2261
|
+
if (["bridgeId", "axleType"].some((n) => vehicleValidation.differences.includes(n))) {
|
|
2262
|
+
const defaultAxles = await getAxlesWithAutolearnSettings(deviceId);
|
|
2263
|
+
const axleTypes = getVehicleAxlesTypes(tcVehicle);
|
|
2264
|
+
const updatedAxles = assignAxleTypes(defaultAxles, axleTypes);
|
|
2265
|
+
await bridgeCommands.setAutoLearnSettings(deviceId, updatedAxles);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
async function getSensorReading(deviceId, positionId) {
|
|
2270
|
+
const value = await bridgeCommands.getSensorMeasurement(deviceId, positionId);
|
|
2271
|
+
const sensorId = value.slice(5, 9).reverse().map((s) => bridgeTools.decimalToHex(s).toUpperCase()).join("");
|
|
2272
|
+
const correctedSensorId = bridgeTools.convertSensorIdFromBridge(sensorId);
|
|
2273
|
+
return {
|
|
2274
|
+
date: Date.now() - value[14] * 2e3,
|
|
2275
|
+
sensorId: correctedSensorId,
|
|
2276
|
+
sensorStatus: value[12],
|
|
2277
|
+
eceStatus: value[13],
|
|
2278
|
+
pressureIssue: getPressureIssue(deviceId, value[13]),
|
|
2279
|
+
...getPressureAndTemperatureReadingObjects(value[9], value[10])
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
async function getVehicleReadings(deviceId, tcVehicle) {
|
|
2283
|
+
const readings = [];
|
|
2284
|
+
try {
|
|
2285
|
+
for (const vehicleTyre of tcVehicle.tcTyres) {
|
|
2286
|
+
if (vehicleTyre.tcTpmsSensor && vehicleTyre.mountedOn?.positionId) {
|
|
2287
|
+
if (!canCommunicateWith(deviceId)) return;
|
|
2288
|
+
const reading = await getSensorReading(deviceId, vehicleTyre.mountedOn.positionId);
|
|
2289
|
+
readings.push(reading);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
console.error("getSensorReading failed: ", error);
|
|
2294
|
+
}
|
|
2295
|
+
return readings;
|
|
2296
|
+
}
|
|
2297
|
+
async function getAutolearnStatuses(deviceId, tcVehicle) {
|
|
2298
|
+
if (!isVersionGreaterThan(deviceId, "0.9.7"))
|
|
2299
|
+
throw new Error("getAutolearnStatuses is not supported by this firmware version");
|
|
2300
|
+
const statuses = await bridgeCommands.getAutolearnIdStatus(deviceId);
|
|
2301
|
+
const autolearnedStatuses = [];
|
|
2302
|
+
const statusEntries = Object.values(statuses);
|
|
2303
|
+
for (const tcTyre of tcVehicle.tcTyres) {
|
|
2304
|
+
if (!tcTyre.mountedOn?.positionId || tcTyre.mountedOn?.positionId % 10 === 0) continue;
|
|
2305
|
+
const axleIndex = Math.floor(tcTyre.mountedOn?.positionId / 100) - 1;
|
|
2306
|
+
const axleStatuses = statusEntries[axleIndex];
|
|
2307
|
+
const tyreIndex = getBridgeIndexFromPositionId(tcTyre.mountedOn.positionId, 7);
|
|
2308
|
+
const tyreStatus = axleStatuses.split("").reverse().join("")[tyreIndex];
|
|
2309
|
+
let autolearnedSensorId;
|
|
2310
|
+
if (["1", "4", "9"].includes(tyreStatus)) {
|
|
2311
|
+
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axleIndex);
|
|
2312
|
+
const axleData = axleInfo.map((i) => bridgeTools.decimalToHex(i).toUpperCase());
|
|
2313
|
+
const joinedAxleData = Array.from(
|
|
2314
|
+
{ length: axleData.length / 4 },
|
|
2315
|
+
(_2, index) => axleData[4 * index + 3] + axleData[4 * index + 2] + axleData[4 * index + 1] + axleData[4 * index]
|
|
2316
|
+
);
|
|
2317
|
+
autolearnedSensorId = bridgeTools.convertSensorIdFromBridge(joinedAxleData[tyreIndex]);
|
|
2318
|
+
}
|
|
2319
|
+
autolearnedStatuses.push({
|
|
2320
|
+
positionId: tcTyre.mountedOn.positionId,
|
|
2321
|
+
autolearnedSensorId,
|
|
2322
|
+
autolearnedStatus: getAutolearnStatusName(tyreStatus)
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
return autolearnedStatuses;
|
|
2326
|
+
}
|
|
2327
|
+
function getAutolearnStatusName(statusId) {
|
|
2328
|
+
if (["2", "A"].includes(statusId)) {
|
|
2329
|
+
return "error";
|
|
2330
|
+
}
|
|
2331
|
+
if (statusId === "4") {
|
|
2332
|
+
return "known";
|
|
2333
|
+
}
|
|
2334
|
+
if (["1", "9"].includes(statusId)) {
|
|
2335
|
+
return "unknown";
|
|
2336
|
+
}
|
|
2337
|
+
if (statusId === "0") {
|
|
2338
|
+
return "default";
|
|
2339
|
+
}
|
|
2340
|
+
throw new Error("Unknown autolearn status");
|
|
2341
|
+
}
|
|
2342
|
+
async function resetAutolearnStatuses(deviceId, positionIds) {
|
|
2343
|
+
if (!isVersionGreaterThan(deviceId, "0.9.7"))
|
|
2344
|
+
throw new Error("resetAutolearnStatuses is not supported by this firmware version");
|
|
2345
|
+
const statuses = await bridgeCommands.getAutolearnIdStatus(deviceId);
|
|
2346
|
+
const statusesKeys = Object.keys(statuses);
|
|
2347
|
+
const sortedByAxle = ___default.groupBy(positionIds, (p) => Math.floor(p / 100));
|
|
2348
|
+
for (const [axleIndex, axlePositionIds] of Object.entries(sortedByAxle)) {
|
|
2349
|
+
const statusesAxleIndex = Number(axleIndex) - 1;
|
|
2350
|
+
for (const positionId of axlePositionIds) {
|
|
2351
|
+
const bitIndex = getBridgeIndexFromPositionId(positionId, 7);
|
|
2352
|
+
let status = statuses[statusesKeys[statusesAxleIndex]].split("").reverse().join("");
|
|
2353
|
+
status = `${status.substring(0, bitIndex)}0${status.substring(bitIndex + 1)}`;
|
|
2354
|
+
statuses[statusesKeys[statusesAxleIndex]] = status.split("").reverse().join("");
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
await bridgeCommands.setAutolearnIdStatus(deviceId, statuses);
|
|
2358
|
+
}
|
|
2359
|
+
function getBridgeIndexFromPositionId(positionId, centerIndex = 8) {
|
|
2360
|
+
const halfPoint = positionId % 10 * 6;
|
|
2361
|
+
const tyrePosition = positionId % 100;
|
|
2362
|
+
const result = (tyrePosition - halfPoint) / 10 - (tyrePosition <= halfPoint ? 1 : 0);
|
|
2363
|
+
const bitIndex = centerIndex + result;
|
|
2364
|
+
return bitIndex;
|
|
2365
|
+
}
|
|
2366
|
+
function getPressureAndTemperatureReadingObjects(rawPressure, rawTemperature) {
|
|
2367
|
+
if (rawTemperature === 255 || rawPressure === 0) {
|
|
2368
|
+
return {
|
|
2369
|
+
pressure: void 0,
|
|
2370
|
+
temperature: void 0
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
const ambientTemperature = 20;
|
|
2374
|
+
const measuredTemperature = rawTemperature - 40;
|
|
2375
|
+
const measuredPressureAbsolute = rawPressure / 19.753086;
|
|
2376
|
+
const compensatedPressureAbsolute = measuredPressureAbsolute * (273 + ambientTemperature) / (273 + measuredTemperature);
|
|
2377
|
+
return {
|
|
2378
|
+
pressure: {
|
|
2379
|
+
raw: rawPressure,
|
|
2380
|
+
meas: Math.max(0, measuredPressureAbsolute - 1),
|
|
2381
|
+
bar: Math.max(0, compensatedPressureAbsolute - 1)
|
|
2382
|
+
},
|
|
2383
|
+
temperature: { raw: rawTemperature, amb: ambientTemperature, celsius: measuredTemperature }
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
function getPressureIssue(deviceId, eceStatus) {
|
|
2387
|
+
if (eceStatus === 254) {
|
|
2388
|
+
return {
|
|
2389
|
+
date: Date.now(),
|
|
2390
|
+
tcIssue: {
|
|
2391
|
+
id: "outdatedCritical"
|
|
2392
|
+
},
|
|
2393
|
+
type: "outdated",
|
|
2394
|
+
severity: "critical"
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
const usesNewValues = isVersionGreaterThan(deviceId, "0.8.F");
|
|
2398
|
+
if (eceStatus === (usesNewValues ? 3 : 4)) {
|
|
2399
|
+
return {
|
|
2400
|
+
date: Date.now(),
|
|
2401
|
+
tcIssue: {
|
|
2402
|
+
id: "overinflationCritical"
|
|
2403
|
+
},
|
|
2404
|
+
type: "over_inflated",
|
|
2405
|
+
severity: "critical"
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
if (eceStatus === (usesNewValues ? 1 : 3)) {
|
|
2409
|
+
return {
|
|
2410
|
+
date: Date.now(),
|
|
2411
|
+
tcIssue: {
|
|
2412
|
+
id: "overinflationWarning"
|
|
2413
|
+
},
|
|
2414
|
+
severity: "warning",
|
|
2415
|
+
type: "over_inflated"
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
if (eceStatus === (usesNewValues ? 4 : 2)) {
|
|
2419
|
+
return {
|
|
2420
|
+
date: Date.now(),
|
|
2421
|
+
tcIssue: {
|
|
2422
|
+
id: "underinflationCritical"
|
|
2423
|
+
},
|
|
2424
|
+
severity: "critical",
|
|
2425
|
+
type: "under_inflated"
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
if (eceStatus === (usesNewValues ? 2 : 1)) {
|
|
2429
|
+
return {
|
|
2430
|
+
date: Date.now(),
|
|
2431
|
+
tcIssue: {
|
|
2432
|
+
id: "underinflationWarning"
|
|
2433
|
+
},
|
|
2434
|
+
severity: "warning",
|
|
2435
|
+
type: "under_inflated"
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
async function discoverOtaBridge(deviceId, reportStatus) {
|
|
2440
|
+
for (let i = 0; i < 200; i++) {
|
|
2441
|
+
const msg = "Discovering OTA bridge...";
|
|
2442
|
+
reportStatus(msg, i / 200);
|
|
2443
|
+
const d = store.devices[deviceId];
|
|
2444
|
+
if (d) {
|
|
2445
|
+
if (d.type === "bridgeOta") return d;
|
|
2446
|
+
if (d.type === "bridge" && i > 100) throw new Error("Bridge has not switched to OTA mode. Please, try again.".t());
|
|
2447
|
+
}
|
|
2448
|
+
await toolsSvc.delay(200);
|
|
2449
|
+
}
|
|
2450
|
+
throw new Error("Cannot detect the bridge");
|
|
2451
|
+
}
|
|
2452
|
+
async function waitForBridgeToReconnect(deviceId, reportStatus) {
|
|
2453
|
+
let receivedOta = false;
|
|
2454
|
+
for (let i = 0; i < 200; i++) {
|
|
2455
|
+
reportStatus("Waiting for bridge to reconnect...", i / 200);
|
|
2456
|
+
const d = store.devices[deviceId];
|
|
2457
|
+
if (d) {
|
|
2458
|
+
if (d.type === "bridge") return d;
|
|
2459
|
+
if (d.type === "bridgeOta" && i > 50) receivedOta = true;
|
|
2460
|
+
}
|
|
2461
|
+
await toolsSvc.delay(200);
|
|
2462
|
+
}
|
|
2463
|
+
if (receivedOta) {
|
|
2464
|
+
throw new Error("Bridge has not switched from OTA mode. Please, try again.");
|
|
2465
|
+
}
|
|
2466
|
+
throw new Error("Cannot reconnect to the bridge after firmware update");
|
|
2467
|
+
}
|
|
2468
|
+
async function getVehicle(deviceId) {
|
|
2469
|
+
if (!canCommunicateWith(deviceId)) throw new Error("Not connected to any device");
|
|
2470
|
+
const deviceData = getDeviceData(deviceId);
|
|
2471
|
+
const tcVehicle = {
|
|
2472
|
+
registrationNumber: "",
|
|
2473
|
+
vin: deviceData.vin,
|
|
2474
|
+
axles: [],
|
|
2475
|
+
tcTyres: [],
|
|
2476
|
+
tcBridge: { id: deviceData.bridgeId }
|
|
2477
|
+
};
|
|
2478
|
+
await assignAxles(deviceId, tcVehicle);
|
|
2479
|
+
await assignTyres(deviceId, tcVehicle);
|
|
2480
|
+
return tcVehicle;
|
|
2481
|
+
}
|
|
2482
|
+
async function assignAxles(deviceId, tcVehicle) {
|
|
2483
|
+
const result = await bridgeCommands.getVehicleLayout(deviceId);
|
|
2484
|
+
const data = result.map((x) => bridgeTools.decimalToHex(x));
|
|
2485
|
+
const joinedData = Array.from({ length: data.length / 2 }, (_2, index) => data[2 * index + 1] + data[2 * index]);
|
|
2486
|
+
const activeAxles = joinedData.slice(0, 15).filter((x) => Number(`0x${x}`));
|
|
2487
|
+
const axlesPressureData = await bridgeCommands.getAxlesPressure(deviceId);
|
|
2488
|
+
let axleTypesBytes;
|
|
2489
|
+
if (isVersionGreaterThan(deviceId, "0.9.7")) {
|
|
2490
|
+
const axleTypes = await bridgeCommands.getAutolearnSettings(deviceId);
|
|
2491
|
+
axleTypesBytes = bridgeTools.convertAutoLearnObjectToBinary(axleTypes);
|
|
2492
|
+
}
|
|
2493
|
+
for (let index = 0; index < activeAxles.length; index++) {
|
|
2494
|
+
let isSteer = false;
|
|
2495
|
+
let isDrive = false;
|
|
2496
|
+
let isLift = false;
|
|
2497
|
+
if (axleTypesBytes) {
|
|
2498
|
+
const axleKey = `axle${String(index + 1).padStart(2, "0")}`;
|
|
2499
|
+
const tempAxle = axleTypesBytes[axleKey];
|
|
2500
|
+
isLift = tempAxle ? tempAxle.charAt(6) === "1" : false;
|
|
2501
|
+
isSteer = tempAxle ? tempAxle.charAt(4) === "1" : false;
|
|
2502
|
+
isDrive = tempAxle ? tempAxle.charAt(3) === "1" : false;
|
|
2503
|
+
}
|
|
2504
|
+
const tyreCount = Number.parseInt(activeAxles[index], 16).toString(2).split("").filter((x) => x === "1").length;
|
|
2505
|
+
if (!tyreCount) continue;
|
|
2506
|
+
const hasSpareTyre = tyreCount % 2 === 1;
|
|
2507
|
+
const axle = { tyresCount: hasSpareTyre ? tyreCount - 1 : tyreCount, isSteer, isDrive, isLift };
|
|
2508
|
+
const axleWithLimits = await assignAxlePressureLimits(deviceId, tcVehicle, axle, index, axlesPressureData);
|
|
2509
|
+
tcVehicle.axles.push(axleWithLimits);
|
|
2510
|
+
if (hasSpareTyre) {
|
|
2511
|
+
const spareAxle = { tyresCount: 0, isSpare: true };
|
|
2512
|
+
const spareAxleWithLimits = await assignAxlePressureLimits(deviceId, tcVehicle, spareAxle, 0, axlesPressureData);
|
|
2513
|
+
tcVehicle.axles.push(spareAxleWithLimits);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
tcVehicle.axles = ___default.sortBy(tcVehicle.axles, (a) => a.isSpare ? 1 : 0);
|
|
2517
|
+
}
|
|
2518
|
+
async function assignTyres(deviceId, tcVehicle) {
|
|
2519
|
+
const mountedTyres = tcVehicle.axles.filter((a) => !a.isSpare);
|
|
2520
|
+
for (let index = 0; index < tcVehicle.axles.length; index++) {
|
|
2521
|
+
const axle = tcVehicle.axles[index];
|
|
2522
|
+
const axleInfo = await bridgeCommands.getAxleInfo(deviceId, axle.isSpare ? index - mountedTyres.length : index) || [];
|
|
2523
|
+
const axleData = axleInfo.map((i) => bridgeTools.decimalToHex(i).toUpperCase());
|
|
2524
|
+
const joinedAxleData = Array.from(
|
|
2525
|
+
{ length: axleData.length / 4 },
|
|
2526
|
+
(_2, index2) => axleData[4 * index2 + 3] + axleData[4 * index2 + 2] + axleData[4 * index2 + 1] + axleData[4 * index2]
|
|
2527
|
+
);
|
|
2528
|
+
if (axle.isSpare) {
|
|
2529
|
+
const spareTyrePositionId = getPositionId(index + 1, 1, 0, true);
|
|
2530
|
+
const spareTyre = createNewTyre(spareTyrePositionId);
|
|
2531
|
+
if (Number.parseInt(joinedAxleData[7], 16)) {
|
|
2532
|
+
spareTyre.tcTpmsSensor = {
|
|
2533
|
+
id: bridgeTools.convertSensorIdFromBridge(joinedAxleData[7])
|
|
2534
|
+
// 7th place is reserved for spare tyres
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
tcVehicle.tcTyres.push(spareTyre);
|
|
2538
|
+
continue;
|
|
2539
|
+
}
|
|
2540
|
+
const hasTwoTyres = ___default.inRange(axle.tyresCount, 2, 4);
|
|
2541
|
+
const hasFourTyres = ___default.inRange(axle.tyresCount, 4, 6);
|
|
2542
|
+
let sensorIndexes = [];
|
|
2543
|
+
if (hasTwoTyres) sensorIndexes = [6, 8];
|
|
2544
|
+
if (hasFourTyres) sensorIndexes = [5, 6, 8, 9];
|
|
2545
|
+
for (let tyreIndex = 0; tyreIndex < sensorIndexes.length; tyreIndex++) {
|
|
2546
|
+
const tyrePositionId = getPositionId(index + 1, tyreIndex + 1, axle.tyresCount);
|
|
2547
|
+
const tyre = createNewTyre(tyrePositionId);
|
|
2548
|
+
if (Number.parseInt(joinedAxleData[sensorIndexes[tyreIndex]], 16)) {
|
|
2549
|
+
tyre.tcTpmsSensor = {
|
|
2550
|
+
id: bridgeTools.convertSensorIdFromBridge(joinedAxleData[sensorIndexes[tyreIndex]])
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
tcVehicle.tcTyres.push(tyre);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
async function assignAxlePressureLimits(deviceId, tcVehicle, tcVehicleAxle, axleIndex, bridgeAxlesPressureData) {
|
|
2558
|
+
const axlesPressureData = bridgeAxlesPressureData ?? await bridgeCommands.getAxlesPressure(deviceId);
|
|
2559
|
+
if (axlesPressureData[axleIndex * 3]) {
|
|
2560
|
+
tcVehicleAxle.maxTargetPressure = bridgeTools.kpaByteToBar(axlesPressureData[axleIndex * 3]);
|
|
2561
|
+
}
|
|
2562
|
+
if (axlesPressureData[axleIndex * 3 + 1]) {
|
|
2563
|
+
tcVehicleAxle.minTargetPressure = bridgeTools.kpaByteToBar(axlesPressureData[axleIndex * 3 + 1]);
|
|
2564
|
+
}
|
|
2565
|
+
if (axlesPressureData[axleIndex * 3 + 2]) {
|
|
2566
|
+
tcVehicleAxle.targetPressure = bridgeTools.kpaByteToBar(
|
|
2567
|
+
axlesPressureData[axleIndex * 3 + 2],
|
|
2568
|
+
void 0
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
return tcVehicleAxle;
|
|
2572
|
+
}
|
|
2573
|
+
function createNewTyre(positionId) {
|
|
2574
|
+
return {
|
|
2575
|
+
mountedOn: { positionId },
|
|
2576
|
+
serialNumber: `TYRE-${positionId}`
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
const bridge = {
|
|
2581
|
+
async connect(deviceId) {
|
|
2582
|
+
await promiseQueue$1.clearQueue("Previous pending commands aborted");
|
|
2583
|
+
const bridgeMeta = deviceMeta.bridge;
|
|
2584
|
+
await bluetooth.connect(deviceId, this.disconnect);
|
|
2585
|
+
await ble.requestMtu(deviceId, deviceMeta.bridge.mtu);
|
|
2586
|
+
await ble.startNotification(
|
|
2587
|
+
deviceId,
|
|
2588
|
+
bridgeMeta.communication.serviceId,
|
|
2589
|
+
bridgeMeta.communication.characteristicId,
|
|
2590
|
+
(notification) => promiseQueue$1.processMessage(deviceId, notification),
|
|
2591
|
+
(error) => console.log("Nofit Error", error)
|
|
2592
|
+
);
|
|
2593
|
+
await bridgeCommands.sendPinCommand(deviceId);
|
|
2594
|
+
store.deviceState[deviceId] = "paired";
|
|
2595
|
+
},
|
|
2596
|
+
async disconnect(deviceId) {
|
|
2597
|
+
store.deviceState[deviceId] = "disconnecting";
|
|
2598
|
+
await promiseQueue$1.clearQueue("Previous pending commands aborted");
|
|
2599
|
+
await bluetooth.disconnect(deviceId);
|
|
2600
|
+
store.deviceState[deviceId] = "disconnected";
|
|
2601
|
+
},
|
|
2602
|
+
getVehicle: bridgeService.getVehicle,
|
|
2603
|
+
setVehicle: bridgeService.setVehicle,
|
|
2604
|
+
getConfiguration: bridgeService.getConfiguration,
|
|
2605
|
+
setConfiguration: bridgeService.setConfiguration,
|
|
2606
|
+
getSensorReading: bridgeService.getSensorReading,
|
|
2607
|
+
getVehicleReadings: bridgeService.getVehicleReadings,
|
|
2608
|
+
getAutolearnStatuses: bridgeService.getAutolearnStatuses,
|
|
2609
|
+
resetAutolearnStatuses: bridgeService.resetAutolearnStatuses,
|
|
2610
|
+
updateFirmware: bridgeService.updateFirmware
|
|
2611
|
+
};
|
|
2612
|
+
|
|
2613
|
+
const flexiGaugeTpmsMeta = deviceMeta.flexiGaugeTpms;
|
|
2614
|
+
const flexiGaugeTpmsCommands = {
|
|
2615
|
+
startTpmsScan,
|
|
2616
|
+
getBattery
|
|
2617
|
+
};
|
|
2618
|
+
async function startTpmsScan(deviceId) {
|
|
2619
|
+
if (!canCommunicateWith(deviceId)) throw new Error("Flexi Gauge not connected");
|
|
2620
|
+
const scanMsg = stringToArrayBuffer("*TPMS ON TIME=10\r\n");
|
|
2621
|
+
return bluetooth.write(
|
|
2622
|
+
deviceId,
|
|
2623
|
+
flexiGaugeTpmsMeta.communication.serviceId,
|
|
2624
|
+
flexiGaugeTpmsMeta.communication.characteristicId,
|
|
2625
|
+
scanMsg
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
async function getBattery(deviceId) {
|
|
2629
|
+
const batteryValue = await bluetooth.read(
|
|
2630
|
+
deviceId,
|
|
2631
|
+
flexiGaugeTpmsMeta.battery.serviceId,
|
|
2632
|
+
flexiGaugeTpmsMeta.battery.characteristicId
|
|
2633
|
+
);
|
|
2634
|
+
const battery = Array.from(new Uint8Array(batteryValue))[0];
|
|
2635
|
+
return battery;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
let sensorReadings = [];
|
|
2639
|
+
let treadDepthCallback;
|
|
2640
|
+
let buttonCallback;
|
|
2641
|
+
let tpmsCallback;
|
|
2642
|
+
const capabilities = [
|
|
2643
|
+
{
|
|
2644
|
+
id: "td",
|
|
2645
|
+
processFn: processTreadDepth,
|
|
2646
|
+
regex: /^\*TD.*/
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
id: "button",
|
|
2650
|
+
processFn: processButtonPress,
|
|
2651
|
+
regex: /^\*[UDLRC] $/
|
|
2652
|
+
},
|
|
2653
|
+
{
|
|
2654
|
+
id: "tpms",
|
|
2655
|
+
processFn: processTpms,
|
|
2656
|
+
regex: /^\*TPMS.*/
|
|
2657
|
+
}
|
|
2658
|
+
];
|
|
2659
|
+
const flexiGaugeTpmsService = {
|
|
2660
|
+
startTpmsScan(deviceId) {
|
|
2661
|
+
flexiGaugeTpmsCommands.startTpmsScan(deviceId);
|
|
2662
|
+
},
|
|
2663
|
+
getBattery(deviceId) {
|
|
2664
|
+
return flexiGaugeTpmsCommands.getBattery(deviceId);
|
|
2665
|
+
},
|
|
2666
|
+
onTreadDepth(callback) {
|
|
2667
|
+
treadDepthCallback = callback;
|
|
2668
|
+
},
|
|
2669
|
+
onButton(callback) {
|
|
2670
|
+
buttonCallback = callback;
|
|
2671
|
+
},
|
|
2672
|
+
onTpms(callback) {
|
|
2673
|
+
tpmsCallback = callback;
|
|
2674
|
+
},
|
|
2675
|
+
icon: "icon-flexigauge",
|
|
2676
|
+
processMessage(deviceId, message) {
|
|
2677
|
+
const numberArray = Array.from(new Uint8Array(message));
|
|
2678
|
+
const stringFromBytes = String.fromCharCode.apply(null, numberArray).replace("\r\n", "");
|
|
2679
|
+
for (const capability of capabilities) {
|
|
2680
|
+
if (capability.regex.test(stringFromBytes)) {
|
|
2681
|
+
return capability.processFn(stringFromBytes);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
function processTreadDepth(value) {
|
|
2687
|
+
const treadDepth = value.match(/\d+/)[0];
|
|
2688
|
+
const convertedValue = treadDepth / 1e3;
|
|
2689
|
+
treadDepthCallback(convertedValue);
|
|
2690
|
+
}
|
|
2691
|
+
function processButtonPress(value) {
|
|
2692
|
+
const buttonName = value.match(/\*(.)/);
|
|
2693
|
+
if (!buttonName || !buttonName[1]) throw new Error("Unrecognized button");
|
|
2694
|
+
buttonCallback(buttonName[1]);
|
|
2695
|
+
}
|
|
2696
|
+
function processTpms(value) {
|
|
2697
|
+
const groupedValue = value.split(" ");
|
|
2698
|
+
const type = groupedValue[1];
|
|
2699
|
+
if (type === "ISON") {
|
|
2700
|
+
sensorReadings = [];
|
|
2701
|
+
}
|
|
2702
|
+
if (type === "DATA") {
|
|
2703
|
+
const reading = {};
|
|
2704
|
+
for (const group of groupedValue) {
|
|
2705
|
+
if (!group.includes("=")) continue;
|
|
2706
|
+
const groupString = group.replace(",", "");
|
|
2707
|
+
const groupSeparated = groupString.split("=");
|
|
2708
|
+
reading[groupSeparated[0]] = groupSeparated[1];
|
|
2709
|
+
}
|
|
2710
|
+
sensorReadings.push(reading);
|
|
2711
|
+
}
|
|
2712
|
+
if (type === "ENDSCANNING") {
|
|
2713
|
+
if (!sensorReadings.length) {
|
|
2714
|
+
return notify.create({
|
|
2715
|
+
type: "info",
|
|
2716
|
+
message: "No sensors found"
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
const strongestReading = sensorReadings.reduce((accumulator, currentReading) => {
|
|
2720
|
+
return Number(currentReading.Rssi) > Number(accumulator.Rssi) ? currentReading : accumulator;
|
|
2721
|
+
}, sensorReadings[0]);
|
|
2722
|
+
tpmsCallback(strongestReading);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
const flexiGaugeTpms = {
|
|
2727
|
+
async connect(deviceId) {
|
|
2728
|
+
const bridgeMeta = deviceMeta.flexiGaugeTpms;
|
|
2729
|
+
await bluetooth.connect(deviceId, this.disconnect);
|
|
2730
|
+
await ble.startNotification(
|
|
2731
|
+
deviceId,
|
|
2732
|
+
bridgeMeta.communication.serviceId,
|
|
2733
|
+
bridgeMeta.communication.characteristicId,
|
|
2734
|
+
(notification) => flexiGaugeTpmsService.processMessage(deviceId, notification),
|
|
2735
|
+
(error) => console.log("Nofit Error", error)
|
|
2736
|
+
);
|
|
2737
|
+
store.deviceState[deviceId] = "paired";
|
|
2738
|
+
},
|
|
2739
|
+
async disconnect(deviceId) {
|
|
2740
|
+
store.deviceState[deviceId] = "disconnecting";
|
|
2741
|
+
await bluetooth.disconnect(deviceId);
|
|
2742
|
+
store.deviceState[deviceId] = "disconnected";
|
|
2743
|
+
},
|
|
2744
|
+
onTreadDepth(callback) {
|
|
2745
|
+
flexiGaugeTpmsService.onTreadDepth(callback);
|
|
2746
|
+
},
|
|
2747
|
+
onButtonPress(callback) {
|
|
2748
|
+
flexiGaugeTpmsService.onButton(callback);
|
|
2749
|
+
},
|
|
2750
|
+
onTpms(callback) {
|
|
2751
|
+
flexiGaugeTpmsService.onTpms(callback);
|
|
2752
|
+
},
|
|
2753
|
+
getBattery: flexiGaugeTpmsService.getBattery,
|
|
2754
|
+
startTpmsScan: flexiGaugeTpmsService.startTpmsScan
|
|
2755
|
+
};
|
|
2756
|
+
|
|
2757
|
+
const bridgeSimulator = {
|
|
2758
|
+
async connect(bridge) {
|
|
2759
|
+
bridge.status = "connecting";
|
|
2760
|
+
await toolsSvc.delay(100);
|
|
2761
|
+
bridge.status = "connected";
|
|
2762
|
+
},
|
|
2763
|
+
async disconnect(bridge) {
|
|
2764
|
+
bridge.status = "disconnecting";
|
|
2765
|
+
await toolsSvc.delay(100);
|
|
2766
|
+
bridge.status = void 0;
|
|
2767
|
+
},
|
|
2768
|
+
createSimulatedBridge(initialData) {
|
|
2769
|
+
return {
|
|
2770
|
+
type: "bridge",
|
|
2771
|
+
id: "bridge1",
|
|
2772
|
+
rssi: -50,
|
|
2773
|
+
bridgeId: "FFFFAABB",
|
|
2774
|
+
vin: "VIN1",
|
|
2775
|
+
name: "Bridge1",
|
|
2776
|
+
simulatorData: {},
|
|
2777
|
+
advertisingData: {
|
|
2778
|
+
configVersion: 1,
|
|
2779
|
+
macAddress: [1, 2, 3, 4, 5, 6],
|
|
2780
|
+
randomAdvNumber: [0, 0, 0, 0, 0, 0, 0, 0],
|
|
2781
|
+
vinNum: [86, 87, 88, 89, 90, 91, 92, 93],
|
|
2782
|
+
fwVersion: "0.7.C",
|
|
2783
|
+
timeFromStart: 255
|
|
2784
|
+
},
|
|
2785
|
+
...initialData
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
// simulateWriteToDevice(
|
|
2789
|
+
// device: SimulatedBluetoothDeviceBridge,
|
|
2790
|
+
// serviceId: string,
|
|
2791
|
+
// characteristicId: string,
|
|
2792
|
+
// value: ArrayBuffer,
|
|
2793
|
+
// ) {
|
|
2794
|
+
// const command = new Uint8Array(value)
|
|
2795
|
+
// if (!command.length) {
|
|
2796
|
+
// console.warn('[BT Simulator] Empty write command received')
|
|
2797
|
+
// return
|
|
2798
|
+
// }
|
|
2799
|
+
// const [_sl, _msgType, size, commandId, subCommandId, ...payloadBytes] = command
|
|
2800
|
+
// const payload = payloadBytes.slice(0, size - 2)
|
|
2801
|
+
// const _macAddress = payload.slice(0, 6)
|
|
2802
|
+
// const _paddedTimestamp = payload.slice(6, 14)
|
|
2803
|
+
// const _crc = payload.slice(-4)
|
|
2804
|
+
// const payloadData = payload.slice(14, -4)
|
|
2805
|
+
// const responseHeader = [
|
|
2806
|
+
// securityLevelIds.tirecheck,
|
|
2807
|
+
// messageTypeIds.response,
|
|
2808
|
+
// 0,
|
|
2809
|
+
// commandId,
|
|
2810
|
+
// subCommandId,
|
|
2811
|
+
// ..._.range(14).map((x) => 0), // mac address and timestamp
|
|
2812
|
+
// ]
|
|
2813
|
+
// if (!devtoolsStore.simulatedBluetoothDeviceHandlers[device.id])
|
|
2814
|
+
// console.warn(`[Bluetooth] Device ${device.id} is disconnected`)
|
|
2815
|
+
// const simulateReadFromDevice = (data: ArrayBuffer) => {
|
|
2816
|
+
// const dataToSend = new Uint8Array(data)
|
|
2817
|
+
// devtoolsStore.simulatedBluetoothDeviceHandlers[device.id]?.(
|
|
2818
|
+
// new Uint8Array([...dataToSend, 0, 0, 0, 0, 0, 0, 0, 0]).buffer,
|
|
2819
|
+
// ) // Append CRC to the data
|
|
2820
|
+
// }
|
|
2821
|
+
// const context: BridgeSimulatorContext = { device, responseHeader, payloadData, simulateReadFromDevice }
|
|
2822
|
+
// if (commandId === commandIds.keepAlive) {
|
|
2823
|
+
// context.responseHeader = [
|
|
2824
|
+
// securityLevelIds.tirecheck,
|
|
2825
|
+
// messageTypeIds.response,
|
|
2826
|
+
// 2,
|
|
2827
|
+
// commandIds.keepAliveResponse,
|
|
2828
|
+
// subCommandId,
|
|
2829
|
+
// ...Array.from({ length: 4 }).fill(0),
|
|
2830
|
+
// ]
|
|
2831
|
+
// return simulateKeepalive(context)
|
|
2832
|
+
// }
|
|
2833
|
+
// if (commandId === commandIds.pin) {
|
|
2834
|
+
// return simulatePinResponse(context)
|
|
2835
|
+
// }
|
|
2836
|
+
// if (commandId === commandIds.sensorMeasurement) {
|
|
2837
|
+
// return simulateReadSensorMeasurement(context, subCommandId)
|
|
2838
|
+
// }
|
|
2839
|
+
// if (subCommandId === subCommandIds.vehicleLayout) {
|
|
2840
|
+
// if (commandId === commandIds.readData) {
|
|
2841
|
+
// return simulateReadVehicleLayout(context)
|
|
2842
|
+
// }
|
|
2843
|
+
// if (commandId === commandIds.writeData) {
|
|
2844
|
+
// return simulateWriteVehicleLayout(context)
|
|
2845
|
+
// }
|
|
2846
|
+
// }
|
|
2847
|
+
// if (subCommandId === subCommandIds.pressurePerAxle) {
|
|
2848
|
+
// if (commandId === commandIds.readData) {
|
|
2849
|
+
// return simulateReadPressurePerAxle(context)
|
|
2850
|
+
// }
|
|
2851
|
+
// if (commandId === commandIds.writeData) {
|
|
2852
|
+
// return simulateWritePressurePerAxle(context)
|
|
2853
|
+
// }
|
|
2854
|
+
// }
|
|
2855
|
+
// if (subCommandId === subCommandIds.customerPressureThresholds) {
|
|
2856
|
+
// if (commandId === commandIds.readData) {
|
|
2857
|
+
// return simulateReadPressureThresholds(context)
|
|
2858
|
+
// }
|
|
2859
|
+
// if (commandId === commandIds.writeData) {
|
|
2860
|
+
// return simulateWritePressureThresholds(context)
|
|
2861
|
+
// }
|
|
2862
|
+
// }
|
|
2863
|
+
// if (_.inRange(subCommandId, subCommandIds.idsPerWheel(0), subCommandIds.idsPerWheel(16))) {
|
|
2864
|
+
// const axleIndex = subCommandId - subCommandIds.idsPerWheel(0)
|
|
2865
|
+
// if (commandId === commandIds.readData) {
|
|
2866
|
+
// return simulateReadSensorIdsPerWheel(axleIndex, context)
|
|
2867
|
+
// }
|
|
2868
|
+
// if (commandId === commandIds.writeData) {
|
|
2869
|
+
// return simulateWriteSensorIdsPerWheel(axleIndex, context)
|
|
2870
|
+
// }
|
|
2871
|
+
// }
|
|
2872
|
+
// if (
|
|
2873
|
+
// [
|
|
2874
|
+
// subCommandIds.rebootSubCommand,
|
|
2875
|
+
// subCommandIds.customerCanSettings,
|
|
2876
|
+
// subCommandIds.workshopCanSettings,
|
|
2877
|
+
// subCommandIds.bridgeConfiguration,
|
|
2878
|
+
// subCommandIds.customerTemperatureThresholds,
|
|
2879
|
+
// subCommandIds.customerImbalanceThresholds,
|
|
2880
|
+
// ].includes(subCommandId)
|
|
2881
|
+
// ) {
|
|
2882
|
+
// const { responseHeader, simulateReadFromDevice } = context
|
|
2883
|
+
// return simulateReadFromDevice?.(new Uint8Array([...responseHeader]).buffer)
|
|
2884
|
+
// }
|
|
2885
|
+
// console.error(`[Bluetooth simulator] Unknown command: 0x${commandId.toString(16)} 0x${subCommandId.toString(16)}`)
|
|
2886
|
+
// },
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
let simulatedDevices = {};
|
|
2890
|
+
let areSimulatorMethodsInjected = false;
|
|
2891
|
+
let advertisingInterval;
|
|
2892
|
+
const simulator = {
|
|
2893
|
+
setSimulatedDevices(devices) {
|
|
2894
|
+
ensureSimulatorMethodsAreInjected();
|
|
2895
|
+
simulatedDevices = { ...devices };
|
|
2896
|
+
},
|
|
2897
|
+
addSimulatedDevice(device) {
|
|
2898
|
+
ensureSimulatorMethodsAreInjected();
|
|
2899
|
+
simulatedDevices[device.id] = device;
|
|
2900
|
+
},
|
|
2901
|
+
getSimulatedDevices() {
|
|
2902
|
+
return simulatedDevices;
|
|
2903
|
+
}
|
|
2904
|
+
};
|
|
2905
|
+
function ensureSimulatorMethodsAreInjected() {
|
|
2906
|
+
if (areSimulatorMethodsInjected) return;
|
|
2907
|
+
areSimulatorMethodsInjected = true;
|
|
2908
|
+
const scanDevices = bluetooth.scanDevices;
|
|
2909
|
+
bluetooth.scanDevices = async (...args) => {
|
|
2910
|
+
scanDevices(...args);
|
|
2911
|
+
if (advertisingInterval) {
|
|
2912
|
+
clearInterval(advertisingInterval);
|
|
2913
|
+
advertisingInterval = void 0;
|
|
2914
|
+
}
|
|
2915
|
+
advertisingInterval = toolsSvc.setIntervalImmediate(() => {
|
|
2916
|
+
for (const key in simulatedDevices) {
|
|
2917
|
+
deviceAdvertisingCallback?.(simulatedDevices[key]);
|
|
2918
|
+
}
|
|
2919
|
+
}, 2e3);
|
|
2920
|
+
};
|
|
2921
|
+
for (const key in bridge) {
|
|
2922
|
+
const fnName = key;
|
|
2923
|
+
const originalFn = bridge[fnName];
|
|
2924
|
+
bridge[fnName] = (device, ...args) => {
|
|
2925
|
+
const deviceId = device.id;
|
|
2926
|
+
const simulatedDevice = simulatedDevices[deviceId];
|
|
2927
|
+
if (simulatedDevices) return bridgeSimulator[fnName](simulatedDevice, ...args);
|
|
2928
|
+
else return originalFn(device, ...args);
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
class BridgeTcVehicleAxle {
|
|
2934
|
+
targetPressure;
|
|
2935
|
+
tyresCount = 2;
|
|
2936
|
+
isSteer;
|
|
2937
|
+
isSpare;
|
|
2938
|
+
isDrive;
|
|
2939
|
+
isLift;
|
|
2940
|
+
spacesBelow;
|
|
2941
|
+
maxTargetPressure;
|
|
2942
|
+
minTargetPressure;
|
|
416
2943
|
}
|
|
417
2944
|
|
|
418
2945
|
function createTirecheckDeviceSdk(bleImplementation) {
|
|
419
2946
|
setBleImplementation(bleImplementation);
|
|
420
2947
|
return {
|
|
421
|
-
|
|
2948
|
+
/** Generic methods common for all devices */
|
|
2949
|
+
bluetooth,
|
|
2950
|
+
/** Methods for working with Tirecheck CAN Bridge */
|
|
2951
|
+
bridge,
|
|
2952
|
+
/** Methods for working with Tirecheck CAN Bridge in OTA mode */
|
|
2953
|
+
bridgeOta,
|
|
2954
|
+
/** Methods for working with Tirecheck TPMS FlexiGauge */
|
|
2955
|
+
flexiGaugeTpms,
|
|
2956
|
+
/** Allows simulating devices without actually using bluetooth */
|
|
2957
|
+
simulator
|
|
422
2958
|
};
|
|
423
2959
|
}
|
|
424
2960
|
|
|
2961
|
+
exports.BridgeTcVehicleAxle = BridgeTcVehicleAxle;
|
|
425
2962
|
exports.createTirecheckDeviceSdk = createTirecheckDeviceSdk;
|