@robotical/raftjs 2.0.5 → 2.0.7
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 +1 -0
- package/dist/react-native/RaftChannelSimulated.js +179 -23
- 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 +1 -0
- package/dist/web/RaftChannelSimulated.d.ts +1 -0
- package/dist/web/RaftChannelSimulated.js +179 -23
- 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 +1 -0
- package/package.json +1 -1
- package/src/RaftChannelSimulated.ts +221 -38
- package/src/RaftCustomAttrHandler.ts +81 -35
- package/src/RaftDeviceInfo.ts +1 -0
|
@@ -296,49 +296,62 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
296
296
|
dataView.setUint16(bytePos, deviceTimeMs % 65536, false);
|
|
297
297
|
bytePos += 2;
|
|
298
298
|
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const nextBytePos = this._writeRawValueToBuffer(
|
|
328
|
-
dataView,
|
|
329
|
-
bytePos,
|
|
330
|
-
typeCode,
|
|
331
|
-
littleEndian,
|
|
332
|
-
rawValue
|
|
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;
|
|
314
|
+
const timeRadians = deviceTimeMs * frequencyHz * (2 * Math.PI) / 1000;
|
|
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
|
|
333
327
|
);
|
|
334
328
|
|
|
335
|
-
if (
|
|
336
|
-
RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg -
|
|
337
|
-
|
|
329
|
+
if (scaledValues.length !== repeatCount) {
|
|
330
|
+
RaftLog.warn(`RaftChannelSimulated._createSimulatedDeviceInfoMsg - value count mismatch for ${attr.n}`);
|
|
331
|
+
continue;
|
|
338
332
|
}
|
|
339
333
|
|
|
340
|
-
|
|
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
|
+
}
|
|
341
352
|
}
|
|
353
|
+
} else {
|
|
354
|
+
bytePos += dataBlockSizeBytes;
|
|
342
355
|
}
|
|
343
356
|
|
|
344
357
|
// Convert the buffer to a byte array
|
|
@@ -471,6 +484,80 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
471
484
|
return values;
|
|
472
485
|
}
|
|
473
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
|
+
switch (deviceTypeInfo.type) {
|
|
496
|
+
case "LTR-329": {
|
|
497
|
+
if (dataBlockSizeBytes < 4) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const frequencyHz = (deviceIntervalMs > 0)
|
|
502
|
+
? (1000 / deviceIntervalMs) / 10
|
|
503
|
+
: 0.1;
|
|
504
|
+
const timeRadians = deviceTimeMs * frequencyHz * (2 * Math.PI) / 1000;
|
|
505
|
+
|
|
506
|
+
const range = deviceTypeInfo.resp?.a?.[0]?.r ?? [0, 64000];
|
|
507
|
+
const minLux = range[0] ?? 0;
|
|
508
|
+
const maxLux = range[1] ?? 64000;
|
|
509
|
+
|
|
510
|
+
const baseLux = (maxLux + minLux) / 4;
|
|
511
|
+
const amplitudeLux = (maxLux - minLux) / 6;
|
|
512
|
+
let combined = Math.round(baseLux + amplitudeLux * Math.sin(timeRadians));
|
|
513
|
+
combined = Math.max(minLux, Math.min(maxLux, combined));
|
|
514
|
+
|
|
515
|
+
const irBase = combined * 0.35;
|
|
516
|
+
const irVariance = (combined * 0.15) * Math.sin(timeRadians + Math.PI / 4);
|
|
517
|
+
let ir = Math.round(irBase + irVariance);
|
|
518
|
+
ir = Math.max(minLux, Math.min(combined, ir));
|
|
519
|
+
|
|
520
|
+
dataView.setUint16(bytePos, combined, true);
|
|
521
|
+
dataView.setUint16(bytePos + 2, ir, true);
|
|
522
|
+
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
case "RoboticalServo": {
|
|
526
|
+
if (dataBlockSizeBytes < 6) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const swingAmplitudeDeg = 90;
|
|
531
|
+
const cycleMs = 4000;
|
|
532
|
+
const angularSpeedRadPerMs = (2 * Math.PI) / cycleMs;
|
|
533
|
+
const phaseRadians = (deviceTimeMs % cycleMs) * angularSpeedRadPerMs;
|
|
534
|
+
|
|
535
|
+
const angleDegrees = swingAmplitudeDeg * Math.sin(phaseRadians);
|
|
536
|
+
const velocityDegPerSec = swingAmplitudeDeg * angularSpeedRadPerMs * 1000 * Math.cos(phaseRadians);
|
|
537
|
+
const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(max, value));
|
|
538
|
+
|
|
539
|
+
const angleRaw = clamp(Math.round(angleDegrees * 10), -1800, 1800);
|
|
540
|
+
let velocityRaw = Math.round(velocityDegPerSec);
|
|
541
|
+
velocityRaw = clamp(velocityRaw, -32768, 32767);
|
|
542
|
+
|
|
543
|
+
let currentRaw = Math.round(20 + 0.08 * Math.abs(velocityDegPerSec));
|
|
544
|
+
currentRaw = clamp(currentRaw, 0, 127);
|
|
545
|
+
|
|
546
|
+
const isMoving = Math.abs(velocityDegPerSec) > 5;
|
|
547
|
+
const stateRaw = (0x02) | (isMoving ? 0x01 : 0);
|
|
548
|
+
|
|
549
|
+
dataView.setInt16(bytePos, angleRaw, false);
|
|
550
|
+
dataView.setInt8(bytePos + 2, currentRaw);
|
|
551
|
+
dataView.setUint8(bytePos + 3, stateRaw);
|
|
552
|
+
dataView.setInt16(bytePos + 4, velocityRaw, false);
|
|
553
|
+
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
default:
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
474
561
|
private _getGridDimensions(attr: any, repeatCount: number): { rows: number; cols: number } {
|
|
475
562
|
if (attr && typeof attr.resolution === "string") {
|
|
476
563
|
const match = attr.resolution.match(/(\d+)\s*x\s*(\d+)/i);
|
|
@@ -723,6 +810,102 @@ export default class RaftChannelSimulated implements RaftChannel {
|
|
|
723
810
|
}
|
|
724
811
|
]
|
|
725
812
|
}
|
|
813
|
+
},
|
|
814
|
+
"LTR-329": {
|
|
815
|
+
"name": "LTR-329",
|
|
816
|
+
"desc": "Visible light and IR Sensor",
|
|
817
|
+
"manu": "Lite On",
|
|
818
|
+
"type": "LTR-329",
|
|
819
|
+
"clas": ["LGHT"],
|
|
820
|
+
"resp": {
|
|
821
|
+
"b": 4,
|
|
822
|
+
"a": [
|
|
823
|
+
{
|
|
824
|
+
"n": "ir",
|
|
825
|
+
"t": "<h",
|
|
826
|
+
"u": "lux",
|
|
827
|
+
"r": [0, 64000],
|
|
828
|
+
"f": "d",
|
|
829
|
+
"o": "uint16"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"n": "visible",
|
|
833
|
+
"t": "<h",
|
|
834
|
+
"u": "lux",
|
|
835
|
+
"r": [0, 64000],
|
|
836
|
+
"f": "d",
|
|
837
|
+
"o": "uint16"
|
|
838
|
+
}
|
|
839
|
+
],
|
|
840
|
+
"c": {
|
|
841
|
+
"n": "ltr329_light_calc",
|
|
842
|
+
"c": "int combined = buf[0] + (((uint16_t)buf[1])<<8); out.ir = buf[2] + (((uint16_t)buf[3])<<8); out.visible = combined - out.ir;",
|
|
843
|
+
"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));"
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
"RoboticalServo": {
|
|
848
|
+
"name": "Robotical Servo",
|
|
849
|
+
"desc": "Servo",
|
|
850
|
+
"manu": "Robotical",
|
|
851
|
+
"type": "RoboticalServo",
|
|
852
|
+
"clas": ["SRVO"],
|
|
853
|
+
"resp": {
|
|
854
|
+
"b": 6,
|
|
855
|
+
"a": [
|
|
856
|
+
{
|
|
857
|
+
"n": "angle",
|
|
858
|
+
"t": ">h",
|
|
859
|
+
"r": [-180.0, 180.0],
|
|
860
|
+
"f": ".1f",
|
|
861
|
+
"d": 10,
|
|
862
|
+
"o": "int16",
|
|
863
|
+
"u": "degrees"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
"n": "current",
|
|
867
|
+
"t": "b",
|
|
868
|
+
"r": [-128, 127],
|
|
869
|
+
"f": "d",
|
|
870
|
+
"o": "int8"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
"n": "state",
|
|
874
|
+
"t": "B",
|
|
875
|
+
"r": [0, 255],
|
|
876
|
+
"f": "02x",
|
|
877
|
+
"o": "uint8"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"n": "velocity",
|
|
881
|
+
"t": ">h",
|
|
882
|
+
"r": [-32768, 32767],
|
|
883
|
+
"f": "d",
|
|
884
|
+
"o": "int16"
|
|
885
|
+
}
|
|
886
|
+
]
|
|
887
|
+
},
|
|
888
|
+
"actions": [
|
|
889
|
+
{
|
|
890
|
+
"n": "angle",
|
|
891
|
+
"t": ">h",
|
|
892
|
+
"w": "0001",
|
|
893
|
+
"wz": "0064",
|
|
894
|
+
"f": ".1f",
|
|
895
|
+
"mul": 10,
|
|
896
|
+
"sub": 0,
|
|
897
|
+
"r": [-180.0, 180.0],
|
|
898
|
+
"d": 0
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
"n": "enable",
|
|
902
|
+
"t": "B",
|
|
903
|
+
"w": "20",
|
|
904
|
+
"f": "b",
|
|
905
|
+
"r": [0, 1],
|
|
906
|
+
"d": 1
|
|
907
|
+
}
|
|
908
|
+
]
|
|
726
909
|
}
|
|
727
910
|
};
|
|
728
911
|
|
|
@@ -7,23 +7,32 @@
|
|
|
7
7
|
//
|
|
8
8
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
9
9
|
|
|
10
|
-
import { DeviceTypePollRespMetadata } from "./RaftDeviceInfo";
|
|
10
|
+
import { CustomFunctionDefinition, DeviceTypePollRespMetadata } from "./RaftDeviceInfo";
|
|
11
|
+
|
|
12
|
+
type CustomAttrJsFn = (
|
|
13
|
+
buf: Uint8Array,
|
|
14
|
+
attrValues: Record<string, number[]>,
|
|
15
|
+
attrValueVecs: number[][],
|
|
16
|
+
pollRespMetadata: DeviceTypePollRespMetadata,
|
|
17
|
+
msgBuffer: Uint8Array,
|
|
18
|
+
msgBufIdx: number,
|
|
19
|
+
numMsgBytes: number
|
|
20
|
+
) => void;
|
|
11
21
|
|
|
12
22
|
export default class CustomAttrHandler {
|
|
23
|
+
|
|
24
|
+
private _jsFunctionCache = new Map<string, CustomAttrJsFn>();
|
|
13
25
|
|
|
14
26
|
public handleAttr(pollRespMetadata: DeviceTypePollRespMetadata, msgBuffer: Uint8Array, msgBufIdx: number): number[][] {
|
|
15
27
|
|
|
16
|
-
//
|
|
17
|
-
// 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;}
|
|
18
|
-
|
|
19
|
-
// Number of bytes in the each message
|
|
28
|
+
// Number of bytes in each message
|
|
20
29
|
const numMsgBytes = pollRespMetadata.b;
|
|
21
30
|
|
|
22
31
|
// Create a vector for each attribute in the metadata
|
|
23
|
-
const attrValueVecs: [][] = [];
|
|
32
|
+
const attrValueVecs: number[][] = [];
|
|
24
33
|
|
|
25
34
|
// Reference to each vector by attribute name
|
|
26
|
-
const attrValues:
|
|
35
|
+
const attrValues: Record<string, number[]> = {};
|
|
27
36
|
|
|
28
37
|
// Add attributes to the vector
|
|
29
38
|
for (let attrIdx = 0; attrIdx < pollRespMetadata.a.length; attrIdx++) {
|
|
@@ -31,41 +40,78 @@ export default class CustomAttrHandler {
|
|
|
31
40
|
attrValues[pollRespMetadata.a[attrIdx].n] = attrValueVecs[attrIdx];
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const customFnDef = pollRespMetadata.c;
|
|
44
|
+
if (!customFnDef) {
|
|
45
|
+
return attrValueVecs;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Provide the message buffer sliced to the data portion
|
|
49
|
+
const buf = msgBuffer.slice(msgBufIdx);
|
|
50
|
+
if (buf.length < numMsgBytes) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Execute supplied JS implementation if provided
|
|
55
|
+
if (customFnDef.j && customFnDef.j.trim().length > 0) {
|
|
56
|
+
const jsFn = this.getOrCompileJsFunction(customFnDef);
|
|
57
|
+
if (!jsFn) {
|
|
58
|
+
return attrValueVecs;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
jsFn(buf, attrValues, attrValueVecs, pollRespMetadata, msgBuffer, msgBufIdx, numMsgBytes);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(`CustomAttrHandler JS function ${customFnDef.n} execution failed`, err);
|
|
41
64
|
}
|
|
65
|
+
return attrValueVecs;
|
|
66
|
+
}
|
|
42
67
|
|
|
68
|
+
// Custom code for each device type handled natively
|
|
69
|
+
if (customFnDef.n === "max30101_fifo") {
|
|
43
70
|
// Generated code ...
|
|
44
|
-
const N=(buf[0]+32-buf[2])%32;
|
|
45
|
-
let k=3;
|
|
46
|
-
let i=0;
|
|
47
|
-
while (i<N) {
|
|
48
|
-
attrValues[
|
|
49
|
-
attrValues[
|
|
50
|
-
|
|
71
|
+
const N = (buf[0] + 32 - buf[2]) % 32;
|
|
72
|
+
let k = 3;
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < N) {
|
|
75
|
+
attrValues["Red"].push(0);
|
|
76
|
+
attrValues["Red"][attrValues["Red"].length - 1] = (buf[k] << 16) | (buf[k + 1] << 8) | buf[k + 2];
|
|
77
|
+
attrValues["IR"].push(0);
|
|
78
|
+
attrValues["IR"][attrValues["IR"].length - 1] = (buf[k + 3] << 16) | (buf[k + 4] << 8) | buf[k + 5];
|
|
79
|
+
k += 6;
|
|
51
80
|
i++;
|
|
52
|
-
;
|
|
53
|
-
}
|
|
54
|
-
} else if (pollRespMetadata.c!.n === "gravity_o2_calc") {
|
|
55
|
-
// Get the buffer
|
|
56
|
-
const buf = msgBuffer.slice(msgBufIdx);
|
|
57
|
-
if (buf.length < numMsgBytes) {
|
|
58
|
-
return [];
|
|
59
81
|
}
|
|
60
|
-
|
|
61
|
-
// Implement the pseudo-code:
|
|
62
|
-
// float key = 20.9/120.0; float val = key * (buf[0] + (buf[1]/10.0) + (buf[2]/100.0)); out.oxygen = val;
|
|
82
|
+
} else if (customFnDef.n === "gravity_o2_calc") {
|
|
63
83
|
const key = 20.9 / 120.0;
|
|
64
|
-
const val = key * (buf[0] +
|
|
65
|
-
|
|
66
|
-
// Add the value to the oxygen attribute
|
|
67
|
-
attrValues['oxygen'].push(val);
|
|
84
|
+
const val = key * (buf[0] + buf[1] / 10.0 + buf[2] / 100.0);
|
|
85
|
+
attrValues["oxygen"].push(val);
|
|
68
86
|
}
|
|
69
87
|
return attrValueVecs;
|
|
70
88
|
}
|
|
89
|
+
|
|
90
|
+
private getOrCompileJsFunction(customFnDef: CustomFunctionDefinition): CustomAttrJsFn | null {
|
|
91
|
+
if (!customFnDef.j) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const cacheKey = `${customFnDef.n}::${customFnDef.j}`;
|
|
95
|
+
const cachedFn = this._jsFunctionCache.get(cacheKey);
|
|
96
|
+
if (cachedFn) {
|
|
97
|
+
return cachedFn;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const compiledFn = new Function(
|
|
101
|
+
"buf",
|
|
102
|
+
"attrValues",
|
|
103
|
+
"attrValueVecs",
|
|
104
|
+
"pollRespMetadata",
|
|
105
|
+
"msgBuffer",
|
|
106
|
+
"msgBufIdx",
|
|
107
|
+
"numMsgBytes",
|
|
108
|
+
customFnDef.j
|
|
109
|
+
) as CustomAttrJsFn;
|
|
110
|
+
this._jsFunctionCache.set(cacheKey, compiledFn);
|
|
111
|
+
return compiledFn;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(`CustomAttrHandler failed to compile JS function ${customFnDef.n}`, err);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
71
117
|
}
|
package/src/RaftDeviceInfo.ts
CHANGED
|
@@ -79,6 +79,7 @@ export interface DeviceTypeAttribute {
|
|
|
79
79
|
export interface CustomFunctionDefinition {
|
|
80
80
|
n: string; // Function name
|
|
81
81
|
c: string; // Function pseudo-code
|
|
82
|
+
j?: string; // Optional JavaScript implementation
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export interface DeviceTypePollRespMetadata {
|