Skip to main content

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-mappings of the same register span. A few meters use this form for wide-dynamic-range readings. It is read-only; sub-mapping offsets are word offsets relative to the point's mapped window.

Since v0.5 a sub-mapping can also carry a bit window (bit_offset/bit_length), for meters that embed the decade exponent in the high bits of the same word as the mantissa — Iskra's T5/T6 measurement types (signed 8-bit exponent in bits 31–24, 24-bit mantissa in bits 23–0) and the Eaton PXM "GENERAL FORMAT" (8-bit exponent + 56-bit mantissa). The vendor example FD01 E240 decodes as 123456 × 10⁻³ = 123.456:

storage_type: COMPOSED
mapping:
offset: 140
length_words: 2
composed:
kind: MANTISSA_EXPONENT
base: 10
mantissa: { offset: 0, length_words: 2, storage_type: S32, bit_offset: 0, bit_length: 24 }
exponent: { offset: 0, length_words: 2, storage_type: S16, bit_offset: 24, bit_length: 8 }

The sub-mapping's storage_type supplies signedness; a bit-windowed value sign-extends from bit_length. Composed points may also overlap each other — their parent mappings are read windows, which is how interleaved mantissa/exponent register pools (the Iskra non-reset energy totals) are modeled.

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.