Skip to Content

Product tiers — BASIC / STANDARD / PRO

Shared content note: tier semantics are identical across every platform (Arduino / ESP-IDF / MicroPython / CPython / STM32-HAL). The definitive reference is _shared/tier_comparison.md (created in a future Phase 4 session). This page documents the tiers from the Arduino library's perspective — what API surface each tier exposes and how to detect which tier you have.


Tier overview

Tier Voltage HW Current channels Period accumulator Bidirectional energy Notes
BASIC optional (UI1/UI2/UI3 vs I1/I2/I3 SKUs) 1 / 2 / 3 unsigned (clamps negative samples to 0) master-side only — see scenario 5 Cheapest, intended for residential metering
STANDARD always present 1 / 2 / 3 signed (preserves negative samples — solar export visible) native Mid-tier, solar-with-grid use case
PRO always present 3 signed + per-channel reactive power + harmonics native Industrial / three-phase audit

All three tiers expose the same I2C register map at v1.0 of the protocol. The library API surface (readVoltage(), readPower(ch), …) is identical for every tier. Tier differences manifest in:

  1. Which channels exist — discovered via constructor topology hint (v1.0 firmware) or REG_TOPOLOGY (v1.1 firmware, see below).
  2. Whether voltage hardware is presentdev.hasVoltageHw().
  3. Sign of the period-averaged power — BASIC clamps negative; STANDARD and PRO preserve sign. Affects whether you can do native bidirectional accounting (dev.energy().wh(0) going negative on export) or need to split master-side at 5 Hz RT cadence.
  4. Whether the reactive-power register is populated — STANDARD / PRO only; library v1.0 doesn't expose it (RESERVED for v2 — see Changelog).

What the library exposes per tier

cpp
RbAmp dev(Wire, 0x50);
dev.begin();
dev.channels();        // 1, 2, or 3 — set by constructor hint (v1.0) or REG_TOPOLOGY (v1.1+)
dev.hasVoltageHw();    // true on UI* SKUs, false on I*-only SKUs
dev.topology();        // RbAmpTopology::Single / SplitPhase / ThreePhase

The same code runs on any tier. For BASIC modules where you want bidirectional accounting at the master, follow scenario 5 in 06_examples.md — sample dev.readPower(0) at 5 Hz, split into two double-precision buckets.


Detecting tier at runtime

BASIC vs STANDARD/PRO

dev.hasVoltageHw() distinguishes UI (voltage hardware present) from pure-I SKUs. To distinguish BASIC-UI from STANDARD/PRO (both have voltage hardware), watch the sign* of readPower(0) over a known-export load:

cpp
RbAmp dev(Wire, 0x50);
dev.begin();
dev.channels();        // 1, 2, or 3 — set by constructor hint (v1.0) or REG_TOPOLOGY (v1.1+)
dev.hasVoltageHw();    // true on UI* SKUs, false on I*-only SKUs
dev.topology();        // RbAmpTopology::Single / SplitPhase / ThreePhase

For installations where you don't know the tier ahead of time, the safe default is to assume BASIC + master-side split — this works on every tier (signed RT readPower(0) is always signed, only the period accumulator differs).

UI1 / UI2 / UI3 channel count

v1.0 firmware

The library cannot reliably auto-detect channel count on v1.0 firmware — unmapped register reads return 0x00 rather than NACK (SPEC §8). The constructor's hint is authoritative:

cpp
RbAmp dev(Wire, 0x50);                          // defaults to ThreePhase — over-detect harmless
RbAmp dev(Wire, 0x50, RbAmpTopology::Single);   // explicit UI1
RbAmp dev(Wire, 0x50, RbAmpTopology::SplitPhase); // explicit UI2

Unused channels on a UI1 module accessed via dev.readCurrent(2) (with a THREE_PHASE hint) read 0.0 A and contribute 0 Wh to integration. Over-detection is therefore harmless — it just means your dashboards show "3 channels" instead of "1".

v1.1 firmware

REG_TOPOLOGY (0x24, R-only byte) reports the compile-time channel count. Read it via dev.rawTopology():

cpp
const uint8_t topo = dev.rawTopology();
switch (topo) {
    case 1: Serial.println(F("UI1 / I1")); break;
    case 2: Serial.println(F("UI2 / I2")); break;
    case 3: Serial.println(F("UI3 / I3")); break;
    case 0: Serial.println(F("v1.0 firmware — REG_TOPOLOGY unmapped, falling back to hint")); break;
    default: Serial.print(F("I2C error or unknown — got 0x")); Serial.println(topo, HEX);
}

A future v1.1 release of the Arduino library will read REG_TOPOLOGY inside begin() and override the constructor hint when the firmware version is ≥ 0x02. Your existing RbAmp dev(Wire, 0x50) call benefits automatically — no code change needed.

Firmware version gating

cpp
const uint8_t fw = dev.firmwareVersion();
if (fw >= 0x02) {
    // v1.1+ features available:
    //  - REG_TOPOLOGY auto-detection
    //  - CT_MODEL preset auto-load
    //  - 5.2.E ISR race fix
}

Which tier for which use case

Use case Recommended tier Why
Single-room appliance metering BASIC One channel, no export, cheapest
Whole-house consumption BASIC UI1 Mains feed, unidirectional
Solar + grid (sell back) STANDARD UI1 Signed period accumulator — dev.energy().wh(0) goes negative on net export
Per-appliance multi-channel BASIC UI3 3 CT clamps on 3 loads, no per-circuit export expected
Three-phase industrial PRO 3 channels + reactive power + harmonics (v2 features)
Solar inverter monitoring STANDARD UI1 Generation side — signed reading lets you see clip / curtailment
Mains + Solar + Loads dashboard STANDARD UI1 × 2 + BASIC UI3 Mains bidir, solar gen-only, loads per-circuit

See scenario 6 in 06_examples.md for the full home-balance recipe.


Tier upgrade path

A module's tier is fixed at manufacture (analog front-end populates voltage hardware or not; firmware build sets V03_N_I). You cannot upgrade a BASIC to a STANDARD in software — the hardware is missing the voltage divider.

To migrate from BASIC to STANDARD:

  1. Replace the module physically.
  2. New module ships with a fresh I2C address (default 0x50) — use scenario 10 in 06_examples.md to assign your old address.
  3. Calibration is per-module (NF + GAIN in flash) — re-tune per Sensor Selection for the new module's CT clamp.
  4. Energy totals do not transfer between modules — the chip itself doesn't store Wh, only the master does. After swap, restore your master-side total to the new dev.energy() accumulator via library reset() + manual write to your own persistence store.



Source & issues: rb-amp/rbamp-arduino · this page in the repo: docs/02_tiers.md