Skip to content

Commit 90f4ccf

Browse files
marknolanclaude
andcommitted
DEV-793 Address round-3 review: tick-wrap fix, self-conflict ids, gap window + round-2 leftovers
CRITICAL (review #1, empirically confirmed - 22 minute-boundary drops in the Test_061 recording alone): the slow-sensor rate refinement wrapped inter-block tick deltas at 2^24, but per-block ticks are a SUB-MINUTE counter (reset at TICKS_PER_MINUTE - the same semantics the minute-rollover backfill depends on), so every minute crossing inflated the period ~8.5x. Negative deltas are now re-based by one minute. MAJOR (review #2): SensorVD6283/SensorMLX90632 - and the SensorLSM6DSV refs they were copied from - passed their OWN SENSOR_ID as listOfSensorIdsConflicting (5th ctor arg), so setSensorEnabledState() would self-disable them; now empty per the gen-1 convention (e.g. SENSOR_GSR_VERISENSE). Latent only - the SD-parse path is bitmap-driven; all references bit-identical. MAJOR (review #3): the CSV gap-split window is now seeded from the OBSERVED per-sample period spread [min,max] with the standard tolerance rather than a single-rate +/-10 percent band - the slow sensors' cadence is bimodal (exposure vs exposure+dead-time, ~100/110 ms for the light), so any single-centre band can clip real spacing and fragment the CSV (a band centred on the achieved median rate demonstrably split Test_062). A dropped block (2x period) still splits. MINORs: McCamy CCT guarded against a zero denominator and clamped to >=0 (review #6); pre-v13 files carrying gen-2 block ids now fail with the informative IOException instead of an NPE - their sensor classes are only registered for v13+ (review #7); Lux/CCT marked CHANNEL_SOURCE.API per the SensorGSR convention (review #5); DEFAULT_EXPO_US=100800 vs table[0]=100000 documented as firmware-faithful, verified against App_vd6283tx.c - review #4 refuted; guard comment rewritten (review #9); refinement variable renamed (review #10). Round-2 leftovers: the LSM6DSV data-parse now uses the mag as the reference stream when neither accel nor gyro is enabled (split halves of a mag-only block get exact ranges; mirrors countLsm6dsvAlignedSamples - defensive, not firmware-producible); stale 'mag bytes left zero' javadoc corrected; API_00008 gains CAL assertions locking the accel (835.3517 LSB/(m/s^2)) and corrected gyro (57.142857 LSB/dps) constants as literals. Full ASM_PC_00005 suite green (139 tests incl. suite wrapper), API_00008 green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent 39aeb92 commit 90f4ccf

6 files changed

Lines changed: 129 additions & 43 deletions

File tree

ShimmerDriver/src/main/java/com/shimmerresearch/verisense/VerisenseDevice.java

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,15 +1945,20 @@ public DataBlockDetails parseDataBlockMetaData(byte[] byteBuffer, int dataBlockS
19451945

19461946
DataBlockDetails dataBlockDetails = null;
19471947
DATABLOCK_SENSOR_ID[] payloadSensorIdValues = DATABLOCK_SENSOR_ID.values();
1948-
// Only IDs with a parse implementation are accepted. The enum intentionally
1949-
// declares the remaining second-generation block IDs (LIGHT, ALGO_HUB,
1950-
// SKIN_TEMP, FLICKER) ahead of their parser support - accepting them here
1951-
// without a getSensorKeysForDatablockId()/calculateFifoBlockSize() handler
1952-
// would corrupt the parse, so they are rejected with the informative
1953-
// exception below until their decoders are implemented.
1954-
// Parse-supported block ids: everything up to LIGHT (7), plus SKIN_TEMP (9).
1955-
// ALGO_HUB (8) and FLICKER (10) still fail loudly below until their decoders land.
1956-
if(sensorId>0 && (sensorId<=DATABLOCK_SENSOR_ID.LIGHT.ordinal() || sensorId==DATABLOCK_SENSOR_ID.SKIN_TEMP.ordinal())) {
1948+
// Only IDs with a parse implementation are accepted; anything else is
1949+
// rejected with the informative exception below (accepting an unhandled ID
1950+
// would corrupt the parse). Parse-supported: the gen-1 ids (1-5) on any
1951+
// payload design, and the second-generation ids LSM6DSV (6), LIGHT (7) and
1952+
// SKIN_TEMP (9) on payload design v13+ only - their sensor classes are only
1953+
// registered for v13+ devices, so a (corrupt) pre-v13 file carrying one of
1954+
// those ids must fail loudly here rather than NPE further down. ALGO_HUB (8)
1955+
// and FLICKER (10) still fail loudly until their decoders land.
1956+
boolean isGen2DataBlockSensorId = sensorId==DATABLOCK_SENSOR_ID.LSM6DSV.ordinal()
1957+
|| sensorId==DATABLOCK_SENSOR_ID.LIGHT.ordinal()
1958+
|| sensorId==DATABLOCK_SENSOR_ID.SKIN_TEMP.ordinal();
1959+
boolean isParseSupported = (sensorId>0 && sensorId<DATABLOCK_SENSOR_ID.LSM6DSV.ordinal())
1960+
|| (isGen2DataBlockSensorId && isPayloadDesignV13orAbove());
1961+
if(isParseSupported) {
19571962
DATABLOCK_SENSOR_ID datablockSensorId = payloadSensorIdValues[sensorId];
19581963

19591964
List<SENSORS> listOfSensorClassKeys = getOrCreateListOfSensorClassKeysForDataBlockId(datablockSensorId);
@@ -2086,9 +2091,9 @@ private int countLsm6dsvAlignedSamples(byte[] byteBuffer, int entriesStart, int
20862091
* entries (0x04) are skipped.
20872092
* <p>
20882093
* Accel and gyro share the LSM6DSV ODR and are interleaved 1:1, so they are
2089-
* emitted as aligned samples through {@link #buildMsgForSensorList}. (Mag is read
2090-
* via the sensor hub at a different rate; full per-stream mag timestamping is TODO
2091-
* - mag bytes are currently left zero so accel/gyro stay byte-aligned.)
2094+
* emitted as aligned samples through {@link #buildMsgForSensorList}. Mag is read
2095+
* via the sensor hub at its own (lower) cadence and is emitted as a separate OJC
2096+
* stream (pass 2 below), its samples spread evenly across the block duration.
20922097
*/
20932098
private void parseDataBlockDataLsm6dsv(DataBlockDetails dataBlockDetails, byte[] byteBuffer, int currentByteIndex, COMMUNICATION_TYPE commType) {
20942099
int numEntries = (byteBuffer[currentByteIndex] & 0xFF) | ((byteBuffer[currentByteIndex+1] & 0xFF) << 8);
@@ -2101,8 +2106,11 @@ private void parseDataBlockDataLsm6dsv(DataBlockDetails dataBlockDetails, byte[]
21012106
// The aligned (accel/gyro) reference stream drives per-sample timing and, for
21022107
// midday/midnight-split blocks, defines each half's share of the FIFO. Each mag
21032108
// entry records how many reference entries preceded it in the FIFO so it can be
2104-
// assigned to the correct half by arrival order.
2105-
int refTag = accelEn ? LSM6DSV_TAG_ACCEL : LSM6DSV_TAG_GYRO;
2109+
// assigned to the correct half by arrival order. When neither accel nor gyro is
2110+
// enabled the mag itself acts as the reference (mirrors
2111+
// countLsm6dsvAlignedSamples; not firmware-producible today but expressible).
2112+
boolean magIsReference = !accelEn && !gyroEn;
2113+
int refTag = accelEn ? LSM6DSV_TAG_ACCEL : (gyroEn ? LSM6DSV_TAG_GYRO : LSM6DSV_TAG_MAG);
21062114
List<byte[]> accelSamples = new ArrayList<byte[]>();
21072115
List<byte[]> gyroSamples = new ArrayList<byte[]>();
21082116
List<byte[]> magSamples = new ArrayList<byte[]>();
@@ -2126,28 +2134,41 @@ private void parseDataBlockDataLsm6dsv(DataBlockDetails dataBlockDetails, byte[]
21262134
}
21272135

21282136
int alignedCountFull = accelEn ? accelSamples.size() : (gyroEn ? gyroSamples.size() : 0);
2137+
int referenceCountFull = magIsReference ? magSamples.size() : alignedCountFull;
21292138

21302139
// Midday/midnight-split support: both halves of a split block are handed the SAME
21312140
// raw block bytes (see the byte-offset walk in PayloadContentsDetailsV8orAbove);
2132-
// each half parses only its own aligned-sample range. The metadata-time split set
2133-
// each part's sampleCount in the aligned-sample domain.
2134-
int alignedFrom = 0;
2135-
int alignedTo = alignedCountFull;
2141+
// each half parses only its own reference-sample range. The metadata-time split
2142+
// set each part's sampleCount in the reference-stream domain (aligned accel/gyro,
2143+
// or mag when it is the reference).
2144+
int refFrom = 0;
2145+
int refTo = referenceCountFull;
21362146
if(dataBlockDetails.isFirstPartOfSplitDataBlock()) {
2137-
alignedTo = Math.min(dataBlockDetails.getSampleCount(), alignedCountFull);
2147+
refTo = Math.min(dataBlockDetails.getSampleCount(), referenceCountFull);
21382148
} else if(dataBlockDetails.isSecondPartOfSplitDataBlock()) {
2139-
alignedFrom = Math.max(0, alignedCountFull - dataBlockDetails.getSampleCount());
2149+
refFrom = Math.max(0, referenceCountFull - dataBlockDetails.getSampleCount());
21402150
}
2151+
// The aligned (pass 1) emission range: equal to the reference range normally,
2152+
// empty when the mag is the reference stream (no accel/gyro to emit).
2153+
int alignedFrom = magIsReference ? 0 : refFrom;
2154+
int alignedTo = magIsReference ? 0 : refTo;
21412155
int alignedCount = alignedTo - alignedFrom;
21422156

2143-
// Mag entries are assigned to a split half by FIFO arrival order relative to the
2144-
// aligned boundary: an entry that arrived before the first aligned sample of the
2145-
// second half belongs to the first half.
2157+
// Mag selection per split half. With an aligned reference, mag entries are
2158+
// assigned by FIFO arrival order relative to the aligned boundary (an entry
2159+
// that arrived before the first aligned sample of the second half belongs to
2160+
// the first half). When the mag IS the reference, each mag entry's recorded
2161+
// position is its own index, so the range check is direct.
21462162
List<byte[]> magSamplesPart = new ArrayList<byte[]>();
21472163
if(magEn) {
21482164
for(int i=0;i<magSamples.size();i++) {
21492165
int pos = magAlignedPos.get(i);
2150-
boolean inPart = dataBlockDetails.isSecondPartOfSplitDataBlock() ? (pos > alignedFrom) : (pos <= alignedTo);
2166+
boolean inPart;
2167+
if(magIsReference) {
2168+
inPart = (pos >= refFrom && pos < refTo);
2169+
} else {
2170+
inPart = dataBlockDetails.isSecondPartOfSplitDataBlock() ? (pos > refFrom) : (pos <= refTo);
2171+
}
21512172
if(inPart) { magSamplesPart.add(magSamples.get(i)); }
21522173
}
21532174
}

ShimmerDriver/src/main/java/com/shimmerresearch/verisense/payloaddesign/PayloadContentsDetailsV8orAbove.java

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -327,27 +327,33 @@ private boolean isParserAtEndOfBuffer(int bufferLength, int currentByteIndex) {
327327
* blocks were created with is left in place.
328328
*/
329329
private void refineSlowSensorSamplingRateFromBlockTicks(DATABLOCK_SENSOR_ID slowSensorId) {
330-
List<DataBlockDetails> lightBlocks = new ArrayList<DataBlockDetails>();
330+
List<DataBlockDetails> slowSensorBlocks = new ArrayList<DataBlockDetails>();
331331
for(DataBlockDetails dataBlockDetails:listOfDataBlocksInOrder) {
332332
if(dataBlockDetails.datablockSensorId==slowSensorId) {
333-
lightBlocks.add(dataBlockDetails);
333+
slowSensorBlocks.add(dataBlockDetails);
334334
}
335335
}
336-
if(lightBlocks.size()<2) {
336+
if(slowSensorBlocks.size()<2) {
337337
return;
338338
}
339339

340340
// v11+ payloads store microcontroller-clock ticks per block, earlier designs
341341
// store real-world-clock ticks; either works as only deltas are used.
342342
boolean useUcClockTicks = verisenseDevice.isPayloadDesignV11orAbove();
343343
List<Double> perSamplePeriodsS = new ArrayList<Double>();
344-
for(int i=1;i<lightBlocks.size();i++) {
345-
VerisenseTimeDetails prev = useUcClockTicks? lightBlocks.get(i-1).getTimeDetailsUcClock():lightBlocks.get(i-1).getTimeDetailsRwc();
346-
VerisenseTimeDetails curr = useUcClockTicks? lightBlocks.get(i).getTimeDetailsUcClock():lightBlocks.get(i).getTimeDetailsRwc();
347-
// 3-byte tick counter at 32768 Hz; handle wrap (every ~512 s, far above the
348-
// ~1 s light-block cadence).
349-
long deltaTicks = (curr.getEndTimeTicks() - prev.getEndTimeTicks()) & 0xFFFFFF;
350-
int sampleCount = lightBlocks.get(i).getSampleCount();
344+
for(int i=1;i<slowSensorBlocks.size();i++) {
345+
VerisenseTimeDetails prev = useUcClockTicks? slowSensorBlocks.get(i-1).getTimeDetailsUcClock():slowSensorBlocks.get(i-1).getTimeDetailsRwc();
346+
VerisenseTimeDetails curr = useUcClockTicks? slowSensorBlocks.get(i).getTimeDetailsUcClock():slowSensorBlocks.get(i).getTimeDetailsRwc();
347+
// The per-block ticks are a SUB-MINUTE counter (resets at
348+
// TICKS_PER_MINUTE, 32768 Hz x 60 s - the same semantics the
349+
// minute-rollover logic in backfillDataBlockUcClockOrRwcTimestamps
350+
// depends on), so a minute-boundary crossing shows as a negative
351+
// delta that must be re-based by one minute - NOT wrapped at 2^24.
352+
long deltaTicks = curr.getEndTimeTicks() - prev.getEndTimeTicks();
353+
if(deltaTicks<0) {
354+
deltaTicks += (long) AsmBinaryFileConstants.TICKS_PER_MINUTE;
355+
}
356+
int sampleCount = slowSensorBlocks.get(i).getSampleCount();
351357
if(deltaTicks>0 && sampleCount>0) {
352358
perSamplePeriodsS.add((deltaTicks/32768.0)/sampleCount);
353359
}
@@ -363,10 +369,31 @@ private void refineSlowSensorSamplingRateFromBlockTicks(DATABLOCK_SENSOR_ID slow
363369
}
364370

365371
double achievedRateHz = 1.0/medianPeriodS;
366-
for(DataBlockDetails dataBlockDetails:lightBlocks) {
372+
for(DataBlockDetails dataBlockDetails:slowSensorBlocks) {
367373
dataBlockDetails.setSamplingRate(achievedRateHz);
368374
dataBlockDetails.calculateTimestampDiffInS();
369375
}
376+
377+
// Seed the CSV gap-splitting window from the OBSERVED period spread rather
378+
// than a single-rate +/-10% band. The header-derived estimate can sit within
379+
// ~1% of the band edge (VD6283: 10 Hz estimated vs ~9.09 Hz achieved), and
380+
// the slow sensors' cadence is inherently bimodal (exposure vs exposure +
381+
// dead time: ~100 vs ~110 ms for the light at the default exposure), so a
382+
// band centred on ANY single rate can clip real spacing and fragment the
383+
// CSV. Spanning [min, max] observed period with the standard tolerance keeps
384+
// everything seen continuous while a dropped block (2x period) still splits.
385+
// populateExpectedPayloadTsDiffLimitMapIfNeeded runs after this and is
386+
// containsKey-guarded, so this seeding wins.
387+
double minPeriodS = perSamplePeriodsS.get(0);
388+
double maxPeriodS = perSamplePeriodsS.get(perSamplePeriodsS.size()-1);
389+
double[] samplingRateLimits = new double[] {
390+
(1.0/maxPeriodS)*UtilCsvSplitting.FILE_GAP_TOLERANCE_MULTIPLIER.LOWER,
391+
(1.0/minPeriodS)*UtilCsvSplitting.FILE_GAP_TOLERANCE_MULTIPLIER.UPPER};
392+
for(SENSORS sensorClassKey:verisenseDevice.getOrCreateListOfSensorClassKeysForDataBlockId(slowSensorId)) {
393+
if(sensorClassKey!=SENSORS.CLOCK && !UtilCsvSplitting.SAMPLING_RATE_LIMITS_PER_SENSOR.containsKey(sensorClassKey)) {
394+
UtilCsvSplitting.SAMPLING_RATE_LIMITS_PER_SENSOR.put(sensorClassKey, samplingRateLimits);
395+
}
396+
}
370397
}
371398

372399
private void backfillDataBlockRwcTimestamps() {

ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorLSM6DSV.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ public static final class DatabaseConfigHandle {
297297
Configuration.Verisense.SensorBitmap.LSM6DSV_ACCEL,
298298
GuiLabelSensors.ACCEL2,
299299
CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV,
300-
Arrays.asList(Configuration.Verisense.SENSOR_ID.LSM6DSV_ACCEL),
300+
// listOfSensorIdsConflicting - none (gen-1 convention, e.g. SENSOR_GSR_VERISENSE)
301+
new java.util.ArrayList<Integer>(),
301302
Arrays.asList(GuiLabelConfig.LSM6DSV_ACCEL_RANGE, GuiLabelConfig.LSM6DSV_GYRO_RANGE, GuiLabelConfig.LSM6DSV_RATE),
302303
Arrays.asList(ObjectClusterSensorName.LSM6DSV_ACC_X, ObjectClusterSensorName.LSM6DSV_ACC_Y, ObjectClusterSensorName.LSM6DSV_ACC_Z),
303304
false);
@@ -307,7 +308,8 @@ public static final class DatabaseConfigHandle {
307308
Configuration.Verisense.SensorBitmap.LSM6DSV_GYRO,
308309
GuiLabelSensors.GYRO,
309310
CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV,
310-
Arrays.asList(Configuration.Verisense.SENSOR_ID.LSM6DSV_GYRO),
311+
// listOfSensorIdsConflicting - none (gen-1 convention, e.g. SENSOR_GSR_VERISENSE)
312+
new java.util.ArrayList<Integer>(),
311313
Arrays.asList(GuiLabelConfig.LSM6DSV_ACCEL_RANGE, GuiLabelConfig.LSM6DSV_GYRO_RANGE, GuiLabelConfig.LSM6DSV_RATE),
312314
Arrays.asList(ObjectClusterSensorName.LSM6DSV_GYRO_X, ObjectClusterSensorName.LSM6DSV_GYRO_Y, ObjectClusterSensorName.LSM6DSV_GYRO_Z),
313315
false);
@@ -317,7 +319,8 @@ public static final class DatabaseConfigHandle {
317319
Configuration.Verisense.SensorBitmap.LSM6DSV_MAG,
318320
GuiLabelSensors.MAG,
319321
CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV,
320-
Arrays.asList(Configuration.Verisense.SENSOR_ID.LSM6DSV_MAG),
322+
// listOfSensorIdsConflicting - none (gen-1 convention, e.g. SENSOR_GSR_VERISENSE)
323+
new java.util.ArrayList<Integer>(),
321324
Arrays.asList(GuiLabelConfig.LIS2MDL_RATE),
322325
Arrays.asList(ObjectClusterSensorName.LIS2MDL_MAG_X, ObjectClusterSensorName.LIS2MDL_MAG_Y, ObjectClusterSensorName.LIS2MDL_MAG_Z),
323326
false);

ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorMLX90632.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public static class DatabaseChannelHandles {
7878
Configuration.Verisense.SensorBitmap.MLX90632,
7979
GuiLabelSensors.SKIN_TEMP,
8080
CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV,
81-
Arrays.asList(Configuration.Verisense.SENSOR_ID.MLX90632),
81+
// listOfSensorIdsConflicting - none (gen-1 convention, e.g. SENSOR_GSR_VERISENSE)
82+
new java.util.ArrayList<Integer>(),
8283
Arrays.asList(),
8384
Arrays.asList(ObjectClusterSensorName.SKIN_TEMP_OBJECT,
8485
ObjectClusterSensorName.SKIN_TEMP_AMBIENT),

ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorVD6283.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ public class SensorVD6283 extends AbstractSensor {
6868
public static final int[] EXPOSURE_US_TABLE = {100000, 1600, 6400, 12800, 25600, 51200, 102400, 204800};
6969
/** Op-config index -> 8.8 fixed-point gain (firmware vd6283_gainIndexToValue). */
7070
public static final int[] GAIN_8P8_TABLE = {0x0100, 0x01AB, 0x0280, 0x0500, 0x0A00, 0x1900, 0x3200, 0x42AB};
71-
/** Reference exposure the normalisation is relative to (firmware VD6283TX_DEFAULT_EXPO). */
71+
/** Reference exposure the normalisation is relative to (firmware
72+
* VD6283TX_DEFAULT_EXPO = 100800). NOTE this deliberately differs from
73+
* EXPOSURE_US_TABLE[0] (100000): the firmware normalises against 100800
74+
* regardless of the configured exposure, so a 1.008 scale factor at the
75+
* default exposure is firmware-faithful (verified against App_vd6283tx.c;
76+
* the web SDK uses the same pair). */
7277
public static final double DEFAULT_EXPO_US = 100800;
7378
/** ALS-counts -> XYZ matrix (firmware App_vd6283tx.c). Rows are X, Y, Z. */
7479
private static final double[][] XYZ_MATRIX = {
@@ -118,7 +123,8 @@ public static class DatabaseChannelHandles {
118123
Configuration.Verisense.SensorBitmap.VD6283,
119124
GuiLabelSensors.LIGHT,
120125
CompatibilityInfoForMaps.listOfCompatibleVersionInfoLSM6DSV,
121-
Arrays.asList(Configuration.Verisense.SENSOR_ID.VD6283),
126+
// listOfSensorIdsConflicting - none (gen-1 convention, e.g. SENSOR_GSR_VERISENSE)
127+
new java.util.ArrayList<Integer>(),
122128
Arrays.asList(),
123129
Arrays.asList(ObjectClusterSensorName.LIGHT_RED,
124130
ObjectClusterSensorName.LIGHT_VISIBLE,
@@ -173,6 +179,11 @@ private static ChannelDetails createRawChannel(String objectClusterName, String
173179
aMap.put(ObjectClusterSensorName.LIGHT_LUX, CHANNEL_LIGHT_LUX);
174180
aMap.put(ObjectClusterSensorName.LIGHT_CCT, CHANNEL_LIGHT_CCT);
175181
CHANNEL_MAP_REF = Collections.unmodifiableMap(aMap);
182+
183+
// Computed (API-side) channels, not part of the on-disk packet - repo
184+
// convention per SensorGSR.
185+
CHANNEL_LIGHT_LUX.mChannelSource = ChannelDetails.CHANNEL_SOURCE.API;
186+
CHANNEL_LIGHT_CCT.mChannelSource = ChannelDetails.CHANNEL_SOURCE.API;
176187
}
177188
//--------- Channel info end --------------
178189

@@ -253,8 +264,17 @@ public double[] computeLuxCct(double red, double green, double blue) {
253264
if (norm != 0) {
254265
double x = X / norm;
255266
double y = Y / norm;
256-
double n = (x - 0.3320) / (0.1858 - y);
257-
cct = 449*n*n*n + 3525*n*n + 6823.3*n + 5520.33;
267+
double denominator = 0.1858 - y;
268+
if (denominator != 0) {
269+
double n = (x - 0.3320) / denominator;
270+
cct = 449*n*n*n + 3525*n*n + 6823.3*n + 5520.33;
271+
}
272+
// McCamy's polynomial is only meaningful for positive colour
273+
// temperatures; clamp the out-of-gamut cases (like lux above) so
274+
// no negative/undefined CCT reaches the CSV.
275+
if (!(cct > 0) || Double.isInfinite(cct)) {
276+
cct = 0;
277+
}
258278
}
259279
return new double[] {lux, cct};
260280
}

ShimmerDriver/src/test/java/com/shimmerresearch/verisense/API_00008_VerisenseLsm6dsvTaggedFifoParsing.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ private static double uncal(ObjectCluster ojc, String channelName) {
9898
return ojc.getFormatClusterValue(channelName, CHANNEL_TYPE.UNCAL.toString());
9999
}
100100

101+
private static double cal(ObjectCluster ojc, String channelName) {
102+
return ojc.getFormatClusterValue(channelName, CHANNEL_TYPE.CAL.toString());
103+
}
104+
101105
/** Accel + gyro interleaved 1:1 with a mag sample and a timestamp entry mixed in. */
102106
@Test
103107
public void test001_accelGyroMagInterleaved() throws Exception {
@@ -134,6 +138,16 @@ public void test001_accelGyroMagInterleaved() throws Exception {
134138
assertEquals(150, uncal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LIS2MDL_MAG_Z), 0.001);
135139
assertTrue(Double.isNaN(uncal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_X)));
136140
assertTrue(Double.isNaN(uncal(magOjc, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_X)));
141+
142+
// CAL assertions - regression-locks the calibration constants against
143+
// LITERAL expected values (the gyro sensitivity was ~12.8% wrong before the
144+
// DEV-793 round-2 review fix; deriving the expectation from the class
145+
// constants would defeat the lock). Defaults: accel +/-4 g = 835.3517
146+
// LSB/(m/s^2); gyro +/-500 dps = 57.142857 LSB/dps (ST 17.50 mdps/LSB).
147+
assertEquals(100 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_X), 0.0001);
148+
assertEquals(-200 / 835.3517, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_ACC_Y), 0.0001);
149+
assertEquals(10 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_X), 0.0001);
150+
assertEquals(-20 / 57.142857143, cal(aligned0, SensorLSM6DSV.ObjectClusterSensorName.LSM6DSV_GYRO_Y), 0.0001);
137151
}
138152

139153
/** Gyro-only: gyro acts as the aligned reference stream. */

0 commit comments

Comments
 (0)