Use Your AI Agent to Talk to Your Products with Notehub IQ

Blues Developers
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub
Shop
Docs
Help
Support DocsNotehub StatusSecurity AdvisoriesVisit our Forum
Sign In
Docs Home
What’s New
Resources
API Reference
Notecard and Notehub API documentation
Blog
Technical articles for developers
Connected Product Guidebook
In-depth guides for connected product development
Developer Certification
Get certified on wireless connectivity with Blues
Newsletter
The monthly Blues developer newsletter
Terminal
Connect to a Notecard in your browser
Webinars
Listing of Blues technical webinars
Blues.comNotehub
Shop
Docs
Example Apps
Samples
Building a Simple Asset Tracker
Building Edge AI Applications
Continuous Asset Tracking with External GPS and Immediate Location Sync
Controlling Device State and Receiving Acknowledgment
Managing a Notecard's WiFi Network Remotely
Putting a Host to Sleep Between Sensor Readings
Routing Data from Notehub to a Custom Cloud Endpoint
Using LEDs and NeoPixels to Monitor Notecard Status
Accelerators
Asset Location Tracking
Heavy Equipment Hours-of-Use & Utilization Tracker
Returnable Container / Tote Pool Tracker
Untethered Trailer and Chassis Fleet Tracker
Asset Performance Optimization
Legacy Diesel Generator Fleet Performance Uplift
Solar Array String-Level Performance Dashboard
Battery Management Systems
Aerial Lift / Rental Equipment Battery Health Monitor
Off-Grid Solar Battery Site Controller
Remote Cabinet Backup Battery Sentinel
Downtime Prevention
Municipal Wastewater Lift Station Monitor
Rooftop HVAC Predictive Maintenance
VFD Pump Predictive Maintenance
Energy Monitoring Solutions
Commercial Tenant Sub-Metering Bridge
EV Charger Session & Utilization Monitor
Utility Distribution Transformer Load Monitor
Energy Savings
Commercial Plug-Load & After-Hours Waste Dashboard
Demand-Response Solar + Battery Dispatcher
Multi-Site Walk-In Cooler Energy & Setpoint Monitor
Industrial Equipment Monitoring
CNC Machine Spindle Load and Cycle Time Tracker
Injection Molding Shot-to-Shot Process Monitor
Trailer Manufacturer Connected Trailer Platform
Loss Prevention
Construction Equipment Anti-Theft Tracker with Immobilizer
Reefer Trailer Cold-Chain & Door-Event Monitor
Remote Patient Monitoring
Cellular Medication Adherence Pillbox
Post-Discharge Vitals Relay Hub
Remotely Monitored Assets
Off-Grid Livestock Water Tank Monitor
Remote Apiary Hive Health Monitor
Safety Assurance
Construction Site Environmental & Noise Exposure Monitor
Lone Worker Panic and Fall Detection Beacon
Regulatory-Grade Pharmacy and Lab Cold Storage Audit Monitor
Supply Chain Tracking
Pallet-Attached Cold Chain Logger
Rail Car Condition & Interchange Tracker
Truck Roll Reduction
Commercial Grease Interceptor Level Monitor
Propane / LPG Tank Fill Telemetry
homechevron_rightExample Appschevron_rightAcceleratorschevron_rightCommercial Tenant Sub-Metering Bridge

Commercial Tenant Sub-Metering Bridge

Commercial Tenant Sub-Metering Bridge banner
note

This reference application is here to give you ideas and help you get started quickly. It's built on specific hardware that may not match yours, so focus on the sections that fit your use case. If you'd like to talk through your project and whether it's a good fit for Blues, reach out.

Building with an AI assistant? Point it at this accelerator's firmware in firmware/tenant_sub_meter/ so it can learn how the device works and help you adapt it to your hardware.

This project is a cellular energy monitoring retrofit for multi-tenant commercial buildings. Put four Rogowski coil sensors on a panel, each with its own active integrator circuit, then add a Blues Notecard Cell+WiFi and a Notecarrier CX with its onboard STM32 host. Every hour, the landlord gets per-tenant estimated interval energy (Wh), 15-minute blocked-average demand (W), and a per-channel fault bitmask. That data travels on a channel the tenants can't see or interfere with, and nobody has to run a single network cable through the building.

1. Project Overview

The problem. The landlord of a multi-tenant commercial building (a flex-industrial park, a shared professional building, an office strip) wants to charge each tenant for the electricity they actually use, instead of splitting the utility bill by square footage or some other proxy. The fair way to do that is sub-metering: put a watt-hour meter on each tenant's branch circuit and read it monthly.

What stops most landlords is cost and complexity. A commercial sub-meter needs a wired or networked path to get its data off the panel and into the billing system. An older building with four or eight tenants has no data infrastructure at all in the electrical room. Adding it means negotiating with tenants, coordinating with the utility, and running cable through occupied ceilings, so most landlords give up and keep allocating by square footage.

This project skips the meters with network cards. It clips Rogowski coil sensors onto each tenant's existing feed conductors, with no wire cuts, no conduit, and no utility coordination, and it uses a Notecard to move the data to the cloud. A voltage transducer supplies a coherent voltage reference. That lets the device compute active power (W) and estimated interval energy (Wh) from sequential interleaved V×I measurements, instead of estimating a figure from an assumed voltage and a fixed power factor. The system installs in an afternoon and costs a fraction of a traditional per-tenant metered retrofit.

What this project delivers, and what it does not. This design gives you estimated tenant load allocation data. That's useful for proportional charge-back, load profiling, and spotting tenants with unusually high consumption. It does not replace a utility-certified sub-meter. Readings come from periodic active-power snapshots (one ~200-millisecond V×I mean every five minutes) rather than continuous integration. And because V and I are sampled one after the other instead of simultaneously, there's a small systematic error at low power factors. For internal bill-back, the approach is accurate enough to split costs proportionally between tenants. If your billing needs certified metering accuracy, for example sub-metering that falls under utility-tariff accuracy regulations, replace this approach with a dedicated simultaneous-sampling energy-metering IC or a certified sub-meter interface.

Why Notecard, and why cellular specifically. In most IoT projects, the case for cellular comes down to location. The asset is remote, or on a rooftop, or somewhere Ethernet is impractical to run. This one is different. The panel room is usually indoors with WiFi somewhere nearby, and the catch is whose WiFi it is.

New to Blues?

Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.

Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.

In a multi-tenant building, the tenants almost always control the WiFi access points. A retail tenant pays for its own ISP and runs its own router, and so does a professional office tenant. The building owner usually has no WiFi at all, and if they do, the tenants can get on it. Sending billing telemetry over any network the tenants can reach creates a political and legal problem, since a savvy tenant can see what's being sent, disrupt the connection, or claim the data was tampered with. The landlord needs a data channel that is:

  1. 100% landlord-controlled: the tenant can't touch it
  2. Invisible to tenants: the tenant can't see or intercept it
  3. Operationally simple: no IT setup, no passwords, and no tenant cooperation

A cellular Notecard meets all three. It's a module with its own SIM that calls home to the Blues Notehub cloud service over the cellular network, the same way the building's alarm system uses its own GSM dialout. The tenant has no access to that connection and no visibility into it. Nobody fills out a form, pairs with an AP, or raises an IT ticket with a tenant, and the data channel belongs to the landlord as much as the panel does.

That matters more than it might seem, because billing disputes are among the most contentious issues in commercial tenancy. When a meter's data travels over the tenant's network, a clever tenant can plausibly dispute its readings. A cellular Notecard is the only architecture that rules out that dispute by design.

Deployment scenario. A small weatherproof or panel-rated enclosure mounts inside or next to the building's electrical panel. Four Rogowski coil sensors, one per tenant feed, run their leads into the enclosure through a short cable gland. A voltage transducer taps the same panel bus for a building voltage reference, and line power comes from a spare breaker on the same panel. You don't rent rack space, claim a network closet, or need IT help from anyone.

2. System Architecture

System architecture: 4 Rogowski coils + ZMPT101B voltage transducer → Notecarrier CX with STM32 host and Notecard MBGLW → cellular → Notehub → bill-back / analytics / property management

