From 2bd1de9d860211cb1c970ae57736bc7c14352207 Mon Sep 17 00:00:00 2001 From: Mark Nolan Date: Fri, 25 Sep 2026 10:19:08 +0100 Subject: [PATCH] DEV-1067: decode an open circuit on SR68 GSR range 3 as open GSR_UNCAL_LIMIT_RANGE3_SR68 raises range-3 codes below it to it before calibration so that an open circuit reads as open. It was 1134. That is below the 0.4986 V this API divides by for the SR68, which is code 1134.3 at the 1.8 V full scale. So 1134, and every code clamped up to it, decoded to a negative resistance. In auto-range NudgeGSRResistance floored that at 8 kOhm, so an open circuit read 125 uS, and the LIMIT_FOR_MINIMUM_VALID_GSR_CONDUCTANCE_US check reported it as Connected. On fixed range 3 it was pinned to 680 kOhm. An SR68-9 with its electrodes open (DEV-793 dataset B6) reads range-3 codes peaking at 1126-1136, so most of such a recording hits this. The limit is now 1138, the first code above 0.5 V, which clears 0.4986 V as well. The Java driver divides by 0.5 V for the same front end, so one value is right under both. The Java driver and the web SDK get the same change under DEV-1067. SensorGSROpenCircuitTest decodes codes 1134-1138 on range 3 in auto-range (also asserting Disconnected) and on fixed range 3, and pins the limit to the first code above 0.5 V. At 1134 all three fail (8.0 and 680.0 kOhm). At 1138 all three pass, as does the rest of ShimmerBLETests (121). Built with Visual Studio's MSBuild. Under the dotnet CLI, PropertyChanged.Fody 2.6.0 crashes in ShimmerBLEAPI, and Realm's weaver needs SolutionDir. Not changed here: an SR61-5/6 reports HardwareIdentifier VERISENSE_IMU, so it gets neither the gen-2 resistors nor this clamp. That is the gap that DEV-874 closed in the web SDK and DEV-793 closed in the Java driver. Co-Authored-By: Claude Opus 5.5 --- ShimmerBLE/ShimmerBLEAPI/Sensors/SensorGSR.cs | 11 ++- .../Sensors/SensorGSROpenCircuitTest.cs | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 ShimmerBLE/ShimmerBLETests/Sensors/SensorGSROpenCircuitTest.cs diff --git a/ShimmerBLE/ShimmerBLEAPI/Sensors/SensorGSR.cs b/ShimmerBLE/ShimmerBLEAPI/Sensors/SensorGSR.cs index 0bccda9c..860663ab 100644 --- a/ShimmerBLE/ShimmerBLEAPI/Sensors/SensorGSR.cs +++ b/ShimmerBLE/ShimmerBLEAPI/Sensors/SensorGSR.cs @@ -22,7 +22,16 @@ public class SensorGSR : Sensor protected SensorSetting GSROversamplingRateSetting = Sensor.UnknownSetting; public const double LIMIT_FOR_MINIMUM_VALID_GSR_CONDUCTANCE_US = 0.03; public const int GSR_UNCAL_LIMIT_RANGE3_SR62 = 683; - public const int GSR_UNCAL_LIMIT_RANGE3_SR68 = 1134; + /// + /// Range-3 codes below this are raised to it before calibration so that an open circuit + /// reads as open, which only works if the limit is above the amplifier reference. 1138 is + /// the first code above 0.5 V at the SR68's 1.8 V full scale (0.5 V = code 1137.5), so it + /// also clears the 0.4986 V that CalibrateGsrDataToKOhmsUsingAmplifierEq divides by (code + /// 1134.3); the Java driver divides by 0.5 V, and 1138 is correct under both. It was 1134, + /// the last code below 0.4986 V: that decoded to a negative resistance, nudged to 8 kOhm, + /// so an open circuit read 125 uS and counted as Connected (DEV-1067). + /// + public const int GSR_UNCAL_LIMIT_RANGE3_SR68 = 1138; /// /// GSR range setting diff --git a/ShimmerBLE/ShimmerBLETests/Sensors/SensorGSROpenCircuitTest.cs b/ShimmerBLE/ShimmerBLETests/Sensors/SensorGSROpenCircuitTest.cs new file mode 100644 index 00000000..2a7019e5 --- /dev/null +++ b/ShimmerBLE/ShimmerBLETests/Sensors/SensorGSROpenCircuitTest.cs @@ -0,0 +1,82 @@ +using NUnit.Framework; +using shimmer.Sensors; +using ShimmerAPI; +using static ShimmerBLEAPI.Devices.VerisenseDevice; + +namespace ShimmerBLETests +{ + /// + /// An open circuit on GSR range 3 of a Verisense Pulse+ (SR68) has to decode as open (DEV-1067). + /// With nothing across the electrodes the amplifier output settles on its reference, so the ADC + /// reads a few codes either side of it: an SR68-9 open-circuit recording (DEV-793 dataset B6) + /// peaks at codes 1126-1136. The range-3 limit was 1134, itself below the 0.4986 V reference + /// (code 1134.3), so every clamped sample decoded to a negative resistance, which the auto-range + /// nudge floored at 8 kOhm: an open circuit read 125 uS and counted as Connected. + /// + public class SensorGSROpenCircuitTest + { + const int Range3 = 3; + /// Top of range 3, 4.7 MOhm - an open circuit should read at least this. + const double Range3MaxKOhms = 4700.0; + + [Test] + public void TestOpenCircuitOnRange3ReadsOpenInAutoRange() + { + var sensor = CreateSR68Sensor(SensorGSR.GSRRange.Range_Auto); + for (int code = 1134; code <= 1138; code++) + { + AssertReadsOpen(sensor, code, "auto-range"); + Assert.That(sensor.GetGSRConnectivityLevel(), Is.EqualTo(SensorGSR.GSRConnectivityLevel.Disconnected), "auto-range, code " + code); + } + } + + /// + /// A fixed range pins the reading to that range's limits, so here an open circuit reads + /// 4.7 MOhm (0.213 uS, still above the 0.03 uS connectivity threshold) rather than the + /// 680 kOhm bottom of the range that 1134 produced. + /// + [Test] + public void TestOpenCircuitOnRange3ReadsOpenInFixedRange3() + { + var sensor = CreateSR68Sensor(SensorGSR.GSRRange.Range_3); + for (int code = 1134; code <= 1138; code++) + { + AssertReadsOpen(sensor, code, "fixed range 3"); + } + } + + /// + /// The Java driver divides by 0.5 V where this API uses 0.4986 V, so the limit is the first + /// code above 0.5 V: correct under both references. + /// + [Test] + public void TestLimitIsTheFirstCodeAboveHalfAVolt() + { + int limit = SensorGSR.GSR_UNCAL_LIMIT_RANGE3_SR68; + Assert.That(CalibrateADCValueToVolts(limit, HardwareIdentifier.VERISENSE_PULSE_PLUS), Is.GreaterThan(0.5)); + Assert.That(CalibrateADCValueToVolts(limit - 1, HardwareIdentifier.VERISENSE_PULSE_PLUS), Is.LessThan(0.5)); + } + + private static SensorGSR CreateSR68Sensor(Sensor.SensorSetting range) + { + var sensor = new SensorGSR(); + sensor.SetDeviceHardwareIdentifier(HardwareIdentifier.VERISENSE_PULSE_PLUS); + sensor.SetGSREnabled(true); + sensor.SetGSRRange(range); + return sensor; + } + + private static void AssertReadsOpen(SensorGSR sensor, int code, string rangeLabel) + { + // One GSR sample as the firmware sends it: range in bits 15-14 over the 12-bit code, LSB first. + int raw = (Range3 << 14) | code; + var ojc = sensor.ParseSensorData(new byte[] { (byte)(raw & 0xFF), (byte)(raw >> 8) }, new ObjectCluster("", "")); + double kOhms = ojc.GetData(SensorGSR.ObjectClusterSensorName.GSR, ShimmerConfiguration.SignalFormats.CAL, ShimmerConfiguration.SignalUnits.KiloOhms).Data; + double uS = ojc.GetData(SensorGSR.ObjectClusterSensorName.GSR, ShimmerConfiguration.SignalFormats.CAL, ShimmerConfiguration.SignalUnits.MicroSiemens).Data; + + string context = rangeLabel + ", code " + code; + Assert.That(kOhms, Is.GreaterThanOrEqualTo(Range3MaxKOhms), context); + Assert.That(uS, Is.GreaterThan(0).And.LessThanOrEqualTo(1000.0 / Range3MaxKOhms), context); + } + } +}