3Sense conversions: K-type thermocouple, Pt100/Pt1000 RTD, load cell.
4Ported from diablo_server/frontend/lib/sense-conversions.ts (DiabloAvionics sense_testing_gui.py).
6Used by calibration_server.py as fallback when RobustCalibrationFramework has no calibration
7points, or for quick raw→physical conversion. Single source of truth for calibration service.
10from __future__
import annotations
14from pathlib
import Path
15from typing
import Optional
19K_TYPE_INVERSE: list[tuple[float, ...]] = [
92RTD_EXCITATION_UA = 1000.0
94ADC32_FULL_SCALE = 2147483648.0
98 """Interpret uint32 as int32 (for 32-bit signed ADC codes)."""
100 return u - 0x100000000
if u > 0x7FFFFFFF
else u
108 Convert K-type thermocouple voltage (V) to temperature (°C).
109 Returns None if out of range.
111 v_mv = v_volts * 1000.0
112 for row
in K_TYPE_INVERSE:
113 v_lo, v_hi, t0, v0, p1, p2, p3, p4, q1, q2, q3 = row
114 if v_lo <= v_mv <= v_hi:
116 num = p1 + x * (p2 + x * (p3 + x * p4))
117 den = 1.0 + x * (q1 + x * (q2 + x * q3))
120 return t0 + (x * num) / den
126 adc_ref_voltage: float = 2.5,
127 adc_full_scale: float = ADC32_FULL_SCALE,
130 Convert K-type TC raw ADC code to temperature (°C).
131 Assumes 32-bit signed ADC: voltage = (adc / adc_full_scale) * adc_ref_voltage.
134 v_volts = (code / adc_full_scale) * adc_ref_voltage
143 Convert RTD resistance (Ω) to temperature (°C). IEC 60751.
144 Supports Pt100 (r0=100) and Pt1000 (r0=1000) via r0 parameter.
145 Returns None if out of range.
148 d = PT_A * PT_A - 4 * PT_B * (1.0 - rr)
151 sqrt_d = math.sqrt(d)
152 t = (-PT_A + sqrt_d) / (2 * PT_B)
153 if -400 <= t <= 1100:
160 r0: float = PT1000_R0,
161 excitation_ua: float = RTD_EXCITATION_UA,
164 Convert RTD differential voltage (V) to temperature (°C).
165 excitation_ua = IDAC current in µA (default 1000).
167 if excitation_ua <= 0:
169 r_ohm = (abs(v_volts) * 1e6) / excitation_ua
175 r0: float = PT100_R0,
176 scale_to_ohms: float = 0.001,
179 Convert raw resistance counts (often 24-bit or 32-bit milliohms) to temperature (°C).
180 Default assumes value is milliohms (scale_to_ohms=0.001).
182 if raw_value
is None or not math.isfinite(raw_value):
184 r_ohm = raw_value * scale_to_ohms
190 r0: float = PT1000_R0,
191 excitation_ua: float = RTD_EXCITATION_UA,
192 adc_ref_voltage: float = 2.5,
193 adc_full_scale: float = ADC32_FULL_SCALE,
196 Convert RTD raw ADC code to temperature (°C).
197 Assumes ADC measures differential voltage across RTD with known excitation current.
200 v_volts = (code / adc_full_scale) * adc_ref_voltage
209 sensitivity_mv_per_v: float,
211 full_scale_value: float,
214 Ratiometric load-cell force from raw 32-bit ADC code.
215 Reference = excitation, so voltage cancels.
216 full_scale_value is in desired units (e.g. lbf, kg).
217 Returns None if invalid params.
219 if pga_gain <= 0
or sensitivity_mv_per_v <= 0:
222 code_fs = (sensitivity_mv_per_v / 1000.0) * pga_gain * ADC32_FULL_SCALE
225 return (code_int32 / code_fs) * full_scale_value
233 Load calibration polynomial coefficients [A, B, C, D] for a PT channel from the
234 latest JSON file in tools/calibration/calibrations/.
235 Polynomial: psi = A*adc^3 + B*adc^2 + C*adc + D
237 cal_dir = Path(__file__).parent /
"calibrations"
242 for f
in cal_dir.iterdir()
243 if f.suffix ==
".json" and "learned_prior" not in f.name
249 data = json.loads(fp.read_text())
250 polys = data.get(
"calibration_polynomials", {})
251 key = str(channel_id)
252 if key
in polys
and len(polys[key]) >= 4:
253 return list(polys[key][:4])
263 full_scale_psi: float,
264 sense_resistor_ohms: float,
265 adc_ref_voltage: float = 2.5,
268 4-20 mA HP PT: psi = (i_ma - 4) / 16 * full_scale_psi.
269 v_sense = (adc/2^31)*adc_ref_voltage, i_ma = v_sense/sense_resistor_ohms * 1000.
270 Wire codes are unsigned 0..2^31-1 (match FSW calibration_main convert_hp_pt_to_pressure).
272 ADC_MAX = 2147483648.0
273 I_MIN_MA, I_SPAN_MA = 4.0, 16.0
274 adc_u = adc_code & 0xFFFFFFFF
275 if adc_u >= int(ADC_MAX):
277 if sense_resistor_ohms <= 0.0
or not math.isfinite(adc_ref_voltage):
279 v_sense = (float(adc_u) / ADC_MAX) * adc_ref_voltage
280 i_ma = (v_sense / sense_resistor_ohms) * 1000.0
281 if not math.isfinite(v_sense)
or not math.isfinite(i_ma):
288 psi = ((i_ma - I_MIN_MA) / I_SPAN_MA) * full_scale_psi
289 if not math.isfinite(psi):
291 return max(0.0,
min(psi, full_scale_psi * 1.05))
296 Convert raw ADC code to PSI using the stored cubic calibration polynomial.
297 Returns None if no calibration is available for the channel.
298 Polynomial: psi = A*adc^3 + B*adc^2 + C*adc + D (coeffs[0..3] = A, B, C, D)
305 return A * x * x * x + B * x * x + C * x + D
312 channel_id: Optional[int] =
None,
314 rtd_r0: float = PT1000_R0,
315 rtd_adc_ref_v: float = 2.5,
316 rtd_excitation_ua: float = RTD_EXCITATION_UA,
318 tc_adc_ref_v: float = 2.5,
320 lc_sensitivity_mv_per_v: float = 2.0,
321 lc_pga_gain: float = 128.0,
322 lc_full_scale_value: float = 100.0,
325 Convert raw value to physical unit by sensor type.
326 Returns PSI for PT (if calibration available), °C for TC/RTD, force (lbf/kg) for LC,
327 or None if conversion fails.
328 RTD raw_value is ADC counts (not milliohms); uses raw_rtd_adc_to_temp_c.
336 adc_ref_voltage=rtd_adc_ref_v,
337 excitation_ua=rtd_excitation_ua,
344 sensitivity_mv_per_v=lc_sensitivity_mv_per_v,
345 pga_gain=lc_pga_gain,
346 full_scale_value=lc_full_scale_value,
Optional[float] code_to_force(int code_uint32, float sensitivity_mv_per_v, float pga_gain, float full_scale_value)
Optional[float] raw_rtd_to_temp_c(float raw_value, float r0=PT100_R0, float scale_to_ohms=0.001)
Optional[float] rtd_voltage_to_temp_c(float v_volts, float r0=PT1000_R0, float excitation_ua=RTD_EXCITATION_UA)
Optional[float] pt_adc_to_psi(int adc_code, int channel_id)
Optional[float] raw_rtd_adc_to_temp_c(int adc_code, float r0=PT1000_R0, float excitation_ua=RTD_EXCITATION_UA, float adc_ref_voltage=2.5, float adc_full_scale=ADC32_FULL_SCALE)
float hp_pt_adc_to_psi(int adc_code, float full_scale_psi, float sense_resistor_ohms, float adc_ref_voltage=2.5)
Optional[float] k_type_voltage_to_temp_c(float v_volts)
int _uint32_to_int32(int u)
Optional[float] k_type_adc_to_temp_c(int adc_code, float adc_ref_voltage=2.5, float adc_full_scale=ADC32_FULL_SCALE)
Optional[list[float]] _load_pt_polynomial(int channel_id)
Optional[float] raw_to_physical(str stype, int raw_value, *, Optional[int] channel_id=None, float rtd_r0=PT1000_R0, float rtd_adc_ref_v=2.5, float rtd_excitation_ua=RTD_EXCITATION_UA, float tc_adc_ref_v=2.5, float lc_sensitivity_mv_per_v=2.0, float lc_pga_gain=128.0, float lc_full_scale_value=100.0)
Optional[float] rtd_resistance_to_temp_c(float r_ohm, float r0=PT1000_R0)