Device-side responsibilities. All four channels share one STM32L433 host on the Notecarrier CX. That host wakes every five minutes (the default) and does the same work each cycle. For each active tenant channel, it interleaves ADC samples between the voltage transducer output and the Rogowski integrator output, then computes active power (W) from a V×I cross-product. It accumulates estimated interval energy (Wh), 15-minute blocked-average demand (W), and a per-channel hardware-fault bitmask in a RAM struct. The struct survives each sleep because the host sleeps in STM32 STOP2, which retains RAM. Once an hour the host emits a summary Note with the accumulated energy, peak demand, and fault flags. Configuration (sensor sensitivity, voltage scaling, all of it) lives in environment variables that Notehub distributes, so the landlord can adjust calibration in the field without a reflash.

Notecard responsibilities. The Notecard runs the cellular link and controls when the host sleeps. It queues Notes locally, wakes the cellular radio on the configured hub.set outbound cadence (60 min by default), and flushes the whole queue to Notehub in one cellular session. Environment-variable changes pushed from Notehub come back on its inbound cadence (120 min by default). Between samples, card.attn sleep mode holds the ATTN pin low while the host sleeps in STM32 STOP2 (a few µA), then raises it to wake the host for the next sample.

Notehub responsibilities. Notes reach Notehub over the Notecard's embedded global SIM, and Notehub stores them durably and routes them downstream. Notehub is also the sole monthly aggregation path. A billing or property-management platform uses the Notehub Event Query API to sum each tenant's t*_wh values across all of a device's hourly meter_summary.qo events for any billing period. The device never emits a month-end Note or tries to compute a billing total, because the hourly event stream in Notehub is the complete, authoritative record.

Fleets group devices by property, so a property manager with several buildings can push sensor calibration settings to each building without touching individual devices. A Notehub HTTP or MQTT route can forward each meter_summary.qo Note straight to the property-management or billing platform as it arrives.

Routing to the cloud (high level only). Notehub can route to HTTP, MQTT, AWS, Azure, GCP, Snowflake, and other destinations. Route setup depends on your project, and this one doesn't ship with a specific downstream endpoint. See the Notehub routing docs for details.

3. Technical Summary

The device has four independent sub-meter channels, each with isolated analog inputs. The Notecarrier CX's STM32L4 host reads them in turn, taking a ~200 ms burst of 2000 sequentially interleaved V/I ADC sample pairs from each, and runs accumulation windows across configurable billing periods. Per-tenant energy (Wh), peak demand (W), and hardware-fault status go out through Notecard on a configurable outbound cadence. On-device queuing, sleep gating, and fault-aware transmission ride out carrier dropouts and cloud-side downtime without losing data. You don't need external flash or EEPROM, since state lives in host RAM, which STOP2 retains between wakes.

Here is a sample Note this device emits:

{
  "t1_wh":       482.3,
  "t1_demand_w":  655.0,
  "t2_wh":       214.1,
  "t2_demand_w":  301.5,
  "t3_wh":       673.8,
  "t3_demand_w":  912.0,
  "t4_wh":       149.2,
  "t4_demand_w":  236.0,
  "fault_mask":     0
}

4. Hardware Requirements

Prototype / Bench Evaluation BOM

Use this BOM for firmware bring-up, calibration, and evaluation. The ZMPT101B module isn't agency-listed for permanent installation inside a commercial panel enclosure, so swap in an agency-listed isolated voltage transducer before you deploy to a live panel. §11 Limitations and Next Steps covers the full production voltage-sensing path, including the hardware requirements and notes on adapting the firmware.

PartQtyRationale
Notecarrier CX1Integrated carrier with an onboard STM32L433 host. Its six analog inputs cover all four current channels plus the voltage channel, with one to spare, so you don't need a separate MCU.
Notecard Cell+WiFi (MBGLW) (datasheet)1Cellular is required, because tenant WiFi and building WiFi are off-limits for billing telemetry. The MBGLW SKU has WiFi, but this design deliberately leaves it unused.
Blues Mojo (commissioning and bench validation only, not deployed to the field)1Coulomb counter for checking the sleep/wake current profile during bring-up. See §10.
Magnelab RCT-1800-000 flexible Rogowski coil (spec sheet), or an equivalent bare-output flexible Rogowski coil rated for the installed conductor capacity4Non-invasive clip-on coil that needs no conductor cut or conduit penetration. The RCT-1800-000 fits a wide range of conductor diameters, and its flexible loop closes with a snap-together coupler. Its dI/dt output needs the per-channel active integrator described in §5. Use one coil per tenant. Any Rogowski coil with a proportional dI/dt output will work, as long as you calibrate rogowski_amps_per_volt to match the installed coil's rated output sensitivity.
Microchip MCP6004 (datasheet) quad op-amp, single-supply 1.8 V–6.0 V. Use MCP6004-I/P (DIP-14) for breadboard/perfboard builds or MCP6004-I/SL (SOIC-14) for PCB layouts1Drives all four active Miller integrators from one package. It runs single-supply from the Notecarrier CX 3.3 V rail with rail-to-rail input/output, and its 1 MHz GBW is far above the 60 Hz line frequency it integrates.
10 kΩ resistor 1% (R_in, integrator input)4One per integrator channel. Sets the integrator time constant together with C_f.
1 MΩ resistor 1% (R_f, integrator DC-stabilization)4One per integrator channel. Limits the Miller integrator's DC gain so op-amp offset drift can't saturate the output against a rail.
10 nF film or C0G capacitor (C_f, integrator feedback)4One per integrator channel. Works with R_in and R_f to set the frequency response.
100 kΩ resistor 1% (bias half-rail divider)10Two per input channel (four current + one voltage = five channels). Each pair forms the half-rail divider that centers an AC signal at 1.65 V for the single-supply ADC.
10 µF electrolytic capacitor5One per channel. On the current-sensor channels (A0–A3), it shunts AC noise from the op-amp IN+ bias node to GND. On the voltage channel (A4), it's wired in series between ZMPT101B VOUT and ADC A4 to AC-couple the module output onto the 1.65 V bias point (electrolytic polarity: + toward VOUT).
ZMPT101B voltage sensor module, VCC/GND/VOUT pinout, 5 V supply (bench/prototype only, not agency-listed for mains use in a commercial panel enclosure). It's a Zhongmet Technology ZMPT101B transformer IC on a PCB with an onboard burden resistor and trim pot. See the ZMPT101B IC datasheet.1Galvanically isolated AC voltage sensor module that works well for bench validation and firmware calibration. It taps the building line voltage through screw terminals and outputs a scaled AC waveform at VOUT. The firmware's volt_scale env var adapts to any module with a proportional output. See §5 for wiring details. Replace it with an agency-listed isolated voltage transducer before deploying to a commercial panel. §11 covers the hardware and firmware requirements for the production path.
AC/DC supply, 5 V / 2 A (e.g. MeanWell IRM-10-5)1Makes 5 V DC from 120 VAC at a spare panel breaker. The 2 A rating leaves headroom for the Notecard's cellular transmit burst on top of the host MCU and analog front-end circuits.
Molex 2091420180 Flexible LTE/WiFi/GNSS Antenna, u.FL, 180 mm lead, 698 MHz–4.0 GHz1Required for any metal-enclosure or metal-panel installation. Replace the stub antenna included with the Notecarrier CX kit if the 180 mm lead is too short to exit the enclosure through a cable gland. Route the antenna outside the metal panel (see §5). If the cable gland is farther from the Notecard, add a u.FL-to-u.FL extension cable of the right length between the Notecard's antenna port and the antenna's u.FL terminator.
NEMA 4X or panel-rated enclosure, ~6×4×2″1Houses the Notecarrier CX, op-amp integrator board, and power supply in the panel room.

All Blues hardware ships with an active SIM that includes 500 MB of data and 10 years of service, with no activation fees and no monthly commitment.

5. Wiring and Assembly

Wiring: 4 Rogowski coils on A0–A3, ZMPT101B voltage on A4, MCP6004 op-amp integrator; ATTN → D5 jumper so the Notecard can wake the host from STOP2; cellular antenna via u.FL; 120 VAC → 5 V supply → Mojo (bench) → +VBAT

All host I/O lands on the Notecarrier CX dual 16-pin header. A short jumper between ATTN and D5 on that header lets the Notecard wake the sleeping host. The Notecard MBGLW seats in the carrier's M.2 slot. On the bench, Mojo sits inline between the 5 V supply output and the Notecarrier CX +VBAT pad.

warning

Route the cellular antenna outside metal panels. Connect the external cellular antenna's u.FL lead to the Notecard's primary antenna port, and run the cable through a cable gland to outside the metal panel cabinet or NEMA enclosure. Steel panels and enclosures act as Faraday cages, so a rubber-duck or pigtail antenna left inside a closed metal panel won't hold a reliable cellular session. It's the most common field bring-up failure in panel-mounted deployments.

