From e8dd95c625d330535e27b240c7c00e5bd25bd126 Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Fri, 25 Sep 2026 10:18:55 +0100 Subject: [PATCH] DEV-1067: decode an open circuit on gen-2 GSR range 3 as open Range-3 codes below VERISENSE_PULSE_PLUS_GSR_UNCAL_LIMIT_RANGE3 are raised to it before calibration so that an open circuit reads as open. The limit was 1134. At the second-generation front end's 1.8 V full scale that is below the 0.5 V that calibrateGsrDataToKOhmsUsingAmplifierEq divides by, which is code 1137.5. So codes 1134 to 1137, and every code clamped up to 1134, decoded to a negative resistance. nudgeGsrResistance then floored it at 8 kOhm in auto-range, reading the open circuit as 125 uS, the highest conductance the device can report. On fixed range 3 it pinned it to 680 kOhm, the bottom of the range. It is not a corner case. With the electrodes open the amplifier output sits on its reference, and the ADC reads either side of it. DEV-793's B6 recording (SR68-9, auto-range, open-circuit gaps; ASM_PC Test_056) has 4,629 range-3 samples between codes 1080 and 1250, peaking at 1126-1136, and 3,205 of them are at or below 1137. The committed Test_056 reference decodes every one of those as 125.000 uS. The limit is now 1138, the first code above 0.5 V, as 683 is at the Shimmer3's 3.0 V. 1134 came in with ASM-1736 in 2021, replacing a "TODO update value" 683, with no derivation recorded. It is the last code below 0.4986 V, the reference the C# API and web SDK use for this front end, and it is wrong there too. API_00015 decodes codes 1134-1138 on range 3 through VerisenseDevice and processDataCustom, for an SR68-9 and an SR61-5, in auto-range and on fixed range 3. It also pins the limit to the first code above the reference. At 1134 all five tests fail (8.0 and 680.0 kOhm). At 1138 all five pass, as does the rest of ShimmerDriver (108 tests). It is numbered API_00015 because DEV-1047's branch already uses 00012-00014. Downstream: ASM_PC has to bump this submodule and regenerate Test_056's reference, whose open-circuit samples change from 125.000 uS to ~0 uS. Co-Authored-By: Claude Opus 5.5 --- .../verisense/sensors/SensorGSRVerisense.java | 10 +- ...PI_00015_VerisenseGsrOpenCircuitLimit.java | 119 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 ShimmerDriver/src/test/java/com/shimmerresearch/verisense/sensors/API_00015_VerisenseGsrOpenCircuitLimit.java diff --git a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorGSRVerisense.java b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorGSRVerisense.java index cda033743..0d4ba1c66 100644 --- a/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorGSRVerisense.java +++ b/ShimmerDriver/src/main/java/com/shimmerresearch/verisense/sensors/SensorGSRVerisense.java @@ -86,7 +86,15 @@ public static GSR_RANGE getForConfigValue(int configValue) { 150.0, //Range 1 562.0, //Range 2 1740.0}; //Range 3 - public static final int VERISENSE_PULSE_PLUS_GSR_UNCAL_LIMIT_RANGE3 = 1134; + /** + * Range-3 codes below this are raised to it before calibration so that an open circuit reads + * as open. It has to be the first code above the 0.5 V amplifier reference that + * {@link SensorGSR#calibrateGsrDataToKOhmsUsingAmplifierEq} divides by: 0.5 V is code 1137.5 at + * this front end's 1.8 V full scale, so 1138, as 683 is at the Shimmer3's 3.0 V. Anything lower + * decodes to a negative resistance, which the auto-range nudge floors at 8 kOhm, reading an open + * circuit as 125 uS. It was 1134, the last code below 0.4986 V, until DEV-1067. + */ + public static final int VERISENSE_PULSE_PLUS_GSR_UNCAL_LIMIT_RANGE3 = 1138; //--------- Sensor info start -------------- public static final SensorDetailsRef SENSOR_GSR_VERISENSE = new SensorDetailsRef( diff --git a/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/sensors/API_00015_VerisenseGsrOpenCircuitLimit.java b/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/sensors/API_00015_VerisenseGsrOpenCircuitLimit.java new file mode 100644 index 000000000..67e5f1b28 --- /dev/null +++ b/ShimmerDriver/src/test/java/com/shimmerresearch/verisense/sensors/API_00015_VerisenseGsrOpenCircuitLimit.java @@ -0,0 +1,119 @@ +package com.shimmerresearch.verisense.sensors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.shimmerresearch.driver.Configuration.COMMUNICATION_TYPE; +import com.shimmerresearch.driver.Configuration.Verisense; +import com.shimmerresearch.driver.ObjectCluster; +import com.shimmerresearch.driverUtilities.ChannelDetails.CHANNEL_TYPE; +import com.shimmerresearch.driverUtilities.ExpansionBoardDetails; +import com.shimmerresearch.driverUtilities.SensorDetails; +import com.shimmerresearch.driverUtilities.ShimmerVerDetails.HW_ID; +import com.shimmerresearch.sensors.SensorADC.MICROCONTROLLER_ADC_PROPERTIES; +import com.shimmerresearch.sensors.SensorGSR; +import com.shimmerresearch.sensors.SensorGSR.ObjectClusterSensorName; +import com.shimmerresearch.verisense.VerisenseDevice; +import com.shimmerresearch.verisense.sensors.SensorGSRVerisense.GSR_RANGE; + +/** + * An open circuit on GSR range 3 of the second-generation front end (Pulse+ SR68, and the + * SR61-5/6 IMU) has to decode as open (DEV-1067). + *

