Transforms & scaling
A raw register rarely is the engineering value. A transform turns the decoded integer into the number you want, exactly.
Static rational scale and offset
The common case: value = raw × scale + offset, where scale and offset are
exact rationals (numerator / denominator), never lossy floats.
# 2305 → 230.5 V
transform: { scale: { numerator: 1, denominator: 10 } }
# raw × 0.1 − 1 (a signed-range trick some inverters use)
transform:
scale: { numerator: 1, denominator: 10 }
offset: { numerator: -1, denominator: 1 }
Runtimes compute this with exact rational arithmetic (i128, bigint,
fractions.Fraction, …) and surface a double. Encoding a write runs the
inverse exactly.
Register-referenced scale factors (SunSpec)
SunSpec devices store a shared scale factor in a companion register: the
value's real magnitude is raw × 10^SF, where SF is read live from another
point. Use scale_ref:
# ac_power = raw × 10^(value of w_sf)
transform:
scale_ref: { point_id: w_sf, mode: POW10 }
mode: MULTIPLY covers the raw × ref / denominator variant. The runtime reads
the referenced point automatically before decoding.
Selector cases
Some registers change meaning based on another register. For example, an energy
counter's unit (Wh or kWh) can depend on a format flag. selector_ref picks a
per-case scale and offset, falling back to the point's own transform when no
case matches:
selector_ref:
point_id: energy_format
cases:
0: { scale: { numerator: 1, denominator: 1000 }, unit: kWh }
1: { scale: { numerator: 1, denominator: 1 }, unit: kWh }
Composed values
COMPOSED storage reconstructs mantissa × base^exponent from two sub-windows
of the same register span. A few meters use this form for wide-dynamic-range
readings. It is read-only; the mantissa and exponent offsets are part of the
mapping.
All of these are defined once in the document and applied identically by every runtime, and the conformance suite pins them to the same numeric results.