Power chain

AC/DC 5 V supply (IRM-10-5)
  └─ +5 V out ──► Mojo BAT             (bench validation — Mojo measures supply current)
                    └─ Mojo LOAD ──► Notecarrier CX +VBAT   (3.3 V regulated on-board)

In a production deployment without Mojo, connect the AC/DC supply's 5 V output directly to Notecarrier CX +VBAT.

warning

Don't connect the supply output to both +VBAT and +VUSB/+5V on the Notecarrier CX at the same time. The +VBAT pad is the right power input for this supply voltage and load profile, and feeding two supply rails at once can back-feed one regulator through the other.

Active Miller integrator (replicate for each of the four current channels)

A Rogowski coil outputs a voltage proportional to dI/dt, the derivative of the primary current. The active Miller integrator turns that dI/dt signal back into one proportional to instantaneous current, which the ADC can then read for the RMS and V×I math.

Rogowski coil output (bare lead)
  │
  ├── R_in (10 kΩ) ──────────────────────────► op-amp IN− (inverting)
  │                                                │
  │                         R_f (1 MΩ)             │
  │                   ┌────────────────────────────┤
  │                   │                            │
  │                   │     C_f (10 nF)            │
  │                   └──┤├────────────────────────┘
  │
  │   op-amp IN+ (non-inverting) ──────────────── 1.65 V bias node
  │
  └── op-amp OUT ─────────────────────────────── to ADC pin (A0–A3)

Bias node: +3V3 ── 100 kΩ ──┬── 100 kΩ ── GND
                            ├── 10 µF ── GND
                            └── op-amp IN+ of this channel

Use one section of the MCP6004 (U1A–U1D) for each channel. All four op-amps share Vdd = +3V3 and GND.

Rogowski coil polarity: If a channel with a known load shows zero or near-zero t*_wh accumulation in Notehub, the coil leads may be reversed. The firmware clamps negative watts to zero before accumulating energy, so reversed polarity shows up in Notehub as zero or near-zero billed energy instead of negative values. During bench bring-up you can spot it on the debug serial output by looking for a negative watt figure in the [sample] T*: line. To fix it, swap the coil's two output leads at the integrator's R_in input.

ZMPT101B voltage sensor module (bench/prototype — one module, building voltage reference)

This design uses a conditioned ZMPT101B module (VCC / GND / VOUT pinout), not a bare transformer secondary. The module's PCB carries the ZMPT101B transformer, an onboard burden resistor that sets the output scaling, and a calibration trim pot. Wire it like this:

Building 120 V line ──► ZMPT101B module AC primary (IN1 / IN2 screw terminals,
                         fused tap per local code)

Module power and signal:
  VCC  ──► +5V (from AC/DC supply before the Notecarrier CX regulator)
  GND  ──► GND

  VOUT ──► 10 µF (series AC coupling) ──┬── ADC A4
                                        │
               +3V3 ── 100 kΩ ──────────┤
                                        │
                GND ── 100 kΩ ──────────┘

The module's AC output rides on a DC level set by its onboard circuitry, usually near half its supply voltage (about 2.5 V when powered at 5 V). VOUT is AC-coupled through the 10 µF series capacitor to ADC A4, and the external 100 kΩ / 100 kΩ voltage divider then holds the ADC input's DC operating point at 1.65 V, regardless of the module's internal reference. The firmware removes any leftover DC offset in software (v_dc = mean(v_buf[])), so you don't need to trim the exact bias voltage.

The onboard burden resistor and trim pot set the output's peak-to-peak swing. With 120 V on the primary and the factory burden, the VOUT pin usually reads ≈0.1 V RMS, and the default volt_scale of 1200 is the matching calibration constant (120 V ÷ 0.1 V). For the best accuracy, measure a known line voltage with a precision AC voltmeter and trim volt_scale in Notehub to match.

Pin-by-pin assignments

  • A0 → Tenant 1 Rogowski integrator output (op-amp OUT direct)
  • A1 → Tenant 2 Rogowski integrator output (op-amp OUT direct)
  • A2 → Tenant 3 Rogowski integrator output (op-amp OUT direct)
  • A3 → Tenant 4 Rogowski integrator output (op-amp OUT direct)
  • A4 → ZMPT101B VOUT via 10 µF series AC coupling cap into the 1.65 V bias node
  • +3V3 → MCP6004 Vdd, top of all half-rail bias dividers
  • +5V → ZMPT101B module VCC (bench)
  • GND → MCP6004 GND, bottom of all bias dividers, negative terminals of the four current-channel 10 µF bias-decoupling caps (A0–A3 bias nodes), Rogowski coil returns, ZMPT101B module GND (the voltage-channel 10 µF capacitor is the series AC-coupling cap between ZMPT101B VOUT and A4, so its negative lead connects toward A4 and the 1.65 V bias node, not to GND. See the ZMPT101B wiring diagram above.)
  • D5 → Notecarrier CX ATTN (jumper; see below)

ATTN-to-D5 jumper (host sleep)

Fit a short jumper between the ATTN and D5 pins on the Notecarrier CX. Both are on the same 16-pin header. The Notecard pulls ATTN low while the host sleeps and raises it when the next sample is due, and the rising edge on D5 wakes the host from STOP2. Leave the Notecarrier CX EN pin unconnected. On the CX, that pin enables the shared 3.3 V VIO rail (host, Qwiic, 3V3_OUT, and Notecard I/O), so driving it from ATTN would brown out the whole board instead of putting the host to sleep. Putting a Host to Sleep Between Sensor Readings walks through the full pattern.

Rogowski coil placement

Each Rogowski coil wraps around one conductor only of the tenant's single-phase branch-circuit feed, which is the hot leg. The coil has to close fully, with the two ends of the flexible coil clipped together, because an open gap produces a signal that can't be integrated. The firmware is built for single-phase circuits where every tenant comes off the same phase leg. See §11 for the multi-phase case.

warning

Electrical safety. Panel interiors carry hazardous voltages. Installation must be done by a licensed electrician following the applicable electrical code, site lockout/tagout procedures, and the panel manufacturer's instructions. The Rogowski coils and voltage transducer are galvanically isolated from the mains. The danger during installation comes from the nearby bus bars and termination points.

Before powering on for the first time:

The build prints a "PRODUCT_UID is not defined" reminder every time, even after you set it, so check the first item by eye. If upload fails with "board not found", check the DIP switch and USB cable.

6. Notehub Setup

Fast path to first event

  1. Notehub: create a Notehub project and copy its ProductUID.
  2. Wire the bench rig: Notecarrier CX + Notecard MBGLW + four Rogowski integrator circuits on A0–A3 + ZMPT101B on A4. The full pinout is in §5.
  3. Edit one line in firmware/tenant_sub_meter/tenant_sub_meter_helpers.h, replacing #define PRODUCT_UID "" with your project's value.
  4. Flash:
    arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC"
    arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none firmware/tenant_sub_meter/
    arduino-cli upload -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none -p /dev/cu.usbmodem* firmware/tenant_sub_meter/
    The usb=none board option turns off the USB CDC stack, which has to be off for the host to sleep (see §7.1). §7.1 also has full troubleshooting. Connect the Notecarrier CX over USB and set the DIP switch to HST.
  5. Watch serial: open a serial monitor at 115200 baud on the ST-LINK virtual COM port (the Notecarrier CX debug jack; see §7.1). You'll see the Notecard configuration, then channel sample lines. After the first sleep cycle the output goes quiet for 5 minutes while the host is in STOP2. That silence is expected, and it means things are healthy.
  6. Check Notehub: open the Events tab. A _session.qo shows up within minutes. The first meter_summary.qo usually arrives in that initial session, shortly after the first sample cycle completes (within an hour).

Fleets, environment variables, and routing