+ * With nothing across the electrodes the amplifier's gain falls to one and its output settles on + * the reference, so the ADC reads a few codes either side of it. The decode raises every range-3 + * code below {@link SensorGSRVerisense#VERISENSE_PULSE_PLUS_GSR_UNCAL_LIMIT_RANGE3} to that limit, + * which only works if the limit is itself above the reference. It was 1134, below the 0.5 V this + * decode divides by (code 1137.5 at 1.8 V), so codes 1134 to 1137 decoded to a negative + * resistance: auto-range floored it at 8 kOhm, the highest conductance the device can report, and + * fixed range 3 pinned it to 680 kOhm, the bottom of the range. On an SR68-9 with the electrodes + * open (DEV-793 dataset B6, ASM_PC Test_056) the range-3 codes peak at 1126-1136, so most of an + * open-circuit recording read 125 uS. + *

+ * Each case goes through the real decode: {@link VerisenseDevice} builds the sensor class for the + * board and {@link SensorGSR#processDataCustom} parses the sample, so the wiring from hardware ID + * to limit is covered as well as the arithmetic. + */ +public class API_00015_VerisenseGsrOpenCircuitLimit { + + private static final int RANGE_3 = 3; + /** Top of range 3, 4.7 MOhm - an open circuit should read at least this. */ + private static final double RANGE_3_MAX_KOHMS = SensorGSR.SHIMMER3_GSR_RESISTANCE_MIN_MAX_KOHMS[RANGE_3][1]; + /** Codes around the gen-2 amplifier reference, where an open circuit sits. */ + private static final int FIRST_CODE = 1134, LAST_CODE = 1138; + + @Test + public void test001_sr68AutoRangeOpenCircuitReadsOpen() { + assertOpenCircuitReadsOpen(HW_ID.VERISENSE_PULSE_PLUS, 9, GSR_RANGE.AUTO_RANGE); + } + + @Test + public void test002_sr68FixedRange3OpenCircuitReadsOpen() { + assertOpenCircuitReadsOpen(HW_ID.VERISENSE_PULSE_PLUS, 9, GSR_RANGE.RANGE_3); + } + + @Test + public void test003_sr61AutoRangeOpenCircuitReadsOpen() { + assertOpenCircuitReadsOpen(HW_ID.VERISENSE_IMU, 5, GSR_RANGE.AUTO_RANGE); + } + + @Test + public void test004_sr61FixedRange3OpenCircuitReadsOpen() { + assertOpenCircuitReadsOpen(HW_ID.VERISENSE_IMU, 5, GSR_RANGE.RANGE_3); + } + + /** + * The limit is exactly the first code above the reference: one lower and the clamp produces + * negative resistances again, any higher and it overwrites codes that decode properly. + */ + @Test + public void test005_limitIsTheFirstCodeAboveTheAmplifierReference() { + int limit = SensorGSRVerisense.VERISENSE_PULSE_PLUS_GSR_UNCAL_LIMIT_RANGE3; + assertTrue(calibrateRange3(limit) > 0); + assertTrue(calibrateRange3(limit - 1) < 0); + } + + private static void assertOpenCircuitReadsOpen(int hwId, int hwRev, GSR_RANGE gsrRange) { + SensorGSRVerisense sensorGsr = createSensorGsr(hwId, hwRev); + sensorGsr.setGsrRange(gsrRange); + + for (int code = FIRST_CODE; code <= LAST_CODE; code++) { + ObjectCluster ojc = decodeRange3Sample(sensorGsr, code); + double kOhms = ojc.getFormatClusterValue(ObjectClusterSensorName.GSR_RESISTANCE, CHANNEL_TYPE.CAL.toString()); + double uS = ojc.getFormatClusterValue(ObjectClusterSensorName.GSR_CONDUCTANCE, CHANNEL_TYPE.CAL.toString()); + + String context = "SR" + hwId + "-" + hwRev + " " + gsrRange + " code " + code; + assertTrue(context + " read " + kOhms + " kOhm", kOhms >= RANGE_3_MAX_KOHMS); + assertTrue(context + " read " + uS + " uS", uS > 0 && uS <= 1000.0 / RANGE_3_MAX_KOHMS); + } + } + + private static SensorGSRVerisense createSensorGsr(int hwId, int hwRev) { + ExpansionBoardDetails ebd = new ExpansionBoardDetails(hwId, hwRev, 0); + VerisenseDevice verisenseDevice = new VerisenseDevice(); + verisenseDevice.setShimmerVersionObject(VerisenseDevice.FW_CHANGES.CCF21_010_3); + verisenseDevice.setHardwareVersion(ebd.getExpansionBoardId()); + verisenseDevice.setExpansionBoardDetails(ebd); + verisenseDevice.sensorAndConfigMapsCreate(); + + SensorGSRVerisense sensorGsr = verisenseDevice.getSensorGsr(); + assertNotNull("no GSR sensor registered for SR" + hwId + "-" + hwRev, sensorGsr); + return sensorGsr; + } + + /** One GSR sample as the firmware sends it: range in bits 15-14 over the 12-bit code, LSB first. */ + private static ObjectCluster decodeRange3Sample(SensorGSRVerisense sensorGsr, int code) { + int raw = (RANGE_3 << 14) | code; + byte[] sample = new byte[] {(byte) (raw & 0xFF), (byte) ((raw >> 8) & 0xFF)}; + SensorDetails sensorDetails = sensorGsr.getSensorDetails(Verisense.SENSOR_ID.GSR); + return sensorGsr.processDataCustom(sensorDetails, sample, COMMUNICATION_TYPE.SD, new ObjectCluster(), false, 0); + } + + private static double calibrateRange3(int code) { + return SensorGSR.calibrateGsrDataToKOhmsUsingAmplifierEq(code, RANGE_3, + MICROCONTROLLER_ADC_PROPERTIES.VERISENSE_1V8, + SensorGSRVerisense.VERISENSE_PULSE_PLUS_GSR_REF_RESISTORS_KOHMS); + } +}