/  PyMDB  /  Calibrators

Calibrators

Sensors often transmit counts rather than engineering units directly. A calibrator converts the raw value extracted from the packet into the engineering value users see. Calibrators attach to integer and float parameters (and to the corresponding members and command arguments) through the calibrator option — many parameters need no calibrator at all, for instance when the raw count already is the engineering value.

Two calibrator types are available.

Polynomial calibration

Polynomial describes the transfer curve as polynomial coefficients, ordered from x0 upward — Polynomial([c0, c1, c2]) means eng = c0 + c1·raw + c2·raw²:

voltage = Y.FloatParameter(
    system=eps,
    name="bus_voltage",
    units="V",
    encoding=Y.uint12_t,
    calibrator=Y.Polynomial([0.0, 0.00805]),   # eng = 0.00805 * raw
)

A linear scale-and-offset is simply a two-coefficient polynomial.

Piecewise interpolation

Interpolate describes the curve as a set of points; values in between are interpolated linearly. xp holds the raw coordinates (increasing), fp the corresponding engineering values:

temperature = Y.FloatParameter(
    system=eps,
    name="battery_temp",
    units="degC",
    encoding=Y.uint8_t,
    calibrator=Y.Interpolate(
        xp=[0, 64, 128, 192, 255],
        fp=[-50.0, -10.0, 20.0, 45.0, 60.0],
    ),
)

This suits thermistors and other sensors with non-linear response, where the calibration comes from a lookup table.

Computing calibrators

Since the model is a Python program, calibration data does not have to be transcribed by hand. At its simplest, writing coefficients as arithmetic keeps the derivation visible in the source:

# 10-bit ADC, 3.3 V reference, 20 mV/unit sensor
calibrator=Y.Polynomial([0, 3.3 / 1023 * 0.02])

And when the transfer function of the sensor circuit is known, the calibrator can be derived at build time — sample the function over the raw range and either feed the points straight into Interpolate, or fit a polynomial with a library such as NumPy:

import numpy

def pt1000_temperature(raw):
    """Transfer function of the PT1000 readout circuit."""
    v_out = raw * 3 / 1023
    r = (2310 + 462 * v_out) / (3 - (0.231 + 0.0462 * v_out))
    return (r / 1000 - 1) / 3.85e-3

xp = list(range(0, 1023, 10))
fp = [pt1000_temperature(x) for x in xp]

coef = numpy.polyfit(xp, fp, 2).tolist()
coef.reverse()   # polyfit returns highest degree first; Polynomial wants x^0 first
calibrator = Y.Polynomial(coef)

Note the reverse(): NumPy’s polyfit orders coefficients from the highest power down, while Polynomial expects them from x0 upward. Libraries used this way are build-time tools only — PyMDB itself has no such dependencies, and nothing of them remains in the exported XTCE.

Note

In the other direction, calibrators also apply to command arguments: the operator enters the engineering value and Yamcs de-calibrates it to the raw count before encoding. Alarm thresholds and validity ranges are always expressed on the calibrated (engineering) side.