Once your device shows up in the Devices tab and events are flowing, set the project up for ongoing operation:

  1. Create a Fleet per property. Fleets group devices for shared configuration. The natural unit here is one fleet per building. Panels in the same building usually run at the same line voltage and use the same Rogowski coil model, so fleet-level environment variables can say "this building's panels all use these sensor calibrations." For multi-property deployments, Smart Fleets can auto-assign devices by serial number prefix.

  2. Set environment variables. In Notehub, go to Fleet → Environment (or Device → Environment for per-device overrides). Set the variables below, then save. The device pulls them on its next inbound sync (every 2 hours by default), so you don't need a reflash or a truck roll.

    VariableDefaultPurposeWhen to adjust
    sample_interval_sec300Seconds between measurements (minimum 60, max 86400).Leave at 300 for most installs. Smaller values use more power and produce more frequent events.
    summary_interval_min60Minutes between hourly summaries (minimum 15, max 1440). The 15-minute minimum matches the 15-minute demand window.Leave at 60 for standard hourly billing. Values < 15 are silently clamped to 15.
    rogowski_amps_per_volt400.0Rogowski coil + integrator sensitivity in A/V, calculated as rated primary amps ÷ integrator output V RMS at rated load.Critical: calibrate against a reference clamp meter at a known full load before you bill anyone. A wrong value makes the energy readings wrong by the same proportion. See the calibration note in §10.
    volt_scale1200.0Voltage transducer calibration: line-voltage V RMS ÷ ADC-pin V RMS.The default (1200.0) matches a ZMPT101B at 120 V nominal. For accuracy, measure line voltage with a precision AC voltmeter and trim to match.
    num_tenants4Active current channels to sample (1–4).Set it to the number of tenants you're actually metering so the device skips needless ADC reads.
  3. Configure routes. Route meter_summary.qo to your time-series database or billing platform. It's the canonical energy record and the right source for monthly aggregation: use the Notehub Event Query API to sum each tenant's t*_wh values across all hourly events in the billing period. The Notehub routing docs list the destination types.

What you should see in Notehub

Within minutes of first power-on, your project's Events tab should show a _session.qo, which is the cellular handshake. The firmware emits a summary Note on first boot, so the first meter_summary.qo usually arrives in that initial session.

Example meter_summary.qo payload:

{
  "t1_wh":       482.3,
  "t1_demand_w":  655.0,
  "t2_wh":       214.1,
  "t2_demand_w":  301.5,
  "t3_wh":       673.8,
  "t3_demand_w":  912.0,
  "t4_wh":       149.2,
  "t4_demand_w":  236.0,
  "fault_mask":  0
}

A Note like this arrives every hour. t*_wh is each tenant's estimated interval energy in watt-hours over the past hour, and summing these across every hourly event in a billing period gives you monthly kWh. t*_demand_w is the peak 15-minute average demand (in watts) during that hour. fault_mask = 0 means every channel passed its hardware health checks.

7. Firmware Design

The firmware is split across four files, and all four must be in the same sketch directory:

  • firmware/tenant_sub_meter/tenant_sub_meter.ino holds the main sketch: setup() / loop() / runCycle(), state management, measurement orchestration, and the sleep/wake lifecycle.
  • firmware/tenant_sub_meter/tenant_sub_meter_helpers.h holds the shared types (TenantState, AppState, RuntimeConfig, ChannelMeasurement), constants, fault-flag definitions, and helper function declarations.
  • firmware/tenant_sub_meter/tenant_sub_meter_helpers.cpp implements all of the helper functions (measureChannel, fetchEnvOverrides, initNotecard, defineTemplates, sendSummary, and getEpochSec).
  • firmware/tenant_sub_meter/cx_sleep.h handles host sleep: the card.attn request, STOP2 entry, and ATTN-pin wake (cxSleepBegin, cxSleepUntilAttn).

7.1 Installing and flashing

Dependencies:

  • Arduino core for STM32 (stm32duino/Arduino_Core_STM32). Install it from the Arduino Boards Manager (search "STM32 MCU based boards"), or add the index URL https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json under File → Preferences → Additional Boards Manager URLs. The Notecarrier CX's host uses the Cygnet board definition (see the build settings below).
  • Blues Wireless Notecard library (note-arduino). Install it from the Arduino Library Manager or with arduino-cli lib install "Blues Wireless Notecard". When you install, check for the latest release on the note-arduino releases page.
  • STM32duino Low Power library and its dependency STM32duino RTC (stm32duino/STM32LowPower). Install it from the Library Manager. It provides the STOP2 entry and ATTN-pin wake that cx_sleep.h uses.

Build settings: The Notecarrier CX's host is the same STM32L433 as the Blues Cygnet, so select Blues boards → Cygnet (pnum=CYGNET) and set USB support (if available) to None (usb=none). If the USB CDC stack is enabled and no USB host is attached (the Notecarrier CX USB-C switch in NC, or no cable), the STM32's USB wakeup interrupt fires continuously and the host never stays in STOP2. Debug output goes to the LPUART on the Notecarrier CX debug jack instead, which an ST-LINK V3 exposes as a virtual COM port (see Serial Logging With STLINK).

Flashing with the Arduino IDE: Open tenant_sub_meter.ino, apply the build settings above, and click Upload. With the Notecarrier CX's DIP switch in HST, you can reach the host's DFU bootloader over the USB-C port (hold BOOT, tap RST). You can also upload over SWD with an ST-LINK on the debug jack.

Flashing with arduino-cli, step by step:

First, confirm the board and port:

# List all recognized boards
arduino-cli board listall | grep -i cygnet
# Expected output: STMicroelectronics:stm32:Blues:pnum=CYGNET

Then set the Notecarrier CX's DIP switch to HST, and compile and upload. The usb=none option turns off the USB CDC stack, which has to be off for the host to sleep:

# Compile only (no upload yet)
arduino-cli compile \
  -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
  firmware/tenant_sub_meter/

# List USB ports connected to see your device
arduino-cli board list

# Upload (replace /dev/cu.usbmodem* with your actual port from board list)
arduino-cli upload \
  -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
  -p /dev/cu.usbmodem* \
  firmware/tenant_sub_meter/

# Watch the progress — when upload is complete, disconnect USB and flip DIP to NC

On Windows, ports are named COM3, COM4, and so on. On macOS and Linux they look like /dev/cu.usbmodem* or /dev/ttyUSB*.

After flashing, open a serial monitor at 115200 baud on the ST-LINK virtual COM port. On first power-on you'll see the Notecard configuration exchange, then the first channel sample lines. After the first sleep, the output goes quiet for sample_interval_sec. That's expected. The host is in STOP2, and it resumes loop() in place when the Notecard raises ATTN for the next wake.

7.2 Module map

ResponsibilityWhere
Notecard init, hub.set (accelerometer disable is in setup())initNotecard()
Template registration (meter_summary.qo only)defineTemplates()
Environment variable fetch and clampingfetchEnvOverrides()
Sequential-interleaved V×I measurement, RMS current and active powermeasureChannel()
Wh accumulation, demand-window accumulation and peak demand trackingrunCycle() sample loop
Hourly summary emission with confirmed note.addsendSummary()
Host sleep (STOP2 until the Notecard raises ATTN)cx_sleep.h: cxSleepBegin(), cxSleepUntilAttn()
Application state (held in RAM across sleep cycles)AppState

7.3 Sensor reading strategy

The measureChannel() function captures 2000 interleaved voltage/current sample pairs from VOLTAGE_PIN (the voltage transducer output) and the selected current pin (the Rogowski integrator output). At the STM32L433's default 12-bit ADC rate, each analogRead() takes about 50 µs. One interleaved pair takes ~100 µs, so 2000 pairs cover ~200 ms, or about 12 full 60 Hz cycles. Voltage is always sampled first in each pair, which keeps any systematic ADC switching latency between channels the same across every pair.

DC removal. External resistor dividers bias both signals to 1.65 V (half-rail). A single-pass mean over all the samples gives the per-channel DC offset, and the firmware subtracts it before computing RMS or cross-products. That strips out the bias voltage and any leftover ADC offset, so the current channels need no AC-coupling capacitor. The 10 µF series cap on A4 is there only to block the ZMPT101B module's own DC level.

Active power computation. Active power P = mean(v[n] × i[n]), where v[n] and i[n] are the DC-removed voltage and current samples scaled to physical units. Because the V×I cross-product captures the real phase relationship between voltage and current, you don't have to assume a power factor.

Phase/accuracy limitation from sequential sampling. Voltage and current are read one after the other, not simultaneously, so there's a fixed ~50 µs offset between the V sample and the I sample in each pair. One full 60 Hz cycle is 16.67 ms, so 50 µs works out to a constant phase offset δ of about 1.08°. The measured cross-product evaluates cos(φ − δ) instead of cos(φ), where φ = arccos(PF) is the load power-factor angle. That makes the relative error in active power roughly tan(φ)·sin(δ). At power factors above 0.9 (typical commercial loads, φ ≈ 26°), the error is about 1% of the true active-power reading, and at PF 0.7 (φ ≈ 46°) it reaches about 2%. For inductive loads (positive φ), the firmware consistently over-reads active power by that margin. If you need certified accuracy, as with utility-tariff sub-metering subject to accuracy regulations, use a dedicated simultaneous-sampling energy-metering IC instead.

