@robotical/raftjs 2.0.4 → 2.0.6
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/react-native/RaftChannelSimulated.d.ts +10 -0
- package/dist/react-native/RaftChannelSimulated.js +327 -79
- package/dist/react-native/RaftChannelSimulated.js.map +1 -1
- package/dist/react-native/RaftCustomAttrHandler.d.ts +2 -0
- package/dist/react-native/RaftCustomAttrHandler.js +54 -26
- package/dist/react-native/RaftCustomAttrHandler.js.map +1 -1
- package/dist/react-native/RaftDeviceInfo.d.ts +2 -0
- package/dist/web/RaftChannelSimulated.d.ts +10 -0
- package/dist/web/RaftChannelSimulated.js +327 -79
- package/dist/web/RaftChannelSimulated.js.map +1 -1
- package/dist/web/RaftCustomAttrHandler.d.ts +2 -0
- package/dist/web/RaftCustomAttrHandler.js +54 -26
- package/dist/web/RaftCustomAttrHandler.js.map +1 -1
- package/dist/web/RaftDeviceInfo.d.ts +2 -0
- package/package.json +1 -1
- package/src/RaftChannelSimulated.ts +413 -79
- package/src/RaftCustomAttrHandler.ts +81 -35
- package/src/RaftDeviceInfo.ts +2 -0
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
class CustomAttrHandler {
|
|
12
|
+
constructor() {
|
|
13
|
+
this._jsFunctionCache = new Map();
|
|
14
|
+
}
|
|
12
15
|
handleAttr(pollRespMetadata, msgBuffer, msgBufIdx) {
|
|
13
|
-
//
|
|
14
|
-
// int N=(buf[0]+32-buf[2])%32;int k=3;int i=0;while(i<N){out.Red=(buf[k]<<16)|(buf[k+1]<<8)|buf[k+2];out.IR=(buf[k+3]<<16)|(buf[k+4]<<8)|buf[k+5];k+=6;i++;next;}
|
|
15
|
-
// Number of bytes in the each message
|
|
16
|
+
// Number of bytes in each message
|
|
16
17
|
const numMsgBytes = pollRespMetadata.b;
|
|
17
18
|
// Create a vector for each attribute in the metadata
|
|
18
19
|
const attrValueVecs = [];
|
|
@@ -23,43 +24,70 @@ class CustomAttrHandler {
|
|
|
23
24
|
attrValueVecs.push([]);
|
|
24
25
|
attrValues[pollRespMetadata.a[attrIdx].n] = attrValueVecs[attrIdx];
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const customFnDef = pollRespMetadata.c;
|
|
28
|
+
if (!customFnDef) {
|
|
29
|
+
return attrValueVecs;
|
|
30
|
+
}
|
|
31
|
+
// Provide the message buffer sliced to the data portion
|
|
32
|
+
const buf = msgBuffer.slice(msgBufIdx);
|
|
33
|
+
if (buf.length < numMsgBytes) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
// Execute supplied JS implementation if provided
|
|
37
|
+
if (customFnDef.j && customFnDef.j.trim().length > 0) {
|
|
38
|
+
const jsFn = this.getOrCompileJsFunction(customFnDef);
|
|
39
|
+
if (!jsFn) {
|
|
40
|
+
return attrValueVecs;
|
|
33
41
|
}
|
|
42
|
+
try {
|
|
43
|
+
jsFn(buf, attrValues, attrValueVecs, pollRespMetadata, msgBuffer, msgBufIdx, numMsgBytes);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(`CustomAttrHandler JS function ${customFnDef.n} execution failed`, err);
|
|
47
|
+
}
|
|
48
|
+
return attrValueVecs;
|
|
49
|
+
}
|
|
50
|
+
// Custom code for each device type handled natively
|
|
51
|
+
if (customFnDef.n === "max30101_fifo") {
|
|
34
52
|
// Generated code ...
|
|
35
53
|
const N = (buf[0] + 32 - buf[2]) % 32;
|
|
36
54
|
let k = 3;
|
|
37
55
|
let i = 0;
|
|
38
56
|
while (i < N) {
|
|
39
|
-
attrValues[
|
|
40
|
-
attrValues[
|
|
41
|
-
attrValues[
|
|
42
|
-
attrValues[
|
|
57
|
+
attrValues["Red"].push(0);
|
|
58
|
+
attrValues["Red"][attrValues["Red"].length - 1] = (buf[k] << 16) | (buf[k + 1] << 8) | buf[k + 2];
|
|
59
|
+
attrValues["IR"].push(0);
|
|
60
|
+
attrValues["IR"][attrValues["IR"].length - 1] = (buf[k + 3] << 16) | (buf[k + 4] << 8) | buf[k + 5];
|
|
43
61
|
k += 6;
|
|
44
62
|
i++;
|
|
45
|
-
;
|
|
46
63
|
}
|
|
47
64
|
}
|
|
48
|
-
else if (
|
|
49
|
-
// Get the buffer
|
|
50
|
-
const buf = msgBuffer.slice(msgBufIdx);
|
|
51
|
-
if (buf.length < numMsgBytes) {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
// Implement the pseudo-code:
|
|
55
|
-
// float key = 20.9/120.0; float val = key * (buf[0] + (buf[1]/10.0) + (buf[2]/100.0)); out.oxygen = val;
|
|
65
|
+
else if (customFnDef.n === "gravity_o2_calc") {
|
|
56
66
|
const key = 20.9 / 120.0;
|
|
57
|
-
const val = key * (buf[0] +
|
|
58
|
-
|
|
59
|
-
attrValues['oxygen'].push(val);
|
|
67
|
+
const val = key * (buf[0] + buf[1] / 10.0 + buf[2] / 100.0);
|
|
68
|
+
attrValues["oxygen"].push(val);
|
|
60
69
|
}
|
|
61
70
|
return attrValueVecs;
|
|
62
71
|
}
|
|
72
|
+
getOrCompileJsFunction(customFnDef) {
|
|
73
|
+
if (!customFnDef.j) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const cacheKey = `${customFnDef.n}::${customFnDef.j}`;
|
|
77
|
+
const cachedFn = this._jsFunctionCache.get(cacheKey);
|
|
78
|
+
if (cachedFn) {
|
|
79
|
+
return cachedFn;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const compiledFn = new Function("buf", "attrValues", "attrValueVecs", "pollRespMetadata", "msgBuffer", "msgBufIdx", "numMsgBytes", customFnDef.j);
|
|
83
|
+
this._jsFunctionCache.set(cacheKey, compiledFn);
|
|
84
|
+
return compiledFn;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.error(`CustomAttrHandler failed to compile JS function ${customFnDef.n}`, err);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
63
91
|
}
|
|
64
92
|
exports.default = CustomAttrHandler;
|
|
65
93
|
//# sourceMappingURL=RaftCustomAttrHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RaftCustomAttrHandler.js","sourceRoot":"","sources":["../../src/RaftCustomAttrHandler.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,wBAAwB;AACxB,4CAA4C;AAC5C,EAAE;AACF,sBAAsB;AACtB,EAAE;AACF,iHAAiH;;
|
|
1
|
+
{"version":3,"file":"RaftCustomAttrHandler.js","sourceRoot":"","sources":["../../src/RaftCustomAttrHandler.ts"],"names":[],"mappings":";AAAA,iHAAiH;AACjH,EAAE;AACF,wBAAwB;AACxB,4CAA4C;AAC5C,EAAE;AACF,sBAAsB;AACtB,EAAE;AACF,iHAAiH;;AAcjH,MAAqB,iBAAiB;IAAtC;QAEY,qBAAgB,GAAG,IAAI,GAAG,EAA0B,CAAC;IA6FjE,CAAC;IA3FU,UAAU,CAAC,gBAA4C,EAAE,SAAqB,EAAE,SAAiB;QAEpG,kCAAkC;QAClC,MAAM,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC;QAEvC,qDAAqD;QACrD,MAAM,aAAa,GAAe,EAAE,CAAC;QAErC,6CAA6C;QAC7C,MAAM,UAAU,GAA6B,EAAE,CAAC;QAEhD,+BAA+B;QAC/B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;YACnE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvB,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC;QACvC,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,OAAO,aAAa,CAAC;QACzB,CAAC;QAED,wDAAwD;QACxD,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,GAAG,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACd,CAAC;QAED,iDAAiD;QACjD,IAAI,WAAW,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,CAAC;YACtD,IAAI,CAAC,IAAI,EAAE,CAAC;gBACR,OAAO,aAAa,CAAC;YACzB,CAAC;YACD,IAAI,CAAC;gBACD,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;YAC9F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,iCAAiC,WAAW,CAAC,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;YAC1F,CAAC;YACD,OAAO,aAAa,CAAC;QACzB,CAAC;QAED,oDAAoD;QACpD,IAAI,WAAW,CAAC,CAAC,KAAK,eAAe,EAAE,CAAC;YACpC,qBAAqB;YACrB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACX,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC1B,UAAU,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAClG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACpG,CAAC,IAAI,CAAC,CAAC;gBACP,CAAC,EAAE,CAAC;YACR,CAAC;QACL,CAAC;aAAM,IAAI,WAAW,CAAC,CAAC,KAAK,iBAAiB,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;YACzB,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;YAC5D,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,aAAa,CAAC;IACzB,CAAC;IAEO,sBAAsB,CAAC,WAAqC;QAChE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,WAAW,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrD,IAAI,QAAQ,EAAE,CAAC;YACX,OAAO,QAAQ,CAAC;QACpB,CAAC;QACD,IAAI,CAAC;YACD,MAAM,UAAU,GAAG,IAAI,QAAQ,CAC3B,KAAK,EACL,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,CAAC,CAAC,CACE,CAAC;YACpB,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAChD,OAAO,UAAU,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,mDAAmD,WAAW,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACvF,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;CACJ;AA/FD,oCA+FC"}
|
|
@@ -25,10 +25,12 @@ export interface DeviceTypeAttribute {
|
|
|
25
25
|
vf?: boolean | number;
|
|
26
26
|
vft?: string;
|
|
27
27
|
lut?: Array<LUTRow>;
|
|
28
|
+
resolution?: string;
|
|
28
29
|
}
|
|
29
30
|
export interface CustomFunctionDefinition {
|
|
30
31
|
n: string;
|
|
31
32
|
c: string;
|
|
33
|
+
j?: string;
|
|
32
34
|
}
|
|
33
35
|
export interface DeviceTypePollRespMetadata {
|
|
34
36
|
b: number;
|
package/package.json
CHANGED
|
@@ -286,7 +286,7 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
286
286
|
|
|
287
287
|
const attributes = deviceTypeInfo.resp.a;
|
|
288
288
|
const dataBlockSizeBytes = deviceTypeInfo.resp.b;
|
|
289
|
-
|
|
289
|
+
|
|
290
290
|
// Create a buffer for the data
|
|
291
291
|
const dataBuffer = new ArrayBuffer(dataBlockSizeBytes + 2);
|
|
292
292
|
const dataView = new DataView(dataBuffer);
|
|
@@ -295,89 +295,68 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
295
295
|
// Add 16 bit big endian deviceTimeMs mod 65536 to the buffer
|
|
296
296
|
dataView.setUint16(bytePos, deviceTimeMs % 65536, false);
|
|
297
297
|
bytePos += 2;
|
|
298
|
-
|
|
299
|
-
// Calculate sine wave with phase offsets for each attribute
|
|
300
|
-
const numAttributes = attributes.length;
|
|
301
|
-
// Adjust frequency based on the device interval with N samples per cycle
|
|
302
|
-
const numSamplesPerCycle = 10;
|
|
303
|
-
let frequencyHz = 0.1; // Default frequency in Hz
|
|
304
|
-
if (deviceIntervalMs > 0) {
|
|
305
|
-
frequencyHz = (1000 / deviceIntervalMs) / numSamplesPerCycle;
|
|
306
|
-
}
|
|
307
298
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
299
|
+
const handledByCustomGenerator = this._fillCustomRawData(
|
|
300
|
+
deviceTypeInfo,
|
|
301
|
+
dataView,
|
|
302
|
+
bytePos,
|
|
303
|
+
dataBlockSizeBytes,
|
|
304
|
+
deviceIntervalMs,
|
|
305
|
+
deviceTimeMs
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (!handledByCustomGenerator) {
|
|
309
|
+
const numAttributes = attributes.length;
|
|
310
|
+
const numSamplesPerCycle = 10;
|
|
311
|
+
const frequencyHz = (deviceIntervalMs > 0)
|
|
312
|
+
? (1000 / deviceIntervalMs) / numSamplesPerCycle
|
|
313
|
+
: 0.1;
|
|
318
314
|
const timeRadians = deviceTimeMs * frequencyHz * (2 * Math.PI) / 1000;
|
|
319
|
-
const sinValue = Math.sin(timeRadians + phaseOffset);
|
|
320
|
-
|
|
321
|
-
// Scale the value to fit within the attribute's range
|
|
322
|
-
let scaledValue: number;
|
|
323
|
-
if (attr.r && attr.r.length >= 2) {
|
|
324
|
-
const minValue = attr.r[0];
|
|
325
|
-
const maxValue = attr.r[1];
|
|
326
|
-
const midPoint = (maxValue + minValue) / 2;
|
|
327
|
-
const range = (maxValue - minValue) / 2;
|
|
328
|
-
scaledValue = midPoint + sinValue * range * amplitude;
|
|
329
|
-
} else {
|
|
330
|
-
// Default range if not specified
|
|
331
|
-
scaledValue = sinValue * 1000 * amplitude;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Convert to raw integer value if needed
|
|
335
|
-
let rawValue = scaledValue;
|
|
336
|
-
if (attr.d) {
|
|
337
|
-
// Multiply by the divisor to get the raw value (reverse of what happens when decoding)
|
|
338
|
-
rawValue = scaledValue * attr.d;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Write the value to the buffer based on its type
|
|
342
|
-
if (attr.t === "b") {
|
|
343
|
-
dataView.setUint8(bytePos, Math.round(rawValue));
|
|
344
|
-
bytePos += 1;
|
|
345
|
-
} else if (attr.t === "B") {
|
|
346
|
-
dataView.setUint8(bytePos, Math.round(rawValue));
|
|
347
|
-
bytePos += 1;
|
|
348
|
-
} else if (attr.t === "c") {
|
|
349
|
-
dataView.setInt8(bytePos, Math.round(rawValue));
|
|
350
|
-
bytePos += 1;
|
|
351
|
-
} else if (attr.t === "C") {
|
|
352
|
-
dataView.setUint8(bytePos, Math.round(rawValue));
|
|
353
|
-
bytePos += 1;
|
|
354
|
-
} else if (attr.t === "<h") {
|
|
355
|
-
dataView.setInt16(bytePos, Math.round(rawValue), true); // Little endian
|
|
356
|
-
bytePos += 2;
|
|
357
|
-
} else if (attr.t === ">h") {
|
|
358
|
-
dataView.setInt16(bytePos, Math.round(rawValue), false); // Big endian
|
|
359
|
-
bytePos += 2;
|
|
360
|
-
} else if (attr.t === "<H") {
|
|
361
|
-
dataView.setUint16(bytePos, Math.round(rawValue), true); // Little endian
|
|
362
|
-
bytePos += 2;
|
|
363
|
-
} else if (attr.t === ">H") {
|
|
364
|
-
dataView.setUint16(bytePos, Math.round(rawValue), false); // Big endian
|
|
365
|
-
bytePos += 2;
|
|
366
|
-
} else if (attr.t === "f" || attr.t === "<f") {
|
|
367
|
-
dataView.setFloat32(bytePos, rawValue, true); // Little endian
|
|
368
|
-
bytePos += 4;
|
|
369
|
-
} else if (attr.t === ">f") {
|
|
370
|
-
dataView.setFloat32(bytePos, rawValue, false); // Big endian
|
|
371
|
-
bytePos += 4;
|
|
372
|
-
} else {
|
|
373
|
-
RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg - unsupported attribute type ${attr.t}`);
|
|
374
|
-
}
|
|
375
315
|
|
|
316
|
+
// Iterate through attributes and fill the payload
|
|
317
|
+
for (let attrIdx = 0; attrIdx < numAttributes; attrIdx++) {
|
|
318
|
+
const attr = attributes[attrIdx];
|
|
319
|
+
const { typeCode, repeatCount, littleEndian } = this._parseAttrType(attr.t);
|
|
320
|
+
const scaledValues = this._generateAttributeScaledValues(
|
|
321
|
+
attr,
|
|
322
|
+
attrIdx,
|
|
323
|
+
repeatCount,
|
|
324
|
+
numAttributes,
|
|
325
|
+
timeRadians,
|
|
326
|
+
deviceTimeMs
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (scaledValues.length !== repeatCount) {
|
|
330
|
+
RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg - value count mismatch for ${attr.n}`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (let elemIdx = 0; elemIdx < repeatCount; elemIdx++) {
|
|
335
|
+
const scaledValue = scaledValues[elemIdx];
|
|
336
|
+
const rawValue = this._prepareRawValue(attr, typeCode, scaledValue);
|
|
337
|
+
const nextBytePos = this._writeRawValueToBuffer(
|
|
338
|
+
dataView,
|
|
339
|
+
bytePos,
|
|
340
|
+
typeCode,
|
|
341
|
+
littleEndian,
|
|
342
|
+
rawValue
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (nextBytePos < 0) {
|
|
346
|
+
RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg - buffer overflow writing ${attr.n}`);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
bytePos = nextBytePos;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
bytePos += dataBlockSizeBytes;
|
|
376
355
|
}
|
|
377
|
-
|
|
356
|
+
|
|
378
357
|
// Convert the buffer to a byte array
|
|
379
358
|
const dataBytes = new Uint8Array(dataBuffer);
|
|
380
|
-
|
|
359
|
+
|
|
381
360
|
// Create the JSON message structure
|
|
382
361
|
const message = {
|
|
383
362
|
"BUS1": {
|
|
@@ -401,6 +380,305 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
401
380
|
|
|
402
381
|
}
|
|
403
382
|
|
|
383
|
+
private _parseAttrType(attrType: string): { typeCode: string; repeatCount: number; littleEndian: boolean } {
|
|
384
|
+
const repeatMatch = attrType.match(/\[(\d+)\]\s*$/);
|
|
385
|
+
const repeatCount = repeatMatch ? parseInt(repeatMatch[1], 10) : 1;
|
|
386
|
+
const coreType = repeatMatch ? attrType.slice(0, repeatMatch.index) : attrType;
|
|
387
|
+
let littleEndian = false;
|
|
388
|
+
let typeCode = coreType.trim();
|
|
389
|
+
|
|
390
|
+
if (typeCode.startsWith("<")) {
|
|
391
|
+
littleEndian = true;
|
|
392
|
+
typeCode = typeCode.slice(1);
|
|
393
|
+
} else if (typeCode.startsWith(">")) {
|
|
394
|
+
littleEndian = false;
|
|
395
|
+
typeCode = typeCode.slice(1);
|
|
396
|
+
} else if (typeCode === "f") {
|
|
397
|
+
// Match previous behaviour - plain "f" treated as little endian floats
|
|
398
|
+
littleEndian = true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return { typeCode, repeatCount, littleEndian };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private _generateAttributeScaledValues(
|
|
405
|
+
attr: any,
|
|
406
|
+
attrIdx: number,
|
|
407
|
+
repeatCount: number,
|
|
408
|
+
numAttributes: number,
|
|
409
|
+
timeRadians: number,
|
|
410
|
+
deviceTimeMs: number
|
|
411
|
+
): number[] {
|
|
412
|
+
const amplitude = 0.8;
|
|
413
|
+
|
|
414
|
+
if (repeatCount > 1) {
|
|
415
|
+
const useThermalGrid = (attr && typeof attr.resolution === "string") || repeatCount >= 16;
|
|
416
|
+
if (useThermalGrid) {
|
|
417
|
+
return this._generateThermalGridValues(attr, repeatCount, timeRadians, deviceTimeMs);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const values: number[] = [];
|
|
421
|
+
for (let elemIdx = 0; elemIdx < repeatCount; elemIdx++) {
|
|
422
|
+
const phaseOffset = (2 * Math.PI * (attrIdx + elemIdx / repeatCount)) / Math.max(1, numAttributes);
|
|
423
|
+
const sinValue = Math.sin(timeRadians + phaseOffset);
|
|
424
|
+
|
|
425
|
+
if (Array.isArray(attr.r) && attr.r.length >= 2) {
|
|
426
|
+
const minValue = attr.r[0];
|
|
427
|
+
const maxValue = attr.r[1];
|
|
428
|
+
const midPoint = (maxValue + minValue) / 2;
|
|
429
|
+
const range = (maxValue - minValue) / 2;
|
|
430
|
+
const value = midPoint + sinValue * range * amplitude;
|
|
431
|
+
values.push(Math.min(maxValue, Math.max(minValue, value)));
|
|
432
|
+
} else {
|
|
433
|
+
values.push(sinValue * 1000 * amplitude);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return values;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const phaseOffset = numAttributes > 0 ? (2 * Math.PI * attrIdx) / numAttributes : 0;
|
|
440
|
+
const sinValue = Math.sin(timeRadians + phaseOffset);
|
|
441
|
+
|
|
442
|
+
if (Array.isArray(attr.r) && attr.r.length >= 2) {
|
|
443
|
+
const minValue = attr.r[0];
|
|
444
|
+
const maxValue = attr.r[1];
|
|
445
|
+
const midPoint = (maxValue + minValue) / 2;
|
|
446
|
+
const range = (maxValue - minValue) / 2;
|
|
447
|
+
const value = midPoint + sinValue * range * amplitude;
|
|
448
|
+
return [Math.min(maxValue, Math.max(minValue, value))];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return [sinValue * 1000 * amplitude];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private _generateThermalGridValues(
|
|
455
|
+
attr: any,
|
|
456
|
+
repeatCount: number,
|
|
457
|
+
timeRadians: number,
|
|
458
|
+
deviceTimeMs: number
|
|
459
|
+
): number[] {
|
|
460
|
+
const { rows, cols } = this._getGridDimensions(attr, repeatCount);
|
|
461
|
+
const values: number[] = [];
|
|
462
|
+
const ambientBase = 24 + 2 * Math.sin(deviceTimeMs / 7000);
|
|
463
|
+
const hotspotPhase = deviceTimeMs / 3200;
|
|
464
|
+
const hotspotRow = (Math.sin(hotspotPhase) + 1) * (rows - 1) / 2;
|
|
465
|
+
const hotspotCol = (Math.cos(hotspotPhase) + 1) * (cols - 1) / 2;
|
|
466
|
+
const hotspotAmplitude = 6;
|
|
467
|
+
const sigma = Math.max(rows, cols) / 3 || 1;
|
|
468
|
+
|
|
469
|
+
for (let idx = 0; idx < repeatCount; idx++) {
|
|
470
|
+
const row = Math.floor(idx / cols);
|
|
471
|
+
const col = idx % cols;
|
|
472
|
+
const dist = Math.hypot(row - hotspotRow, col - hotspotCol);
|
|
473
|
+
const hotspot = hotspotAmplitude * Math.exp(-(dist * dist) / (2 * sigma * sigma));
|
|
474
|
+
const gentleWave = 0.5 * Math.sin(timeRadians + row * 0.35 + col * 0.25);
|
|
475
|
+
let value = ambientBase + hotspot + gentleWave;
|
|
476
|
+
|
|
477
|
+
if (Array.isArray(attr.r) && attr.r.length >= 2) {
|
|
478
|
+
value = Math.min(attr.r[1], Math.max(attr.r[0], value));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
values.push(value);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return values;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private _fillCustomRawData(
|
|
488
|
+
deviceTypeInfo: DeviceTypeInfo,
|
|
489
|
+
dataView: DataView,
|
|
490
|
+
bytePos: number,
|
|
491
|
+
dataBlockSizeBytes: number,
|
|
492
|
+
deviceIntervalMs: number,
|
|
493
|
+
deviceTimeMs: number
|
|
494
|
+
): boolean {
|
|
495
|
+
if (deviceTypeInfo.type !== "LTR-329") {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (dataBlockSizeBytes < 4) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const frequencyHz = (deviceIntervalMs > 0)
|
|
504
|
+
? (1000 / deviceIntervalMs) / 10
|
|
505
|
+
: 0.1;
|
|
506
|
+
const timeRadians = deviceTimeMs * frequencyHz * (2 * Math.PI) / 1000;
|
|
507
|
+
|
|
508
|
+
const range = deviceTypeInfo.resp?.a?.[0]?.r ?? [0, 64000];
|
|
509
|
+
const minLux = range[0] ?? 0;
|
|
510
|
+
const maxLux = range[1] ?? 64000;
|
|
511
|
+
|
|
512
|
+
const baseLux = (maxLux + minLux) / 4;
|
|
513
|
+
const amplitudeLux = (maxLux - minLux) / 6;
|
|
514
|
+
let combined = Math.round(baseLux + amplitudeLux * Math.sin(timeRadians));
|
|
515
|
+
combined = Math.max(minLux, Math.min(maxLux, combined));
|
|
516
|
+
|
|
517
|
+
const irBase = combined * 0.35;
|
|
518
|
+
const irVariance = (combined * 0.15) * Math.sin(timeRadians + Math.PI / 4);
|
|
519
|
+
let ir = Math.round(irBase + irVariance);
|
|
520
|
+
ir = Math.max(minLux, Math.min(combined, ir));
|
|
521
|
+
|
|
522
|
+
dataView.setUint16(bytePos, combined, true);
|
|
523
|
+
dataView.setUint16(bytePos + 2, ir, true);
|
|
524
|
+
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private _getGridDimensions(attr: any, repeatCount: number): { rows: number; cols: number } {
|
|
529
|
+
if (attr && typeof attr.resolution === "string") {
|
|
530
|
+
const match = attr.resolution.match(/(\d+)\s*x\s*(\d+)/i);
|
|
531
|
+
if (match) {
|
|
532
|
+
const rows = parseInt(match[1], 10);
|
|
533
|
+
const cols = parseInt(match[2], 10);
|
|
534
|
+
if (rows > 0 && cols > 0) {
|
|
535
|
+
return { rows, cols };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const side = Math.round(Math.sqrt(repeatCount));
|
|
541
|
+
if (side > 0 && side * side === repeatCount) {
|
|
542
|
+
return { rows: side, cols: side };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return { rows: repeatCount, cols: 1 };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private _prepareRawValue(attr: any, typeCode: string, scaledValue: number): number {
|
|
549
|
+
if (this._isFloatType(typeCode)) {
|
|
550
|
+
return scaledValue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let raw = scaledValue;
|
|
554
|
+
|
|
555
|
+
if (attr && typeof attr.a === "number") {
|
|
556
|
+
raw -= attr.a;
|
|
557
|
+
}
|
|
558
|
+
if (attr && typeof attr.d === "number") {
|
|
559
|
+
raw *= attr.d;
|
|
560
|
+
}
|
|
561
|
+
if (attr && typeof attr.s === "number" && attr.s !== 0) {
|
|
562
|
+
const shift = attr.s;
|
|
563
|
+
const shiftFactor = Math.pow(2, Math.abs(shift));
|
|
564
|
+
if (shift > 0) {
|
|
565
|
+
raw *= shiftFactor;
|
|
566
|
+
} else {
|
|
567
|
+
raw /= shiftFactor;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return Math.round(raw);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private _writeRawValueToBuffer(
|
|
575
|
+
dataView: DataView,
|
|
576
|
+
bytePos: number,
|
|
577
|
+
typeCode: string,
|
|
578
|
+
littleEndian: boolean,
|
|
579
|
+
rawValue: number
|
|
580
|
+
): number {
|
|
581
|
+
const valueSize = this._byteSizeForType(typeCode);
|
|
582
|
+
if (valueSize <= 0 || bytePos + valueSize > dataView.byteLength) {
|
|
583
|
+
return -1;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
switch (typeCode) {
|
|
587
|
+
case "b":
|
|
588
|
+
dataView.setInt8(bytePos, this._clampRawValue(rawValue, typeCode));
|
|
589
|
+
break;
|
|
590
|
+
case "c":
|
|
591
|
+
dataView.setInt8(bytePos, this._clampRawValue(rawValue, typeCode));
|
|
592
|
+
break;
|
|
593
|
+
case "B":
|
|
594
|
+
case "C":
|
|
595
|
+
dataView.setUint8(bytePos, this._clampRawValue(rawValue, typeCode));
|
|
596
|
+
break;
|
|
597
|
+
case "?":
|
|
598
|
+
dataView.setUint8(bytePos, rawValue ? 1 : 0);
|
|
599
|
+
break;
|
|
600
|
+
case "h":
|
|
601
|
+
dataView.setInt16(bytePos, this._clampRawValue(rawValue, typeCode), littleEndian);
|
|
602
|
+
break;
|
|
603
|
+
case "H":
|
|
604
|
+
dataView.setUint16(bytePos, this._clampRawValue(rawValue, typeCode), littleEndian);
|
|
605
|
+
break;
|
|
606
|
+
case "i":
|
|
607
|
+
case "l":
|
|
608
|
+
dataView.setInt32(bytePos, this._clampRawValue(rawValue, typeCode), littleEndian);
|
|
609
|
+
break;
|
|
610
|
+
case "I":
|
|
611
|
+
case "L":
|
|
612
|
+
dataView.setUint32(bytePos, this._clampRawValue(rawValue, typeCode), littleEndian);
|
|
613
|
+
break;
|
|
614
|
+
case "f":
|
|
615
|
+
dataView.setFloat32(bytePos, rawValue, littleEndian);
|
|
616
|
+
break;
|
|
617
|
+
case "d":
|
|
618
|
+
dataView.setFloat64(bytePos, rawValue, littleEndian);
|
|
619
|
+
break;
|
|
620
|
+
default:
|
|
621
|
+
RaftLog.warn(`RaftChannelSimulated._writeRawValueToBuffer - unsupported attribute type ${typeCode}`);
|
|
622
|
+
return -1;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return bytePos + valueSize;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private _byteSizeForType(typeCode: string): number {
|
|
629
|
+
switch (typeCode) {
|
|
630
|
+
case "b":
|
|
631
|
+
case "B":
|
|
632
|
+
case "c":
|
|
633
|
+
case "C":
|
|
634
|
+
case "?":
|
|
635
|
+
return 1;
|
|
636
|
+
case "h":
|
|
637
|
+
case "H":
|
|
638
|
+
return 2;
|
|
639
|
+
case "i":
|
|
640
|
+
case "I":
|
|
641
|
+
case "l":
|
|
642
|
+
case "L":
|
|
643
|
+
case "f":
|
|
644
|
+
return 4;
|
|
645
|
+
case "d":
|
|
646
|
+
return 8;
|
|
647
|
+
default:
|
|
648
|
+
return 0;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private _clampRawValue(rawValue: number, typeCode: string): number {
|
|
653
|
+
const value = Math.round(rawValue);
|
|
654
|
+
|
|
655
|
+
switch (typeCode) {
|
|
656
|
+
case "b":
|
|
657
|
+
case "c":
|
|
658
|
+
return Math.max(-128, Math.min(127, value));
|
|
659
|
+
case "B":
|
|
660
|
+
case "C":
|
|
661
|
+
case "?":
|
|
662
|
+
return Math.max(0, Math.min(255, value));
|
|
663
|
+
case "h":
|
|
664
|
+
return Math.max(-32768, Math.min(32767, value));
|
|
665
|
+
case "H":
|
|
666
|
+
return Math.max(0, Math.min(65535, value));
|
|
667
|
+
case "i":
|
|
668
|
+
case "l":
|
|
669
|
+
return Math.max(-2147483648, Math.min(2147483647, value));
|
|
670
|
+
case "I":
|
|
671
|
+
case "L":
|
|
672
|
+
return Math.max(0, Math.min(4294967295, value));
|
|
673
|
+
default:
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private _isFloatType(typeCode: string): boolean {
|
|
679
|
+
return typeCode === "f" || typeCode === "d";
|
|
680
|
+
}
|
|
681
|
+
|
|
404
682
|
// Helper function to convert bytes to hex string
|
|
405
683
|
private _bytesToHexStr(bytes: Uint8Array): string {
|
|
406
684
|
return Array.from(bytes)
|
|
@@ -411,6 +689,29 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
411
689
|
// Simulated device type information - this is a copy of part of DeviceTypeInfo in RaftCore
|
|
412
690
|
private _deviceTypeInfo: DeviceTypeInfoRecs =
|
|
413
691
|
{
|
|
692
|
+
"AMG8833": {
|
|
693
|
+
"name": "AMG8833",
|
|
694
|
+
"desc": "Thermal Camera",
|
|
695
|
+
"manu": "Panasonic",
|
|
696
|
+
"type": "AMG8833",
|
|
697
|
+
"clas": ["TCAM"],
|
|
698
|
+
"resp": {
|
|
699
|
+
"b": 128,
|
|
700
|
+
"a": [
|
|
701
|
+
{
|
|
702
|
+
"n": "temp",
|
|
703
|
+
"t": "<h[64]",
|
|
704
|
+
"resolution": "8x8",
|
|
705
|
+
"u": "°C",
|
|
706
|
+
"r": [-55, 125],
|
|
707
|
+
"s": -4,
|
|
708
|
+
"d": 64,
|
|
709
|
+
"f": ".2f",
|
|
710
|
+
"o": "float"
|
|
711
|
+
}
|
|
712
|
+
]
|
|
713
|
+
}
|
|
714
|
+
},
|
|
414
715
|
"LSM6DS": {
|
|
415
716
|
"name": "LSM6DS",
|
|
416
717
|
"desc": "6-Axis IMU",
|
|
@@ -476,7 +777,40 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
476
777
|
}
|
|
477
778
|
]
|
|
478
779
|
}
|
|
780
|
+
},
|
|
781
|
+
"LTR-329": {
|
|
782
|
+
"name": "LTR-329",
|
|
783
|
+
"desc": "Visible light and IR Sensor",
|
|
784
|
+
"manu": "Lite On",
|
|
785
|
+
"type": "LTR-329",
|
|
786
|
+
"clas": ["LGHT"],
|
|
787
|
+
"resp": {
|
|
788
|
+
"b": 4,
|
|
789
|
+
"a": [
|
|
790
|
+
{
|
|
791
|
+
"n": "ir",
|
|
792
|
+
"t": "<h",
|
|
793
|
+
"u": "lux",
|
|
794
|
+
"r": [0, 64000],
|
|
795
|
+
"f": "d",
|
|
796
|
+
"o": "uint16"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"n": "visible",
|
|
800
|
+
"t": "<h",
|
|
801
|
+
"u": "lux",
|
|
802
|
+
"r": [0, 64000],
|
|
803
|
+
"f": "d",
|
|
804
|
+
"o": "uint16"
|
|
805
|
+
}
|
|
806
|
+
],
|
|
807
|
+
"c": {
|
|
808
|
+
"n": "ltr329_light_calc",
|
|
809
|
+
"c": "int combined = buf[0] + (((uint16_t)buf[1])<<8); out.ir = buf[2] + (((uint16_t)buf[3])<<8); out.visible = combined - out.ir;",
|
|
810
|
+
"j": "let combined = buf[0] + (buf[1] << 8); let ir = buf[2] + (buf[3] << 8); attrValues['ir'].push(ir); attrValues['visible'].push(Math.max(0, combined - ir));"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
479
813
|
}
|
|
480
814
|
};
|
|
481
815
|
|
|
482
|
-
}
|
|
816
|
+
}
|