Java port of calibration algorithm for Android CGM apps#5
Merged
Conversation
Port all 7 C structs to Java model classes (CgmInput, AlgorithmOutput, DeviceInfo, AlgorithmState, DebugOutput, CalibrationLog, CalibrationList) and the 90x45 precomputed LOESS kernel coefficient table.
All 28 math utility functions ported from math_utils.c with NaN-aware behavior matching C implementation exactly. 74 JUnit 5 tests covering edge cases, NaN handling, and regression verification.
All 13 signal processing functions ported including the complex LOESS pipeline (Hampel -> IRLS -> running median -> FIR -> trimmed mean), Savitzky-Golay smoothing, regression calibration, and error helpers.
CheckError: all 7 error detectors (err1/2/4/8/16/32/128) ported with 51 TDD tests. Decomposed into well-named static methods. OracleBinaryReader: reads packed binary oracle files (output_t, debug_t, cgm_input_t) for field-by-field verification. 23 tests validating against actual oracle data from 5 sensor lots. Total: 194 tests passing.
Main calibration pipeline ported from calibration.c including all static helpers: ADC conversion, IIR filter, drift correction, temperature compensation, Holt-Kalman bias correction, trendrate. All constants match C at full precision. 244 total tests passing.
Tests Java calibration against 2000 oracle readings (5 lots x 400). Results: lots 0/3/4 glucose match 100%, lots 1/2 need fixes. SG smoothing initialization and error edge cases identified.
- Lot1 (eapp=0.15): add eapp>=0.12 rejection with errcode=64 - Lot2 (eapp=0.05): use lot1 temp formula for all lot types, enable drift correction for all lot types (matches oracle) - SG smoothing: fix output buffer positions [3..8] not [0..5], add zero-guard for unfilled convolution windows All 2000 oracle readings now produce bit-identical glucose values. 250 tests passing.
INTEGRATION.md: public API design (CareSensCalibrator facade), packaging as pure Java JAR via JitPack, xDrip+ integration points, generic design for use by any Android CGM app. SG smoothing: ARM binary analysis reveals the C reimplementation uses a different convolution formula than the proprietary binary. This is a known limitation in the C source, not a Java port issue. Primary result_glucose values are 100% correct across all 2000 oracle readings.
Clean public API for any Android CGM app: - SensorConfig: immutable sensor params with Builder pattern - CalibrationResult: immutable result with glucose, trend, errors - CareSensCalibrator: initialize, processReading, save/restore state No Android dependencies. 29 new tests including oracle verification through the facade. 279 total tests passing.
Quick start, API reference, build instructions, Android integration guide, and architecture overview for the CareSensCalibrator facade.
- Internal classes (MathUtils, SignalProcessing, CheckError, etc.) made package-private; only public API facade classes remain public - Replace 7 manual array shift loops with System.arraycopy - Replace fully-qualified java.util.Arrays with import - Simplify checkBoundary conditional return - Remove unused import All 279 tests still passing. No behavioral changes.
OracleVerificationTest: assert all safety-critical fields (errcode, cal_available_flag, current_stage, trendrate) in addition to glucose. Add detailed per-field mismatch reporting. build.sh: robust standalone build script with JDK auto-detection, JUnit auto-download, and compile/test/all subcommands. build.gradle: target Java 8 for maximum Android compatibility.
- err1: compute i_sse_d_mean before epoch reset, write th_diff at n=1 - err4: fix min_diff to track signed breakthrough vs running minimum - err2: fix crt_current[1] lagged glucose >= threshold Debug match: 263987/264000 (99.995%). All patient-facing outputs (glucose, errcode, trendrate, stage) match 100% across all 2000 oracle readings.
- Trendrate: add zero-denominator guards preventing NaN/Infinity - SensorConfig: deep-copy DeviceInfo in Builder.build() for immutability - CalibrationResult: guard isTrendAvailable() against NaN/Infinity - SensorConfig: validate vref and slope100 independently - CalibrationAlgorithm: reject slope100 <= 0 to prevent division by zero - CareSensCalibrator: add version header to serialized state format 10 new tests, 290 total passing.
Parses 84-byte BLE C5 characteristic notifications into components ready for CareSensCalibrator.processReading(). Includes temperature conversion, uint16 ADC extraction, and immutable ParsedReading result. 10 new tests, 300 total passing.
Full flow: BLE bytes -> BlePacketParser -> CareSensCalibrator -> glucose. Tests multi-reading sequences, state persistence, error handling, and extreme values. 10 new tests, 310 total passing.
- Guard slopeRatioTemp near-zero division (extreme temperature) - Add 50-reading Kalman convergence test - Add oracle data existence check (prevents silent CI skip) - Test mathRound overflow, NaN in statistics, negative ADC values, trendrate glucose boundary conditions (40.0, 500.0) 323 tests passing.
…cation README: reflect both C and Java implementations, updated oracle results table, Java quick start example, updated project structure. CI: GitHub Actions workflow with 3 jobs: - Java unit tests (323 tests) - Oracle verification (downloads oracle data from release asset) - C tests (cmake + ctest)
- Root settings.gradle includes java/ as :opencaresens-air module - jitpack.yml configures JDK 11 for JitPack builds - README: downstream integrations section with JitPack usage and notification list for major updates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete Java port of the CareSens Air CGM calibration algorithm, designed as a standalone library for integration into Android CGM apps such as xDrip+, Jugluco, and AndroidAPS.
This port was prompted by a request from Jon Ande (jamorham), the lead developer of xDrip+, who asked about converting the C implementation to Java for native integration without binary library dependencies.
What this adds
Java calibration library (
java/)A pure Java implementation of the full 14-step calibration pipeline that converts raw ADC sensor readings into calibrated glucose values (mg/dL), matching the behavior of the proprietary i-SENS
libCALCULATION.solibrary.Source files (17):
CareSensCalibrator.javaSensorConfig.javaCalibrationResult.javaBlePacketParser.javaCalibrationAlgorithm.javaSignalProcessing.javaCheckError.javaMathUtils.javaLoessKernel.javaOracleBinaryReader.javamodel/*.java(7 files)Test files (9), 323 tests total:
MathUtilsTest.javaSignalProcessingTest.javaCheckErrorTest.javaCalibrationAlgorithmTest.javaCareSensCalibratorTest.javaBlePacketParserTest.javaOracleBinaryReaderTest.javaOracleVerificationTest.javaEndToEndIntegrationTest.javaC implementation improvements (pre-existing branch work)
The branch also includes C-side improvements that were developed during the oracle verification phase:
Oracle verification results
The Java implementation was verified against 2000 oracle readings generated by the proprietary ARM binary across 5 sensor lots:
All safety-critical outputs match 100%. Debug intermediate fields match 99.995% (263,987 / 264,000). The 13 remaining mismatches are in a single internal diagnostic field (
err2_crt_current[1]) that does not affect any patient-facing output.Known limitation: SG smooth_result_glucose
The Savitzky-Golay smoothed glucose output (
smooth_result_glucose[0..5]) has ~391 mismatches per lot starting at seq 10. Analysis of the ARM binary disassembly revealed that the proprietary implementation uses a causal convolution with a data-dependent kernel that differs from the C reimplementation's weighted average approach. This is a limitation of the C reimplementation itself, faithfully reproduced in the Java port. This does not affect the primaryresult_glucosevalue, which matches 100%.Public API usage
Medical safety considerations
This library produces glucose values that people with diabetes use to dose insulin. Incorrect values can cause dangerous hypoglycemia or hyperglycemia. The following safety measures are in place:
isTrendAvailable()rejects NaN and Infinity; extreme temperature guard prevents near-zeroslopeRatioTempSensorConfig.Buildervalidates required fields;processReading()validates ADC array lengthCalibrationResultandSensorConfigare immutable with defensive array copiesDesign decisions
CareSensCalibrator,SensorConfig,CalibrationResult, andBlePacketParserare public. All algorithm internals are hidden.build.shauto-detects JDK and downloads JUnit. Also includesbuild.gradlefor Gradle-based Android projects.Integration documentation
java/README.md— Quick start, API reference, build instructions, Android integration guidejava/INTEGRATION.md— xDrip+ integration analysis, specific file locations, data flow, generic design principlesTest plan