A Rogowski coil installed with its leads reversed produces consistently negative watts. The firmware clamps negative values to zero, so the problem shows up in the serial log as a zero-energy reading on that channel.

Energy estimation. Every sample_interval_sec (5 min by default), the firmware takes one ~200 ms active-power snapshot (mean V×I over 2000 sample pairs) and multiplies it by the full interval to get an estimated interval energy in Wh. That's accurate for constant or slowly changing loads. On bursting or cycling loads, where power swings a lot inside the 5-minute window, it can over- or under-state energy. The t*_wh accumulator adds up these interval estimates and isn't a continuous integral. So the design fits proportional load allocation (working out which tenants use more energy than others), but not applications that need certified metering accuracy.

Demand window. Two fields grow across sample wakes: demand_window_mwh holds the accumulated energy and demand_window_sec holds the elapsed time. Once demand_window_sec reaches DEMAND_INTERVAL_SEC (900 s = 15 minutes), the firmware computes average watts as demand_window_mwh × 3600 / (demand_window_sec × 1000) and compares the result with peak_demand_cw, the per-tenant centi-watt accumulator for the current summary period. If the new average is higher, it replaces peak_demand_cw. Then both window fields reset and the next 15-minute interval begins. You get a true interval-average demand reading rather than a transient snapshot, and it matches the most common North American utility demand-charge billing window. In meter_summary.qo, the t*_demand_w field reports peak_demand_cw / 100 (converted to watts) for the summary period.

The demand window straddles summary-period boundaries on purpose. peak_demand_cw is cleared after each confirmed sendSummary(), but demand_window_mwh and demand_window_sec keep running, so no 15-minute interval gets silently cut short at a summary boundary.

Summary interval and demand window floor. The firmware clamps summary_interval_min to a minimum of 15 (MIN_SUMMARY_INTERVAL_MIN), which matches DEMAND_INTERVAL_SEC / 60. A summary period shorter than one demand window would always emit t*_demand_w = 0, since no complete 15-minute window could close inside it. That's a window-timing gap, not a hardware fault. If you need finer-grained reporting, use the t*_wh field and accumulate your own demand windows in the downstream system.

Per-channel fault bitmask. Each call to measureChannel() can set three fault flags in each channel's 4-bit nibble. FAULT_BIAS_RANGE (0x01) sets when the ADC DC offset falls outside the expected 1.40–1.90 V half-rail window. FAULT_SATURATED (0x02) sets when the centered signal's RMS exceeds the RMS equivalent of 85 % of the 1.65 V half-rail peak amplitude (≈ 0.99 V RMS at the ADC pin), which means the waveform is getting close to clipping at the ADC rail. Bit 2 is reserved. It used to be FAULT_NO_SIGNAL, which was retired because current magnitude alone can't tell a legitimately unloaded tenant circuit from a disconnected Rogowski coil (see the commissioning diagnostic note below). FAULT_VOLTAGE_REF (0x08) sets when the shared voltage reference path looks suspect: bias out of range, signal saturated, or measured line RMS below VOLTAGE_MIN_V_RMS. Since every channel shares that voltage reference, FAULT_VOLTAGE_REF goes into every active channel's fault nibble at once, so downstream billing can pick out periods where all the tenant watt calculations are compromised.

The firmware ORs the fault flags across every sample wake in the summary period and packs them into the fault_mask field of meter_summary.qo, one 4-bit nibble per tenant channel (T1 in bits 3:0, T4 in bits 15:12). A fault_mask of 0 means every channel passed every check on every sample in the period, and downstream billing systems should reject or flag any summary where fault_mask != 0. Low RMS current (below 0.05 A) only goes to the debug serial port as a commissioning diagnostic. It never lands in fault_mask.

7.4 Event payload design

meter_summary.qo is registered as a template-backed Notefile at first boot. Templates store Notes as fixed-length binary records, which cuts on-wire size by 3–5× compared with free-form JSON.

meter_summary.qo, emitted hourly (canonical record)

The Note carries nine 4-byte floats: estimated interval energy (Wh), 15-minute blocked-average demand (W), and a combined fault bitmask per summary period. t*_wh is the sum of (W_sample × sample_interval_sec / 3600) across every periodic sample in the reporting period. It's estimated interval energy built from power snapshots, not a continuous integral or a kVAh estimate. t*_demand_w is the peak 15-minute blocked-average demand seen during the summary period, meaning the highest average watts over any completed DEMAND_INTERVAL_SEC (900 s) window (see §7.3). fault_mask packs the per-channel hardware-fault flags as one 4-bit nibble per tenant (T1 in bits 3:0; §7.3 has the bit definitions), and 0 means every channel stayed clean for the whole period.

{
  "t1_wh":       482.3,
  "t1_demand_w":  655.0,
  "t2_wh":       214.1,
  "t2_demand_w":  301.5,
  "t3_wh":       673.8,
  "t3_demand_w":  912.0,
  "t4_wh":       149.2,
  "t4_demand_w":  236.0,
  "fault_mask":     0
}

To get monthly per-tenant totals, use the Notehub Event Query API to sum t*_wh across all of a device's meter_summary.qo events for the billing period, then divide by 1000 for estimated kWh. That Notehub-side aggregation is the only monthly rollup, and the device never generates a monthly Note.

7.5 Low-power strategy

The panel installation is line-powered, but keeping the host asleep between samples still pays off. It cuts enclosure heat and supply wear, and the firmware ports straight to battery-powered variants without a rewrite. After each sample cycle, loop() calls cxSleepUntilAttn() (in cx_sleep.h). That function sends a card.attn sleep request for sample_interval_sec seconds, waits for the Notecard to pull ATTN low, and puts the STM32 into STOP2, where it draws a few microamps and keeps its RAM. When the interval is up, the Notecard raises ATTN, the rising edge on D5 wakes the host, and execution picks up on the next line of loop().

Because the host resumes in place instead of cold-booting, the AppState struct just lives in RAM. Nothing gets serialized to the Notecard, and setup() runs only once per power-up. If the sleep request fails or ATTN never goes low (a missing jumper, say), loop() waits out the interval awake and tries again on the next cycle, so the sample cadence holds either way.

Between cellular sessions, the Notecard sits in its own low-power idle state (~18 µA at 5 V for the Cell+WiFi variant). Summary Notes queue locally and go out together in one hourly session, and the radio stays off during the wakes in between.

7.6 Retry and error handling

Environment variable fetch failure. fetchEnvOverrides() calls env.get and checks for a NULL response (Notecard not yet synced) before it touches the RuntimeConfig struct. If the response is missing or malformed, the firmware keeps its defaults. Every variable also gets a numeric bounds check. For example, rogowski_amps_per_volt is only applied if it falls in (0, 10000], so a zero or negative value can't corrupt the power calculations that follow.

card.time not yet valid. getEpochSec() returns 0 until the Notecard has a time sync. The first_summary_sent latch keeps the firmware from re-triggering sendSummary() on every wake before that sync. Once sendSummary() succeeds, the latch is set, and later triggers need both now > 0 and an elapsed summary_interval_min. If the very first sendSummary() succeeded while now was still 0, though, last_summary_epoch stays at 0, which would block every hourly summary after it. So on the first wake where now > 0, the firmware looks for that condition (first_summary_sent && last_summary_epoch == 0) and seeds last_summary_epoch = now, and the normal elapsed-time gate takes over from there.

note.add / template failure: the most critical failure path. sendSummary() only runs when state.notecard_configured == 1. If initNotecard() or defineTemplates() fails on a given wake, the configured flag stays clear, the firmware skips all Note emission for that cycle, and the accumulators are kept without ever reaching the send path. When notecard_configured is set, sendSummary() uses notecard.requestAndResponse() instead of the fire-and-forget sendRequest(), so it explicitly checks the Notecard's acknowledgement (or error):

J *rsp = notecard.requestAndResponse(req);
bool ok = rsp && !notecard.responseError(rsp);
notecard.deleteResponse(rsp);
return ok;

Accumulators are only reset if sendSummary() returns true. If the Notecard returns an error (queue full, I²C fault, or anything else), the per-tenant accum_wh_milli and peak_demand_cw fields stay in AppState. On the next wake the firmware retries sendSummary() with the accumulated totals, which by then include one more sample period's energy, and it keeps retrying until the Notecard acknowledges. STOP2 retains RAM, so the data survives every sleep during the retry window. Only a power cycle or reset clears it.

Template registration failure. defineTemplates() uses requestAndResponse() and returns false if the Notecard returns an error. The notecard_configured flag is set to 1 only when both initNotecard() and defineTemplates() return true. A failure leaves the flag clear, and the next wake retries the full initNotecard() + defineTemplates() sequence on its own. Since all Note emission waits on notecard_configured, no Note can be queued before the template exists. The firmware holds off on sending until setup completes and keeps accumulating energy across the retry wakes without losing any. If you suspect template registration failed after the initial deployment (after a factory reset of the Notecard, for example), this recovery procedure always works:

  1. Wait for any pending Notes to go out in an outbound sync, so queued data isn't lost.
  2. Send {"req":"card.restore","mode":"factory"} from the Notecard In-Browser Terminal or the USB serial REPL. That resets the Notecard to factory defaults, registered templates included.
  3. Power-cycle the device. AppState lives in host RAM, so the power cycle clears notecard_configured to 0 and the firmware runs the full initNotecard() + defineTemplates() sequence again.
note

card.restore with mode:"factory" also clears any Notes queued in the Notecard's local store. If you need to keep in-flight data, finish step 1 before you restore.

7.7 Key code snippet 1: template registration

Only meter_summary.qo is registered as a binary-packed template. 14.1 is the note-c type hint for a 4-byte IEEE-754 float (TFLOAT32). defineTemplates() uses requestAndResponse() and returns a bool, and notecard_configured only latches after a successful response.

bool defineTemplates(void) {
    J *req = notecard.newRequest("note.template");
    if (!req) return false;
    JAddStringToObject(req, "file", SUMMARY_NOTEFILE);
    J *body = JAddObjectToObject(req, "body");
    char key[28];
    for (int t = 1; t <= 4; t++) {
        snprintf(key, sizeof(key), "t%d_wh",       t);
        JAddNumberToObject(body, key, 14.1);  // estimated interval energy, TFLOAT32
        snprintf(key, sizeof(key), "t%d_demand_w", t);
        JAddNumberToObject(body, key, 14.1);  // peak DEMAND_INTERVAL_SEC avg demand W, TFLOAT32
    }
    // fault_mask: 4-bit nibble per tenant channel (T1 in bits 3:0, T4 in bits 15:12).
    // ...
    // TFLOAT32 represents the full uint16 range (0–65535) without loss of precision.
    JAddNumberToObject(body, "fault_mask", 14.1);
    J *rsp = notecard.requestAndResponse(req);
    bool ok = rsp && !notecard.responseError(rsp);
    if (!ok) dbgSerial.println("[init] note.template for meter_summary.qo failed — will retry");
    notecard.deleteResponse(rsp);
    return ok;
}

7.8 Key code snippet 2: sleeping until the Notecard raises ATTN

cxSleepUntilAttn() asks the Notecard to hold ATTN low for exactly cfg.sample_interval_sec seconds, confirms the pin has gone low, and puts the STM32 into STOP2. Because the host stays alive, the card.attn call is a request with a checked result, not a fire-and-forget command. When the Notecard raises ATTN, loop() moves on to the next sample. The AppState struct, with its accumulated estimated Wh, peak demand, demand-window progress, and summary epoch, is exactly as the host left it, because it never left RAM.

void setup() {
    // ...
    // Arm the ATTN wake: the Notecard raises ATTN (D5) to end each sleep.
    cxSleepBegin();
}

void loop() {
    runCycle();

    if (!cxSleepUntilAttn(notecard, cfg.sample_interval_sec, NULL, &dbgSerial)) {
        // The Notecard didn't take the sleep request, or ATTN never went low
        // (check the ATTN -> D5 jumper).  Keep the sample cadence and try again.
        dbgSerial.println("[sleep] ATTN sleep failed — waiting out the interval awake");
        delay(cfg.sample_interval_sec * 1000UL);
    }
}

8. Build and Flash

Prerequisites: Arduino IDE (or arduino-cli on the command line) with the following installed from the Boards Manager and Library Manager:

  • Boards: STM32 MCU based boards (the stm32duino/Arduino_Core_STM32 core). Add the index URL https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json under File → Preferences → Additional Boards Manager URLs if it isn't already listed.
  • Libraries: Blues Wireless Notecard (from note-arduino), STM32duino Low Power, and its dependency STM32duino RTC.

Steps:

  1. Clone or download the repo and open firmware/tenant_sub_meter/tenant_sub_meter.ino in the Arduino IDE. All four files from the firmware/tenant_sub_meter/ directory need to be in the same sketch folder.

  2. Set your Notehub ProductUID in firmware/tenant_sub_meter/tenant_sub_meter_helpers.h by putting your ProductUID (for example, com.your-company:your-app) between the empty quotes on line 21:

    #define PRODUCT_UID ""   // set to your Notehub ProductUID before flashing

    You'll find this value in Notehub under Project Settings → ProductUID, or see Finding a ProductUID.

  3. Set the DIP switch on the Notecarrier CX to HST so USB serial routes to the host MCU for programming.

  4. Via Arduino IDE: Select Tools → Board → Blues boards → Cygnet, set USB support (if available) to None, put the host in DFU mode (hold BOOT, tap RST), then click Upload. The build settings are explained in §7.1. You can also upload over SWD with an ST-LINK on the debug jack.

  5. Via command line (arduino-cli):

    # Compile (usb=none disables USB CDC, which must be off for the host to sleep)
    arduino-cli compile \
      -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
      firmware/tenant_sub_meter/
    
    # Find the port (replace below with the value reported)
    arduino-cli board list
    
    # Upload
    arduino-cli upload \
      -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
      -p /dev/cu.usbmodem* \
      firmware/tenant_sub_meter/

    On Windows the port appears as COM3, COM4, and so on. On macOS or Linux it's usually /dev/cu.usbmodem* or /dev/ttyUSB*.

  6. Open a serial monitor at 115200 baud on the ST-LINK virtual COM port (the Notecarrier CX debug jack; see §7.1) to check that the sketch boots. On first power-on you should see the Notecard configuration exchange (hub.set, template registration), followed by the first channel sample lines. After that first cycle the host enters STOP2 for sample_interval_sec and the serial output goes quiet, which is expected. The Notecard raises ATTN to wake the host for the next sample, and loop() resumes in place.

  7. Once the device appears in Notehub and the first meter_summary.qo Note arrives, flip the DIP switch back to NC for production operation.

The PRODUCT_UID is not defined message during compilation is a reminder that prints on every build, even after step 2, so it doesn't tell you whether the line is set. If upload fails with a "board not found" error, check the DIP switch position and the USB cable.

9. Data Flow

Data flow: 5-min sample of V × I cross-product → per-tenant Wh accumulation and 15-min demand block → meter_summary.qo (hourly templated) → Notehub event-query API for monthly bill-back

Collected. Every sample_interval_sec (5 minutes by default): 2000 sequential interleaved V+I ADC sample pairs per active tenant channel. The V×I cross-product gives active power (W). RMS current (A) is computed along the way and is available to callers, but the current firmware doesn't transmit it. Adding per-channel RMS current to the summary Note would be a simple future extension (see §11).

Accumulated on-device. Between summaries, the device tracks three things for each tenant: estimated Wh (the sum of W_sample × sample_interval_sec / 3600), peak 15-minute blocked-average demand in W (the highest completed DEMAND_INTERVAL_SEC window average, see §7.3), and hardware-fault flags (an OR of the FAULT_* bits across every wake in the period).

Transmitted hourly (canonical record). meter_summary.qo: one Note per summary_interval_min (24 per day by default). It's template-encoded, queued, and flushed in the Notecard's periodic outbound sync. Each Note carries t1_wh–t4_wh (estimated interval energy in Wh), t1_demand_w–t4_demand_w (peak 15-minute blocked-average demand in W), and fault_mask (the combined per-channel fault bitmask) for each tenant. The Note is not sync:true, so it waits for the periodic radio window. These Notes are the authoritative energy record and the only input to Notehub-side monthly aggregation.

Routed. Notes go through Notehub to whatever destinations the project's routes specify. A typical deployment sends meter_summary.qo to a time-series database as the main billing and analytics record. Monthly per-tenant totals come from summing t*_wh across the billing period's hourly events in Notehub, either with the Event Query API or in a downstream aggregation pipeline. Notehub stores every Note durably, so you never need a device-side aggregate to rebuild a monthly total.

10. Validation and Testing

Expected cadence in steady state. A working unit sends one meter_summary.qo event per hour. The first _session.qo should show up in Notehub within two minutes of first power-on, and the first meter_summary.qo usually arrives in that initial session, shortly after the first sample cycle finishes.

Calibrate before deployment. Every Rogowski coil and integrator pairing has its own sensitivity. Before you use the readings for billing:

  1. Put a known, stable load on one tenant circuit (for example, 10 A measured with a calibrated clamp meter).
  2. Wait one hour for a meter_summary.qo to arrive in Notehub.
  3. Calculate the expected energy: loaded_amps × hours_run × 120_V = Wh_expected. For 10 A over 1 hour at 120 V, that's ≈ 1200 Wh.
  4. Compare t*_wh in that hour's summary with the expected value. The ratio is your per-coil correction factor.
  5. Update rogowski_amps_per_volt in the Notehub Fleet Environment to (default_400 × expected_wh) / reported_wh. Push the change, and the device picks it up on its next inbound sync (every 2 hours by default).
  6. Repeat at a second load point (20 A, for example) to confirm linearity, then repeat the whole process for each active tenant channel.

If you skip calibration, readings can be off by ±10–30%, depending on the coil model and integrator tolerance.

Using Mojo to validate power behavior. The Blues Mojo coulomb counter goes inline between the 5 V supply output and the Notecarrier CX +VBAT input, and it reports cumulative mAh to the Notecard over its Qwiic connector. Here are the published current figures from the Notecard low-power design guide and the MBGLW datasheet:

PhaseExpected current
Notecard idle (between syncs, radio off)~18 µA @ 5 V (Cell+WiFi variant)
Host active (sampling four channels)~15–30 mA for < 1 second
Notecard cellular session (LTE Cat-1 bis)~0.28 mAh per warm sync; ~0.49 mAh after cold restart; depends on signal strength
Host asleep (STOP2 between wakes, until card.attn raises ATTN)~1–2 µA added to the Notecard idle figure

The hourly cellular session is the biggest energy cost. The supply is line-powered, though, so the shape of the current trace tells you more than the absolute mAh:

  • Healthy trace: a near-zero baseline, brief (<1 second) micro-spikes every 5 minutes as the host wakes and samples, and one 10–20 second burst at 80–150 mA once an hour.
  • Host not sleeping: a continuous 15–30 mA baseline. The host isn't entering STOP2, so check the ATTN→D5 jumper (see §5) and confirm the firmware was built with the usb=none board option (USB support set to None).
  • Cellular sessions running long (60+ s): hourly bursts at the right spacing, but each one runs long. That usually means a weak-signal site, so check antenna placement or move the unit closer to a window.

Troubleshooting

SymptomLikely causeWhat to check
Device never appears in the Notehub Devices tab.PRODUCT_UID empty or wrong; cellular antenna disconnected; poor cellular coverage.Double-check that PRODUCT_UID in the firmware exactly matches the Notehub project's ProductUID. Look for [notecard] hub.set failed messages on the serial monitor, which mean the Notecard didn't accept the configuration. Move the unit near a window or outside.
_session.qo appears but no meter_summary.qo after 90+ minutes.Template registration failed, or the Note reaches Notehub but no route is configured. Zero-load channels still produce events.Check the Notehub Events tab directly, since meter_summary.qo shows up there whether or not you have routes. Watch serial for [summary] note.add failed messages. If the template failed, restart the firmware with a cold power-cycle of the device.
Serial monitor shows a negative or zero W figure on a [sample] T*: line under a known load.Rogowski coil leads reversed (polarity inverted).Swap the coil's two output leads at the R_in resistor input on the integrator board and test again.
One channel reads large values and another reads zero under the same load.Coil not fully closed (gap in the flexible snap coupler), or a failed integrator component.Check the affected coil's snap coupler for a gap. Confirm that all four integrator resistor values (R_in, R_f) and capacitor (C_f) match the values in §5.
All four channels read the same non-zero value even when unloaded.Cross-talk or bias-node coupling, or not enough decoupling on the shared 3.3 V supply.Make sure each of the four current-channel bias nodes (A0–A3) has its own dedicated 10 µF electrolytic cap to GND, placed as close to the node as possible. (The voltage channel's 10 µF cap sits in series between VOUT and A4, not to GND.) Check that the shared +3V3 rail holds a steady 3.3 V under load, and use a scope to look for ripple.
t*_wh values are implausibly large or small (off by 10%+ vs. clamp meter).Calibration constant rogowski_amps_per_volt is wrong for the installed coil/integrator.Follow the calibration procedure in §10 above. Measure a known load with a calibrated clamp meter, wait for one hourly summary, and compute the correction factor.
Mojo bench trace shows continuous 15–30 mA instead of sleep pulses.Host not entering STOP2, because the ATTN→D5 jumper is missing or the firmware was built with USB CDC enabled.Confirm the jumper between ATTN and D5 on the Notecarrier CX header (see §5), and check that the firmware was built with the usb=none board option (USB support set to None). If you're adapting this to a different carrier, wire ATTN to an interrupt-capable host GPIO and set CX_ATTN_PIN before including cx_sleep.h.
Cellular sessions burst every 5 minutes instead of once an hour.hub.set didn't persist correctly, or the Notecard reverted to defaults.Watch serial for [notecard] first wake: hub.set + template registration. If it shows up on every wake, the config isn't sticking. Restart the Notecard by sending {"req":"card.restore","mode":"factory"} from the Notecard In-Browser Terminal, then power-cycle the device and reflash the firmware.

If your problem isn't on this list, post it on the Blues community forum. That's the fastest way to get a second pair of eyes on a specific Notecard + sensor bring-up issue.

11. Limitations and Next Steps

This is a reference design for proportional tenant allocation. It isn't a certified utility sub-meter, and that's on purpose. The sections below spell out where the measurement model, the bench-grade voltage transducer's lack of agency listing, and the single-phase assumption set the limits, then point to the production paths for cases that need more.

Simplified for this Proof-of-Concept

Every item here traces back to one of those three choices. Each one is fine for a bridge that splits costs proportionally, and each marks where a certified meter would have to go further.

Allocation-grade estimation, not certified metering. The design estimates interval energy by taking one ~200-millisecond active-power snapshot per sample_interval_sec and multiplying it by the full interval. It doesn't integrate power continuously between wakes. That's accurate for constant or slowly changing loads, but it can over- or under-state energy on bursting or cycling loads. For internal bill-back between tenants in the same building, it's an appropriate way to allocate. It is not suitable where accuracy falls under regulatory oversight, such as utility-tariff sub-metering held to accuracy standards, unless you replace the measurement front end with a dedicated simultaneous-sampling energy-metering IC or a certified pulse-output sub-meter interface.

Single-phase voltage reference shared across all channels. The voltage transducer supplies one voltage waveform that serves as the reference for all four current channels. That's accurate when every tenant circuit comes off the same phase leg of the building supply, which is common in small commercial buildings with single-phase 120 V service. Things change in a split-phase (120/240 V) building where tenants might be on different legs, or a three-phase building where tenant feeds come from different phases. There, the phase relationship between the shared voltage reference and a tenant's actual line voltage adds a power-factor error to the real-power calculation. Buildings with known multi-phase distribution need a dedicated voltage sensor per phase (and per-phase V×I pairs in the firmware) to meter real power accurately for every tenant.

Rogowski coil and integrator calibration. The rogowski_amps_per_volt default is a starting point based on the integrator component values and a typical coil's mutual inductance. The real value depends on the coil model, production tolerance, and board layout, so you have to calibrate against a reference clamp meter at a known load before using the readings for billing. The volt_scale default likewise assumes the ZMPT101B module's standard burden, and a different burden resistor needs a different constant. You can tune both through Notehub environment variables without a reflash.

t*_demand_w matches the 15-minute demand-charge window but energy is still estimated. The t*_demand_w field reports the peak 15-minute blocked-average demand, meaning the average watts over the highest completed 900-second window in the summary period. That lines up with the most common North American utility demand-charge billing interval. It's a useful load-profile indicator and a reasonable input for splitting internal bill-back, but the energy underneath it still comes from periodic power snapshots, not a continuous integral. For 30-minute demand tariffs, change DEMAND_INTERVAL_SEC to 1800 in the firmware and reflash.

Runtime sensor-fault detection is best-effort, not certified. Right now fault_mask covers three kinds of hardware condition. FAULT_BIAS_RANGE flags an ADC DC offset outside the expected 1.40–1.90 V half-rail window. FAULT_SATURATED flags centered-signal RMS approaching the ADC rail, tripping when RMS exceeds the equivalent of 85 % of the 1.65 V half-rail peak amplitude (≈ 0.99 V RMS at the ADC pin). FAULT_VOLTAGE_REF flags bias, saturation, or implausibly low line voltage on the shared voltage-reference path, and it's copied into every active channel's nibble at once. Low or missing current is left out of fault_mask on purpose. A legitimately unloaded tenant circuit reads the same near-zero current as a disconnected Rogowski coil, so treating it as a hard fault would quarantine valid zero-usage billing intervals. These checks can catch gross hardware faults, but they can't tell a tenant who really used nothing from a sensor that quietly failed. If a production deployment needs more rigorous fault coverage, add a baseline capture at commissioning and cross-check fault_mask against historical baselines in the downstream system.

Single-phase circuits only. Each Rogowski coil wraps one conductor. A split-phase (120/240 V) tenant feed needs two coils per tenant, and a three-phase feed needs three. This design uses A0–A4, which leaves one on-board analog input (A5) before you'd need an external analog multiplexer or ADC.

Electrical safety and code compliance. This design is a proof of concept. Panel installations in commercial buildings must follow the applicable electrical codes (NEC in the United States, CEC in Canada, and so on) and be done by licensed electricians, and some jurisdictions require a permit. Sub-metering equipment used for tenant billing may also fall under accuracy and certification requirements in local utility or metering regulations, so confirm what applies locally before you deploy.

No local display. There's no real-time readout at the panel. A small OLED on I²C, or a status LED driven by a firmware threshold check, would be easy to add.

Mojo is bench equipment. The firmware doesn't read the Mojo's coulomb-counter register over Qwiic. If fleet-level power telemetry would help you, adding a runtime mAh field to the summary Note is a simple extension.

Production Next Steps

To take the bridge to a production rollout, you'll calibrate each installed coil, replace the bench-grade voltage front end with an agency-listed transducer, and wire the hourly stream into a billing platform. These extensions get you there.

Per-channel calibration workflow comes first. At commissioning, take a reference reading alongside a calibrated clamp meter to work out the right rogowski_amps_per_volt for each installed coil, then push the trimmed value as a per-device environment variable in Notehub.

Production voltage sensing (future revision). The ZMPT101B module in this design is for bench and prototype use only. It is not agency-listed for permanent installation inside a commercial panel enclosure, so replace it with an agency-listed isolated AC voltage transducer before deploying to a live panel. Depending on the device you choose, there are two ways to make the swap:

(a) AC waveform output (for example, a Yokogawa or Verivolt panel-mount PT with a 0–3 V AC output) is a wiring-only drop-in. The existing measureChannel() waveform-RMS algorithm works unchanged. Keep the 10 µF series AC-coupling capacitor and the 100 kΩ / 100 kΩ half-rail bias divider on A4 wired exactly as they were for the ZMPT101B, and calibrate volt_scale to the new transducer's rated output sensitivity (line V RMS ÷ ADC-pin V RMS at rated input). It's the simplest production upgrade path.

(b) 4–20 mA DC output (for example, the NK Technologies VTR2-420-24L-DIN: UL Listed, 150 VAC range, 24 VDC loop-powered, DIN-rail mount) needs these additions and changes that aren't in the current BOM or firmware:

  • 24 VDC loop supply: the transducer is loop-powered and needs its own 24 VDC supply (for example, a MeanWell IRM-10-24 or an equivalent DIN-rail 24 V module). The 5 V AC/DC supply already in the BOM can't power it.
  • 150 Ω ±0.1% precision burden resistor: wire it from ADC A4 to GND. It converts the 4–20 mA loop current to 0.60–3.00 V DC, which stays within the host's 3.3 V ADC limit. Remove the 10 µF AC-coupling capacitor and the 100 kΩ / 100 kΩ half-rail bias divider from A4. The signal is DC, so it needs neither AC coupling nor a bias network.
  • Loop wiring: connect the 24 V supply (+) → transducer VIN; transducer IOUT → burden resistor (+) → ADC A4; burden resistor (−) → GND → 24 V supply (−). Check the transducer polarity against the device datasheet.
  • Firmware adaptation: update measureChannel() to drop the voltage reads from the interleaved loop (keep sampling the current waveform) and take a single DC ADC read on A4 instead. The transducer outputs true RMS, so that read gives RMS line voltage, not an instantaneous value: (adc_v - 0.60) / (3.00 - 0.60) × V_rated_full_scale (where 0.60 V corresponds to 4 mA and 3.00 V to 20 mA). Without the voltage waveform there's no V×I cross-product, so power becomes V_rms × I_rms (apparent power) and you're back to assuming a power factor to get watts. Use the derived value in place of line_v_rms in the fault checks. The voltage-path bias and saturation checks need adapting too. DC offset no longer means anything on A4, and the plausibility check should compare the derived line-voltage value directly against VOLTAGE_MIN_V_RMS. Build the 4–20 mA path as a #define-gated alternative to the existing waveform path, so you can compile and bench-test both options without changing the core algorithm.

Per-phase voltage sensing brings the design to buildings with multi-phase distribution. Replace the single voltage transducer with one per phase, and extend the firmware to pair each current channel with its own phase voltage.

Notecard Outboard DFU lets you update firmware across the whole fleet without a truck roll to each panel.

Integrate with a billing platform by setting up a Notehub HTTP route that POSTs each meter_summary.qo to the analytics and billing database. Have the downstream system reject or quarantine any meter_summary.qo where fault_mask != 0 and fall back to manual estimation for that period. Monthly totals come from querying the Notehub Event Query API and summing t*_wh for each device over the billing period.

Capture a commissioning baseline at first installation. Record each channel's ADC DC offset with all loads off and store the values in Notehub device metadata. The downstream system can then compare fault_mask flags against those baselines to tell genuine zero-load readings from sensor faults.

12. Summary

The landlord who used to split the utility bill by square footage now allocates it by actual use. Four Rogowski coils with active per-channel integrators, plus a voltage transducer, turn the panel into a metering bridge the landlord controls and the tenants never see. It samples every five minutes, computes real power on the device, and sends compact hourly summary Notes through Notehub with each tenant's estimated interval energy (Wh), 15-minute blocked-average demand (W), and per-channel fault bitmask. Monthly per-tenant totals come from querying that hourly stream in Notehub, so the device never has to compute a billing figure.

The readings are allocation-grade estimates. They're accurate for proportional charge-back inside the building but aren't utility-tariff revenue grade, and for most landlords that's the right trade-off. The alternative is either no allocation at all or a full certified sub-meter retrofit at several times the cost.

What really settles the design is the network, more than the hardware. Cellular is the only data channel that belongs entirely to the landlord and that tenants structurally can't reach. Any WiFi-based path puts billing data on a network the tenant can reach, and that's a dispute waiting to happen. The landlord-owned SIM in the Notecard heads off that dispute by design. The panel can stay dumb and the tenants unaware while the allocation data shows up every hour, without anyone having to think about it.

On This Page

  • 1. Project Overview
  • 2. System Architecture
  • 3. Technical Summary
  • 4. Hardware Requirements
    • Prototype / Bench Evaluation BOM
  • 5. Wiring and Assembly
    • Power chain
    • Active Miller integrator (replicate for each of the four current channels)
    • ZMPT101B voltage sensor module (bench/prototype — one module, building voltage reference)
    • Pin-by-pin assignments
    • ATTN-to-D5 jumper (host sleep)
    • Rogowski coil placement
  • 6. Notehub Setup
    • Fast path to first event
    • Fleets, environment variables, and routing
    • What you should see in Notehub
  • 7. Firmware Design
    • 7.1 Installing and flashing
    • 7.2 Module map
    • 7.3 Sensor reading strategy
    • 7.4 Event payload design
    • 7.5 Low-power strategy
    • 7.6 Retry and error handling
    • 7.7 Key code snippet 1: template registration
    • 7.8 Key code snippet 2: sleeping until the Notecard raises ATTN
  • 8. Build and Flash
  • 9. Data Flow
  • 10. Validation and Testing
    • Troubleshooting
  • 11. Limitations and Next Steps
    • Simplified for this Proof-of-Concept
    • Production Next Steps
  • 12. Summary

Resources

  • GitHub
  • Request More Info
Can we improve this page? Send us feedback
© 2026 Blues Inc.
© 2026 Blues Inc.
AboutDocsAPI ReferenceTermsPrivacy