Utility Distribution Transformer Load Monitor

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/transformer_load_monitor/ so it can learn how the device works and help you adapt it to your hardware.
This project is an energy monitoring solution that gives utilities real-time load visibility at the pole, which is where transformer failures actually happen. A Notecarrier CX with three split-core current transformers on the secondary terminals and an I²C temperature sensor tracks per-phase loading, catches dangerous current imbalances, and correlates load with enclosure temperature as a stand-in for thermal stress. It all runs over cellular, and the site doesn't need any infrastructure.
1. Project Overview
The problem. A distribution transformer is the last powered device between the high-voltage grid and a customer's service entrance. Utilities usually instrument their substations carefully, with real-time SCADA, demand data, and fault recorders, but the transformer hanging on the pole outside a neighborhood gets none of that. The utility knows what's flowing into the substation. It has no idea what's flowing through any individual transformer until something fails.
Not knowing gets expensive. Distribution transformers age and fail for reasons you can identify and prevent. Sustained overloading degrades the insulation with each degree of excess temperature. Phase imbalance, where uneven single-phase loads sit on a three-phase transformer, creates circulating currents and localized hot spots. High ambient temperature combined with high loading adds thermal stress on top. None of these conditions shows up suddenly. They build over hours to days, and a transformer running hot on a Tuesday will fail during Friday's peak demand. It takes the neighborhood down with it, and the emergency truck roll costs many times what a proactive replacement would have.
This project fixes that blind spot. A handful of sensors clamped to the transformer secondary, a Notecard for the uplink, and a firmware loop that checks thresholds turn an opaque pole-top asset into continuously monitored equipment. It tells the operations center what's happening in real time and pages the right people before the neighborhood goes dark.
Why Notecard. Utility poles are some of the least connected infrastructure there is. They have no Ethernet or building WiFi, and often nothing but the transformer's own secondary power, so cellular is the only practical backhaul. The Notecard Cell+WiFi variant (MBGLW) carries a prepaid SIM with 500 MB of data and 10 years of service. You don't activate each site, negotiate carrier contracts, or file an IT ticket for network access. A utility rolling these monitors out across its service territory can put a single SKU on every pole in every neighborhood, from dense urban blocks to rural feeders, and Notecard's automatic carrier selection handles the varied network conditions without firmware changes.
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.
The power side is just as simple. With cellular, the device doesn't depend on any site infrastructure at all. The Notecard Cell+WiFi idles at ~18 µA @ 5V between samples while the host MCU sleeps in STOP2 at a few µA. At a 5-minute sample and hourly transmit cadence, the radio is awake for roughly 20–30 seconds per hour, which a small AC/DC supply tapped from the transformer's own secondary can comfortably sustain.
Deployment scenario. A NEMA 4X weatherproof enclosure is zip-tied or U-bolted to the transformer pole and powered from a small AC/DC supply connected to the 120VAC secondary. Three split-core CT leads exit through weatherproof cable glands and clamp directly onto the secondary conductors.
CT installation is non-invasive. The split-core sensors clamp directly onto energized secondary conductors without breaking the circuit. The power supply connection is a different story, because tapping the 120VAC secondary is live utility electrical work. Depending on utility policy and local jurisdiction, this step may need a scheduled outage, a qualified hot-work procedure, or written utility approval before the tap is energized (see §11 Limitations). You don't need an invasive tap into the transformer case or any on-site network provisioning. Once all the necessary work authorizations are in hand, a two-person crew that includes an electrician qualified for live secondary work can install and commission one unit in under an hour.
Power supply scope. This reference design assumes an accessible 120VAC secondary tap, which is the standard case for North American single-phase distribution transformers installed by a qualified crew. Three-phase padmount transformers, sealed primary-side enclosures, and installations where utility rules prohibit tapping the secondary need a different power arrangement. §11 Limitations covers the inductive harvesting alternative.
2. System Architecture
Device-side responsibilities. The host on the pole has a simple job that
repeats. Every 5 minutes (configurable), the Notecarrier CX's STM32L433 host
wakes and reads the configured CT channels. That's two for the common
split-phase install, or three when phase_count=3 is set for three-phase. It
also reads the I²C temperature sensor inside the enclosure, runs three threshold
checks in firmware, and queues the results as
Notes through the Notecard over I²C. As soon as
that's done, it goes back to sleep. Between wakes the host sits in STM32 STOP2
(a few µA) until the Notecard's ATTN pin wakes it at the end of a
card.attn sleep. STOP2
retains RAM, so all the state that has to survive the gap (summary
accumulators, alert cooldown counters, elapsed time) just stays in memory, and
nothing gets serialized to the Notecard.
Notecard responsibilities. The Notecard takes everything the host just
queued and decides when it goes over the air. Notes wait in its on-device queue
and flush on the hub.set
outbound cadence (60 minutes by default). Any Note flagged sync:true, which
includes every alert, skips that timer and ships right away. The Notecard also
distributes environment
variables
from the Blues Notehub cloud service, so an
operations engineer can retune thresholds across the fleet without re-flashing
firmware. The device picks up the changes on its next inbound sync. GNSS is
available on supported hardware if a future revision needs one-time asset
location, but this design doesn't use it, since turning it on has antenna,
power, and cost implications not covered here.
Notehub responsibilities. The Notecard's embedded global SIM carries each
Note from the pole over supported carriers worldwide to
Notehub, which ingests, stores, and routes the events
downstream. Everything the device says goes into two Notefiles:
xfmr_summary.qo for hourly templated rollups suited to long-term trending, and
xfmr_alert.qo for event-triggered Notes that need immediate delivery. Grouping
devices into Fleets by service
territory or transformer rating lets engineers set threshold environment
variables
for a whole group (all the 25-kVA residential units on one feeder can share one
threshold profile) and still override individual devices for unusual
transformers. Smart Fleet
rules can automatically
assign a Notecard to the right fleet based on its reported data.
Routing to the cloud (high level only). Notehub can route to HTTP, MQTT, AWS IoT Core, Azure IoT Hub, GCP Pub/Sub, 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). Alert and summary Notefiles are kept separate on purpose so each can be routed on its own. Alerts go to an outage-management or on-call system with low-latency delivery, and summaries go to a historian or analytics store with higher-volume, batched ingest.
3. Technical Summary
What you need:
- Notecarrier CX + Notecard Cell+WiFi (MBGLW)
- 3× YHDC SCT-013-000 CTs + 3.5 mm jack breakouts
- Per-CT burden circuit: 22 Ω, 2× 10 kΩ, 10 µF cap (see §5)
- MCP9808 I²C temperature sensor
- 5V/2A AC/DC supply (e.g., MeanWell IRM-10-5)
- Computer with Arduino IDE or
arduino-cli
Bench validation (no live transformer needed):
- Assemble the three CT bias circuits on A0, A1, A2 (see the §5 wiring
diagram), and fit the
ATTN→D5jumper for host sleep. - Connect the MCP9808 to I²C (SDA/SCL).
- Flash the firmware. Set
PRODUCT_UIDintransformer_load_monitor.inoto your Notehub project ID, then upload with the board optionsSTMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none(see §7.1). - Open a serial monitor at 115200 baud on the ST-LINK virtual COM port from the
Notecarrier CX debug jack. You should see
[sample] i_a=...lines every 5 minutes. - Create a project in Notehub if you haven't already. The Notecard associates with it automatically on its first cellular connection.
- After 60 minutes you'll see
[summary] queuedon serial. Wait for the device's next outbound sync (hourly by default), then check Notehub Devices → Events for anxfmr_summary.qoentry. - Clamp a CT on a known 120VAC load (a lamp works well) and check that the current value in the summary matches the expected load (within ~5%).
Here is a sample Note this device emits:
{
"file": "xfmr_alert.qo",
"body": {
"alert": "phase_imbalance",
"i_a_rms": 91.4,
"i_b_rms": 34.2,
"i_c_rms": 88.7,
"temp_c": 44.1,
"extra": 62.6
}
}4. Hardware Requirements
| Part | Qty | Rationale |
|---|---|---|
| Notecarrier CX | 1 | Integrated carrier with an onboard STM32L433 host. It has three ADC-capable analog pins for the CT channels, I²C for the temperature sensor, and the Notecard's ATTN pin to wake the host from STOP2 deep sleep, so you don't need a separate MCU. |
| Notecard Cell+WiFi (MBGLW) (datasheet) | 1 | With cellular, no pole depends on IT. One SKU with a prepaid global SIM covers urban and rural feeders without carrier contracts or per-site activation. |
| Blues Mojo (bench / commissioning only, optional) | 1 | Coulomb counter for checking the power budget on the bench. It doesn't ship with the pole-mount unit and stays on the bench during commissioning. It's optional, but it lets you confirm the device hits its expected current draw before field deployment. See §9 for the current trace to look for. |
| YHDC SCT-013-000 split-core CT, 100A / 50mA | 3 | Non-invasive, clip-on current-output CT. The 100A / 50mA variant covers residential distribution transformers up to about 20 kVA at 240V secondary (~83A full-load). For transformers at or above 25 kVA at 240V (~104A at full load), use a higher-range current-output CT (see the note below). It connects directly to the resistor bias circuit with no integrator stage, and it's widely available from Mouser and Amazon. |
| 22 Ω 1% resistor (burden, 1 per CT) | 3 | Converts the 50mA CT output to ~1.1V RMS at 100A, which sits well inside the host ADC's 0–3.3V input range after biasing. |
| 10 kΩ 1% resistor (bias divider, 2 per CT) | 6 | Creates the Vcc/2 (~1.65V) DC bias point that centers the AC CT signal within the ADC input range. |
| 10 µF electrolytic capacitor (1 per CT) | 3 | AC-couples the CT signal to the bias node and keeps DC from the bias network from leaking back into the CT. |
| 3.5mm TRRS audio jack breakout (1 per CT) | 3 | The SCT-013-000 ends in a 3.5mm plug, and a breakout board saves you from cutting the cable. |
| Adafruit MCP9808 High Accuracy I2C Temperature Sensor Breakout #1782 | 1 | ±0.25°C accuracy over –40°C to +125°C, an I²C bus, and a 2.7–5.5V supply make it a good fit for enclosure thermal monitoring. It doesn't use up any analog pins. |
| AC/DC supply, 5V/2A (e.g. MeanWell IRM-10-5) | 1 | Compact PCB-mount supply that makes 5V DC from the transformer's 120VAC secondary. The IRM-10-5 (10W, 2A continuous) has enough rating to sustain cellular sessions that can peak at ~2A. The smaller IRM-05-5 (1A) isn't enough for reliable cellular operation. |
| 2A 250V slow-blow fuse, 5×20 mm (e.g. Littelfuse 218002) | 1 | Protects the 120VAC tap to the IRM-10-5 supply. It's required on the Line leg, installed in a panel-mount or PCB fuse holder rated for 250VAC. |
| 5×20 mm fuse holder, 250VAC rated | 1 | Mounts the slow-blow fuse on the Line conductor inside the enclosure. |
| Cellular stub antenna (bench / commissioning only, included with Notecarrier CX dev kit) | 1 | Fine for bench bring-up and indoor testing, but not suitable for permanent outdoor pole-mount deployment. |
| Outdoor cellular antenna, 700–2700 MHz, SMA (deployment), e.g., SparkFun CEL-16432 | 1 | Replaces the bench stub on installed units. Choose a part rated for the outdoor temperature range and UV exposure. See §5 for placement guidance, which depends on the enclosure. |
| u.FL-to-SMA bulkhead pigtail, ≈100 mm, e.g., Taoglas CA-05250003 or equivalent | 1 | Connects the Notecard's u.FL antenna port to the externally mounted SMA antenna. It's required for every pole-mount deployment that uses the outdoor SMA antenna, and it doubles as the bulkhead lead through the wall of a metal enclosure. |
| NEMA 4X enclosure, ~6×4×2″ | 1 | Weatherproof housing for outdoor pole-mount. Fiberglass or polycarbonate variants are lighter than steel and don't attenuate the cellular antenna. |
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.
CT current range note. The SCT-013-000 rated at 100A suits transformers up
to about 20 kVA at 240V secondary (~83A full-load, with ~17% headroom to the
CT's 100A rated output). A common 25 kVA unit draws ~104A at full load, 4% above
the CT's rating, so use a higher-range current-output CT for transformers at or
above 25 kVA. Larger transformers (50–167 kVA) draw about 210–700A at 240V and
need a higher-range current-output CT rated above that full-load current.
The YHDC SCT-036-200 (200A/50mA) is one example of the type, though at 200A it
tops out just under 50 kVA. Update CT_TURNS_RATIO and CT_BURDEN_OHMS in the
firmware to match.
Voltage-output CTs (such as the YHDC SCT-019-200, which produces 0.333V
full-scale from an internal burden) are not drop-in replacements. They
don't work with the external bias circuit above and need a different analog
front end. See §11 Limitations for the
production path.
POC protection scope. The BOM above gives the 120VAC mains entry primary overcurrent protection (the slow-blow fuse) and nothing else. That's adequate starting hardware for a bench prototype or a controlled field trial, but it is not a complete protection story for a permanent outdoor pole-mount installation. A production design must add: a surge protective device (SPD) rated for the installation's overvoltage category (Category C / lightning-level, per IEC 61643-11 or UL 1449) on the Line/Neutral entry; enclosure grounding and bonding to the pole ground system per applicable NEC sections and utility rules; verified creepage and clearance in all mains-voltage wiring and connectors; UV and temperature qualification of all external cabling and the enclosure; and written utility approval before the secondary tap is permanently energized. Picking components for all of that depends on the installation and is outside this POC's scope. §5 and §11 discuss it further.
5. Wiring and Assembly
All host I/O lands on the Notecarrier CX dual 16-pin header, and the Notecard Cell+WiFi (MBGLW) seats in the carrier's M.2 slot. If you use the Mojo for bench validation, it sits inline between the 5V supply and the Notecarrier's +VBAT pad. Connect its Qwiic cable to any free Qwiic port on the Notecarrier so it can log charge to the Notecard. The transformer firmware doesn't poll the Mojo. Its coulomb-count readings show up as separate Notecard events in Notehub, and it's only a bench commissioning tool, so it isn't installed in the deployed unit.
Bias circuit (build once per CT channel, identical for A, B, and C)
Each CT channel needs a small passive network that turns the CT's AC current into a voltage centered at Vcc/2, so the signal sits well inside the host ADC's 0–3.3V input range.
(per channel — repeat for A0, A1, A2)
+3V3
│
R1 10 kΩ ← top half of bias divider
│
├─────────────────── ADC pin (A0 / A1 / A2)
│ │
R2 10 kΩ ← C 10 µF (+ plate toward ADC pin)
│ │
GND CT+ (TRRS tip)
│
Rb 22 Ω ← burden resistor
│
CT– (TRRS sleeve) ──── GNDHow it works:
- Burden (Rb, 22 Ω): The SCT-013-000 is a current-output CT, and Rb converts its 50 mA RMS rated output to about 1.1 V RMS (≈1.56 V peak swing) at 100 A RMS primary current. Rb is wired directly across the CT output terminals (tip to sleeve).
- Bias divider (R1 + R2, 10 kΩ each): Splits +3.3V to set a DC bias of ≈1.65V (Vcc/2) at the ADC pin, which centers the AC signal in the ADC's single-supply input range.
- Coupling capacitor (C, 10 µF electrolytic): AC-couples CT+ to the ADC pin. The positive plate faces the ADC pin (higher DC potential, ≈1.65 V), and the negative plate faces CT+ (≈0 V DC). C passes the AC signal to the ADC pin and keeps DC from disturbing the bias point.
- Each CT channel needs its own R1, R2, C, and Rb. Don't share bias nodes or burden resistors across channels.
Pin-by-pin connections
- +3V3 → R1 top leg of each bias circuit; MCP9808
VDD. - GND → R2 bottom leg of each bias circuit; CT– (TRRS sleeve) of each jack;
MCP9808
GND. - A0 → bias-divider mid-point (junction of R1 and R2) and positive plate of C for the Phase-A channel. The negative plate of C connects to CT+ (TRRS tip). CT+ also connects to one leg of Rb (22 Ω), and the other leg of Rb connects to GND / CT–.
- A1 → same circuit, Phase-B CT.
- A2 → same circuit, Phase-C CT. For split-phase installations (L1/L2 on
A0/A1), leave A2 unconnected. The firmware defaults to
phase_count=2, so you don't need to change any env var, and the unconnected channel is never sampled. For three-phase installations, wire this bias circuit and setphase_count=3in Notehub before first power-on (see §6). Without that setting, A2 is never read and the imbalance calculations are incomplete. - SDA → MCP9808
SDA(the Notecarrier CX has on-board 4.7 kΩ pull-ups on the I²C bus). - SCL → MCP9808
SCL. - ATTN → D5 jumper (host sleep). Fit a short jumper between the ATTN and
D5 pins, which sit on the same 16-pin header of the Notecarrier CX
(
ATTNat position 3,D5at position 14). The Notecard pullsATTNlow while the host sleeps and raises it when the next sample is due, and the rising edge onD5wakes the host from STOP2. Leave the Notecarrier CX EN pin unconnected. On the CX it enables the shared 3.3 VVIOrail (host, Qwiic,3V3_OUT, and Notecard I/O), so driving it fromATTNwould brown out the whole board instead of putting the host to sleep. See Putting a Host to Sleep Between Sensor Readings for the full pattern. - +VBAT → Mojo
LOADoutput (bench only); MojoBATinput → 5V from the IRM-10-5 supply. - IRM-10-5 LINE/NEUTRAL → 120VAC tapped from the transformer secondary. The Line conductor must pass through a 2A 250V slow-blow fuse (in a rated fuse holder) before reaching the supply.
Cellular antenna
Connect the cellular antenna to the u.FL port on the front edge of the Notecard Cell+WiFi (MBGLW). The Blues development kit includes a compatible stub antenna, but for permanent pole-mount installations, a dedicated LTE antenna rated for outdoor use is preferred.
Placement guidelines:
- Polycarbonate or fiberglass NEMA 4X enclosure: the antenna can mount inside the enclosure on a standoff or against the lid. RF passes through these materials with negligible loss.
- Steel or aluminum enclosure: run a u.FL-to-SMA pigtail to a bulkhead-mount SMA antenna on the outside wall of the enclosure. Steel fully blocks the cellular signal from the inside.
- Orient the antenna element vertically (parallel to the pole) for the best cellular radiation pattern.
- Route the antenna cable away from the transformer core, primary conductors, and the 120VAC supply wiring so noise doesn't couple into the RF path.
3.5mm TRRS jack wiring
The SCT-013-000 uses a 3.5mm plug with tip = CT+ and sleeve = CT–. Wire the breakout board like this:
- Tip → one end of the 22Ω burden (Rb) and to the AC coupling capacitor top plate
- Sleeve → GND (and the other end of Rb)
CT installation
Clamp each CT around a single secondary conductor. Never put one around two conductors at once, because the fields would cancel. For a single-phase (120/240V center-tap) transformer, clamp the Phase-A CT on the X1 secondary conductor (L1 hot leg) and the Phase-B CT on the X3 secondary conductor (L2 hot leg). X2 is the center-tap neutral. Don't monitor X2 unless you want neutral current as a deliberate extra metric. Clamping one CT on X1 and the other on X2 would monitor one hot leg and the neutral, not the L1/L2 pair. For a three-phase transformer, clamp each CT on one secondary phase conductor.
You can leave the third CT slot (A2/Phase-C) unconnected for split-phase
installations. The firmware defaults to phase_count=2, so no env var change is
required. For three-phase installations, clamp the Phase-C CT on the third
secondary conductor and set phase_count=3 in Notehub before first power-on.
Without that setting, A2 is never sampled, and any C-phase overload or imbalance
contribution is invisible to the firmware.
CT open-circuit hazard. The SCT-013-000 is a current-output CT. Never disconnect the 22Ω burden resistor or unplug a CT jack while the CT is clamped on an energized conductor. An open-circuited current-output CT can develop dangerously high voltages across its terminals. Before you clamp the CT onto a live conductor, make sure the burden resistor is in-circuit. And before you disturb the burden wiring or disconnect the 3.5mm jack, take the CT off the conductor.
Safety. Distribution transformer secondary conductors carry lethal voltages. Installation must be done by qualified electrical workers wearing appropriate PPE, following utility safety procedures and applicable codes. This firmware is read-only and never switches the transformer or its connected loads. The CTs are non-invasive and clamp on without breaking the circuit, but working near energized conductors is still hazardous.
Surge and transient protection (POC omission). The 2A slow-blow fuse protects the IRM-10-5 supply against a sustained short, but it doesn't suppress the lightning-induced transients and switching surges that routinely show up on distribution transformer secondaries. That's acceptable for bench bring-up. For any unit meant for permanent pole-mount deployment, install a surge protective device (SPD) rated for the site's overvoltage category (Category C / lightning-level, per IEC 61643-11 or UL 1449) across Line/Neutral ahead of the fuse holder. Before energizing the tap, bond the enclosure to the pole grounding system and verify conductor routing, creepage/clearance distances, and any utility-specific installation approval requirements. See §11 for the full list.
6. Notehub Setup
-
Create a project. Sign up at notehub.io and create a project. Copy the ProductUID (format:
com.your-company.your-name:xfmr-monitor) and set it asPRODUCT_UIDin the firmware before flashing. -
Claim the device. Power up the assembled unit. The Notecard associates itself with your project on its first cellular connection, so there's no manual claim step, and the device appears in the Devices tab within a minute or two.
-
Create a Fleet per service territory. Fleets group devices for shared configuration. The natural unit is one fleet per transformer class or service territory. All the 25-kVA residential transformers on a feeder get the same rated-amps threshold, and the 50-kVA commercial units get a different one. Fleet-level environment variables reach every device in the fleet without re-flashing.
-
Set environment variables. In Notehub, go to Fleet → Environment (or Device → Environment for a per-unit override). Every variable is optional for split-phase (L1/L2) installations, because the firmware defaults are set for that common case. Three-phase installations must set
phase_count=3and force an inbound sync before live monitoring begins. With the defaultphase_count=2on a three-phase transformer, the A2/Phase-C channel is never read, and any C-phase overload or imbalance contribution is invisible to the firmware. The device picks up every other env var change on its next inbound sync (every 2 hours by default), with no truck roll.Variable Default Purpose sample_interval_sec300Seconds between sensor readings (minimum 30). summary_interval_min60Minutes between summary Notes. Changing this also re-applies hub.setto match the Notecard's outbound cadence.rated_amps100.0Transformer secondary rated amps per phase. The default matches the 100A SCT-013-000 CT range. Set it to the nameplate-derived secondary current (kVA rating ÷ secondary voltage), e.g., 83for a 20-kVA / 240V unit or62for a 15-kVA / 240V unit. A 25-kVA / 240V transformer draws ~104A at full load, which is more than the default CT's 100A rating. Before deploying on transformers at or above 25 kVA, swap in a higher-range current-output CT and set this value to the transformer's actual nameplate current.overload_pct95.0Any single phase exceeding this percentage of rated_ampstriggers anoverloadalert.imbalance_pct_thresh20.0(max_phase − min_phase) / max_phase above this percentage triggers a phase_imbalancealert. Only evaluated when at least one phase is carrying >2A.temp_alert_c70.0Enclosure temperature (°C) above which a high_tempalert fires. A typical enclosure's internal temperature limit is 50–65°C, and 70°C is a conservative field alert.alert_cooldown_sec1800Minimum seconds between successive alerts of the same type (default 30 minutes). Keeps a slowly drifting condition from setting off an alert storm. phase_count2Number of active CT channels (1–3). The default 2matches the common split-phase (L1/L2 on A0/A1) installation, so it works out of the box with no env var change. Set to3for three-phase transformers (A0/A1/A2 all wired) before first power-on, or A2 is never read. Set to1for single-leg monitoring. Imbalance only means something across two or more phases, so withphase_count=1,imbalance_pctis always reported as 0.0 in both real-time alerts and summary Notes. Withphase_count=2, L1 and L2 are compared without the unconnected C channel (always 0A) inflating the imbalance reading. -
Configure routes. Add one route for
xfmr_alert.qo(to an outage-management system, on-call paging, or CMMS endpoint) and a second forxfmr_summary.qo(to a long-term store or analytics platform). Because the two Notefiles are separate, each can go to its own destination at its own urgency, and the routes don't need any filter logic.
What you should see in Notehub
Note: Template fields use numeric type hints to encode type and size, where
14.1 = 4-byte IEEE-754 float and 12 = 2-byte signed integer. Notehub decodes
this compact binary format automatically.
_session.qo: Notecard session housekeeping on each cellular connect. If you see it, the radio link is healthy.xfmr_summary.qo: one persummary_interval_min. Here's an annotated example payload from a three-phase install (phase_count=3, defaultrated_amps). The comments are only for explanation, and the actual JSON has none:{ "i_a_rms": 47.3, // phase-A mean RMS over loaded sample intervals only (A) — see §11 "i_b_rms": 44.8, // phase-B mean RMS over loaded sample intervals (A) "i_c_rms": 49.1, // phase-C mean RMS over loaded sample intervals (A) "i_total": 141.2, // sum of per-phase loaded-interval means (A) "loading_pct": 47.1, // i_total / (rated_amps × phase_count) × 100; excludes idle intervals "imbalance_pct": 8.8, // (max_phase − min_phase) / max_phase × 100; from loaded-interval means "temp_c": 38.5, // mean enclosure temperature over valid temperature readings (°C) "overloads": 0, // sample intervals during which any phase exceeded the overload threshold "samples": 12, // loaded sample intervals (at least one phase above the noise floor) "total_wakes": 12 // all host wakes in the window (loaded + idle) }overloadscounts sample intervals in which any phase exceeded the overload threshold, not distinct overload events. One sustained overload that lasts the whole summary window would reportoverloads: 12at the default 5-minute sample rate.samplescounts only intervals where at least one phase was above the 0.5 A noise floor, whiletotal_wakescounts every host wake regardless of load. For time-weighted utilization analytics, multiplyloading_pctbysamples / total_wakesto account for idle intervals (see §11).xfmr_alert.qo: emitted only when a threshold trips, and sent immediately. Thealertfield isoverload,phase_imbalance, orhigh_temp.
7. Firmware Design
The firmware lives in the sketch directory
firmware/transformer_load_monitor/
and is split across four files that must be compiled together:
| File | Role |
|---|---|
transformer_load_monitor.ino | Main sketch: setup(), loop(), global declarations |
transformer_load_monitor_helpers.h | Shared constants, structs, and function prototypes |
transformer_load_monitor_helpers.cpp | All helper function implementations |
cx_sleep.h | Host sleep: card.attn request, STOP2 entry, ATTN wake |
The Arduino IDE and arduino-cli automatically compile every .ino, .h, and
.cpp file in a sketch folder together, so always open or target the
folder (firmware/transformer_load_monitor/), not an individual source
file.
7.1 Installing and flashing
Dependencies:
- Arduino core for STM32
(
stm32duino/Arduino_Core_STM32). Add the board index URLhttps://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.jsonunder File → Preferences → Additional Boards Manager URLs, then install "STM32 MCU based boards." The Notecarrier CX's host uses the Cygnet board definition (see the build settings below). Blues Wireless Notecardlibrary (note-arduino). Install it from the Arduino Library Manager (search for "Blues Wireless Notecard") or witharduino-cli lib install "Blues Wireless Notecard".Adafruit MCP9808 Library, from the Arduino Library Manager (search for "MCP9808"). It depends on Adafruit BusIO, which the Library Manager installs for you.STM32duino Low Powerand its dependencySTM32duino RTC(stm32duino/STM32LowPower). Install them from the Library Manager or witharduino-cli lib install "STM32duino Low Power" "STM32duino RTC". They provide the STOP2 entry andATTN-pin wake used incx_sleep.h.
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 (or the Notecarrier CX USB-C switch is in
NC), 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: Use File → Open… and select the folder
firmware/transformer_load_monitor/, or double-click
transformer_load_monitor.ino and the IDE loads all four files from the folder
automatically. Apply the build settings above and click Upload. With the
Notecarrier CX's USB-C 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:
# Install the STM32 core and libraries first if not already done
arduino-cli core install STMicroelectronics:stm32
arduino-cli lib install "Blues Wireless Notecard" "Adafruit MCP9808 Library" "STM32duino Low Power" "STM32duino RTC"
# Compile and upload — target the sketch folder so all four source files
# are included (/dev/cu.usbmodem* for Mac, /dev/ttyACM* for Linux).
# usb=none disables the USB CDC stack, which must be off for the host to sleep.
arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none firmware/transformer_load_monitor
arduino-cli upload -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none -p /dev/cu.usbmodem* firmware/transformer_load_monitorfirmware/transformer_load_monitor/sketch.yaml sets the same board options, so
running arduino-cli compile in that directory without -b picks them up too.
After flashing, open a serial monitor at 115200 baud on the ST-LINK virtual
COM port. On first boot you should see [sample] i_a=... lines every
sample_interval_sec seconds (5 minutes by default). If no CT is connected,
then after summary_interval_min minutes (60 by default) you'll see
[summary] queued (0 loaded / 12 total wakes). At that point the summary Note
is queued in the Notecard's local on-device store, and it reaches Notehub on the
next outbound cellular sync (60 minutes later by default). The summary always
goes out as a liveness heartbeat, even with no load. Once the CTs are clamped on
live conductors, the sample lines show non-zero current values and the summary
line reports how many intervals were loaded out of the total wakes in that
window.
7.2 Firmware constants and environment variables
transformer_load_monitor_helpers.h defines the main tuning constants:
| Constant | Default | Meaning |
|---|---|---|
CT_SCALE | (3.3/4096) × (2000/22) | Converts raw ADC RMS counts to primary amps. 3.3V over 4096 counts is the volts per count of the 12-bit ADC, and 2000/22 is the CT turns ratio divided by the 22 Ω burden. The firmware computes it as (supply_v / adc_counts) × (CT_TURNS_RATIO / CT_BURDEN_OHMS), so for a higher-range CT you change those two constants and CT_SCALE follows. |
CT_TURNS_RATIO | 2000 | Nameplate ratio (primary amps / secondary amps). For the SCT-013-000 (100A / 50mA) it's 2000. Higher-range CTs change this. A 200A / 50mA CT such as the SCT-036-200, for example, has a ratio of 4000. |
CT_BURDEN_OHMS | 22 | Burden resistor value. Only change it if you substitute a different burden. CT_SCALE is derived from it, so it updates automatically. |
CT_NOISE_FLOOR_A | 0.5 | Current values below this (in amps) are zeroed to suppress noise-floor pickup. Raise it to 1.0 if you see non-zero readings with no load. |
CT_SAMPLE_PERIOD_US | 225 | Pacing interval (microseconds) between ADC samples. 225 microseconds gives ~4.44 kHz sampling, and the ~333-millisecond RMS window covers ~20 mains cycles at 60 Hz. Don't change it unless you understand the mains-cycle alignment issue described in §7.4. |
7.3 Modules
| Responsibility | Function |
|---|---|
Notecard configuration (hub.set, note.template, card.motion.mode) | hubConfigure, defineTemplates |
| Environment variable fetch + clamp | fetchEnvOverrides (batch env.get, inline JGetString + atol/atof parsing, range clamps) |
Re-apply hub.set on summary-interval change | fetchEnvOverrides (compares against state.lastAppliedSummaryMin) |
| CT RMS measurement (two-pass ADC sampling) | readCtRms |
| Temperature reading via MCP9808 | readTemperatureC |
| Alert threshold evaluation | checkAlerts |
Alert Note emission (sync:true) | sendAlert |
| Hourly summary Note emission | sendSummary |
Host sleep (STOP2 until the Notecard raises ATTN) | cxSleepBegin, cxSleepUntilAttn (cx_sleep.h) |
7.4 Sensor reading strategy
CTs. Each SCT-013-000 produces an AC current signal proportional to the line
current flowing through its core. The external 22Ω burden resistor turns that
into an AC voltage of about 1.1 V RMS (≈1.56 V peak) at 100 A RMS primary
current, centered on the ADC's DC bias point (~1.65V = Vcc/2). The firmware
uses a two-pass algorithm. First, 256 samples establish the actual DC offset,
which may drift a little from Vcc/2 because of component tolerance. Then 1480
samples compute the RMS of the centered signal. Both passes pace samples to a
fixed 225-microsecond period (≈4.44 kHz) using micros() and
delayMicroseconds(), so the 1480-sample RMS window always spans ~333
milliseconds. That's about 20 mains cycles at 60 Hz, or ~16.7 cycles at 50 Hz.
Without the pacing, the host ADC's native single-digit-µs throughput would
finish the burst in well under one full mains cycle, and the RMS values would
drift with whichever piece of the waveform happened to get captured.
The three channels are read one after another, so total active measurement time
is about 1 second. The scale factor CT_SCALE = (3.3/4096) × (2000/22) converts
ADC RMS counts directly to primary amps, and readings below 0.5 A are zeroed to
suppress noise-floor pickup.
Temperature. On every wake, the firmware initializes the Adafruit MCP9808 (a
cheap, idempotent begin() that doubles as a presence check), sets it to
0.0625°C resolution, reads it once, and puts it in shutdown mode before the host
sleeps. A missing or unresponsive sensor returns –999.0°C. A separate
valid_temp_samples counter only increments when the reading passes the –40°C
to +125°C sanity range, and the summary average divides sum_temp_c by that
counter instead of by total wake cycles. If a window has no valid temperature
readings at all, the summary reports –999.0°C rather than an average that's
biased low. The MCP9808 and the Notecard share the Notecarrier CX's on-board
I²C pull-ups on the same bus, so you don't need external resistors.
7.5 Event payload design
The device uses one
template-backed
Notefile for summaries (xfmr_summary.qo) and one untemplated Notefile for
alerts (xfmr_alert.qo). Templates give up some flexibility for efficiency. The
Notecard stores fixed-length binary records internally, and the wire payload is
~3–5× smaller than free-form JSON, which matters for a fleet that may transmit
hourly summaries for a decade.
Sample alert (phase imbalance event):
{
"file": "xfmr_alert.qo",
"body": {
"alert": "phase_imbalance",
"i_a_rms": 91.4,
"i_b_rms": 34.2,
"i_c_rms": 88.7,
"temp_c": 44.1,
"extra": 62.6
}
}The extra field carries context that depends on the alert type. For
overload it's the loading percentage of the worst phase, for phase_imbalance
it's the computed imbalance percentage (max−min)/max, and for high_temp it's
the total current across all phases at the time of the alert.
7.6 Low-power strategy
The device is grid-tied (a 120VAC tap on the transformer secondary), so the energy budget isn't a hard constraint, but the sleep pattern still matters. Keeping the host in STOP2 between samples gets rid of a continuous 50–80mA baseline, cuts heat buildup in the enclosure, and makes the power profile easy to read on a Mojo trace during bench validation.
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 host 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 (accumulators, cooldown counters, pending alerts, cached
config) just lives in RAM. Nothing is serialized to the Notecard, and setup()
runs only once per power-up. For the firmware author, sleeping is a single
function call, and the Notecard keeps the schedule. If the sleep request fails
or ATTN never goes low (say the jumper is missing), 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 Cell+WiFi itself idles at ~18 µA @
5V. Alert Notes with
sync:true skip the outbound queue and wake the radio right away, while summary
Notes collect in the on-device queue and go out together once an hour.
7.7 Retry and error handling
hub_configuredandtemplate_configuredare set only when the Notecard confirms the power-uphub.setandnote.templaterequests, andloop()retries anything still unconfirmed on every wake.cxSleepUntilAttn()returnsfalseif the Notecard didn't accept thecard.attnrequest, or ifATTNnever went low because theATTN→D5jumper is missing. In that caseloop()waits outsample_interval_secawake withdelay()and tries again on the next cycle. Sampling continues at the normal cadence, just at a much higher power cost.fetchEnvOverridesreads every variable in one batchenv.get. If that request fails, the firmware keeps its last good config (or the defaults on first boot), and any variable that's missing or empty falls back to its default. The firmware then clamps config values to sane ranges (e.g.,sample_interval_sechas a floor of 30 seconds), so a misconfigured env var can't cause erratic behavior.- CT readings below the 0.5 A noise floor are zeroed. Sample intervals where no
phase clears the noise floor are left out of current accumulation through the
valid_samplescounter, which makes the per-phase RMS averages loaded-interval averages rather than true time-weighted window averages. Thetotal_wakesfield in the summary payload records every host wake (loaded + idle), so dividingsamplesbytotal_wakesgives the fraction of the window that carried detectable load. That's useful for utilization weighting (see §11). A summary window with zero valid CT samples still emits anxfmr_summary.qoNote with zero current fields andsamples: 0, which confirms the device is alive even when the CTs detect no load. - Each alert type has its own cooldown counter (6 cycles by default, which is 30 minutes at the 5-min sample rate), so a sustained overload doesn't page the operations center every 5 minutes. The alert types arm and disarm independently.
fetchEnvOverridesupdates thehub.setoutbound period wheneversummary_interval_mindiffers from the cadence last applied to the Notecard, which keeps the Notecard's transmit timer in step with the firmware's summary window. A failed update is retried on the next wake.
7.8 Key code snippet 1: Note template definition
Template fields use numeric type
hints:
14.1 means a 4-byte IEEE-754 float, and 12 means a 2-byte signed integer.
NOTEFILE_SUMMARY is "xfmr_summary.qo", defined in
transformer_load_monitor_helpers.h.
bool defineTemplates() {
J *req = notecard.newRequest("note.template");
JAddStringToObject(req, "file", NOTEFILE_SUMMARY);
JAddNumberToObject(req, "port", 50);
J *body = JAddObjectToObject(req, "body");
JAddNumberToObject(body, "i_a_rms", 14.1); // Phase-A RMS (A)
JAddNumberToObject(body, "i_b_rms", 14.1); // Phase-B RMS (A)
JAddNumberToObject(body, "i_c_rms", 14.1); // Phase-C RMS (A)
JAddNumberToObject(body, "i_total", 14.1); // Sum of phase currents (A)
JAddNumberToObject(body, "loading_pct", 14.1); // % of transformer nameplate
JAddNumberToObject(body, "imbalance_pct", 14.1); // Phase imbalance %
JAddNumberToObject(body, "temp_c", 14.1); // Enclosure temperature (°C)
JAddNumberToObject(body, "overloads", 12); // Overload intervals in window
JAddNumberToObject(body, "samples", 12); // Loaded sample intervals
JAddNumberToObject(body, "total_wakes", 12); // All host wakes in window
bool ok = notecard.sendRequest(req);
if (!ok) {
debugSerial.println("[template] note.template (summary) failed; will retry on next wake");
}
return ok;
}7.9 Key code snippet 2: CT RMS two-pass algorithm
Pass 1 finds the actual DC bias offset. It should be ~2048 counts for a well-built circuit, and measuring it accounts for component tolerance. Pass 2 computes the RMS of the centered AC signal with float accumulation, which avoids 32-bit integer overflow at full-scale input. Both passes pace samples to a fixed 225-microsecond period, so the RMS burst always spans ~333 milliseconds (≈20 mains cycles at 60 Hz) no matter how fast the host ADC natively runs. §7.4 explains why that matters.
float readCtRms(uint8_t pin) {
// Pass 1: DC offset (~57 ms at the configured sample period).
long sum = 0;
uint32_t next_us = micros();
for (int i = 0; i < CT_DC_SAMPLES; i++) {
sum += analogRead(pin);
next_us += CT_SAMPLE_PERIOD_US;
int32_t wait = (int32_t)(next_us - micros());
if (wait > 0 && wait < (int32_t)CT_SAMPLE_PERIOD_US) {
delayMicroseconds((uint32_t)wait);
}
}
int offset = (int)(sum / CT_DC_SAMPLES);
// Pass 2: AC RMS (~333 ms ≈ 20 cycles @ 60 Hz / 16.7 cycles @ 50 Hz).
float sum_sq = 0.0f;
next_us = micros();
for (int i = 0; i < CT_RMS_SAMPLES; i++) {
float s = (float)(analogRead(pin) - offset);
sum_sq += s * s;
next_us += CT_SAMPLE_PERIOD_US;
int32_t wait = (int32_t)(next_us - micros());
if (wait > 0 && wait < (int32_t)CT_SAMPLE_PERIOD_US) {
delayMicroseconds((uint32_t)wait);
}
}
float rms_counts = sqrtf(sum_sq / CT_RMS_SAMPLES);
// Convert ADC RMS counts → primary amps and suppress noise floor
float i_rms = rms_counts * CT_SCALE;
return (i_rms < CT_NOISE_FLOOR_A) ? 0.0f : i_rms;
}7.10 Key code snippet 3: immediate-sync alert
sync:true tells the Notecard to skip the outbound queue timer and open a
cellular session right away. Over cellular, the alert usually reaches Notehub
within 15–60 seconds of the threshold trip. sendAlert() builds the Note from
its arguments, so the same code sends overload, phase_imbalance, and
high_temp alerts to NOTEFILE_ALERT ("xfmr_alert.qo"). It tries twice, and
if both attempts fail, the alert stays in its pending slot for the next wake.
void sendAlert(uint8_t slot, const char *type, float i_a, float i_b,
float i_c, float temp_c, float extra) {
// ...
bool queued = false;
for (int attempt = 0; attempt < 2 && !queued; attempt++) {
J *req = notecard.newRequest("note.add");
JAddStringToObject(req, "file", NOTEFILE_ALERT);
JAddBoolToObject(req, "sync", true);
J *body = JAddObjectToObject(req, "body");
JAddStringToObject(body, "alert", type);
JAddNumberToObject(body, "i_a_rms", i_a);
JAddNumberToObject(body, "i_b_rms", i_b);
JAddNumberToObject(body, "i_c_rms", i_c);
JAddNumberToObject(body, "temp_c", temp_c);
JAddNumberToObject(body, "extra", extra); // loading_pct, imbalance_pct, or i_total
J *rsp = notecard.requestAndResponse(req);
if (rsp) {
const char *err = JGetString(rsp, "err");
queued = !(err && *err);
notecard.deleteResponse(rsp);
}
// ...
}
// ...
}7.11 Key code snippet 4: sleeping until the Notecard raises ATTN
After sending any queued Notes, loop() asks the Notecard to hold ATTN low
for sample_interval_sec seconds, confirms the pin has gone low, and puts the
STM32 into STOP2. The host stays alive, so the firmware checks the result of the
card.attn request instead of treating it as a fire-and-forget command. When
the Notecard raises ATTN, execution resumes on the next line and loop() runs
again with state intact.
void setup() {
// ...
// Arm the ATTN wake: the Notecard raises ATTN (D5) to end each sleep.
cxSleepBegin();
}
void loop() {
// ...
// ---- Sleep ---------------------------------------------------------
// Ask the Notecard to hold ATTN low for sample_interval_sec, then enter
// STOP2. When ATTN rises, execution resumes here and loop() runs again
// with `state` intact in RAM.
uint32_t sleep_sec = (cfg.sample_interval_sec > 0)
? cfg.sample_interval_sec
: DEFAULT_SAMPLE_INTERVAL_SEC;
if (!cxSleepUntilAttn(notecard, sleep_sec, NULL, &debugSerial)) {
// The Notecard didn't take the sleep request, or ATTN never went low
// (check the ATTN -> D5 jumper). Keep the sample cadence and retry.
debugSerial.println("[sleep] ATTN sleep failed — waiting out the interval awake");
delay(sleep_sec * 1000UL);
}
}8. Data Flow
Every 5 minutes (sample_interval_sec), the host wakes and reads the configured
CT channels (A0 always, A1 when phase_count≥2, A2 when phase_count=3) and
the temperature sensor. Then it checks three threshold rules against the fresh
readings and the accumulated history.
Collected. Per-phase RMS current (A) on phases A, B, and C; enclosure temperature (°C); total CT current (sum of phases); derived loading percentage; derived phase-imbalance percentage; and the count of overloaded sample intervals in the current summary window.
Transmitted:
xfmr_summary.qo: one record persummary_interval_min(24 records/day by default). It holds per-phase means over loaded sample intervals (intervals where at least one phase exceeded the 0.5 A noise floor), mean enclosure temperature, the count of overloaded sample intervals, the number of loaded samples (samples), and total host wakes in the window (total_wakes). It's template-encoded, then queued and flushed by the Notecard's hourly outbound sync. Multiplyloading_pctbysamples / total_wakesto recover the true time-weighted window average (see §11).xfmr_alert.qo: sent as soon as a threshold trips, withsync:trueto bypass the outbound interval. Each alert type has a 30-minute cooldown to keep alerts from piling up into a storm.
Triggers. Three independent rules:
overload: any single phase exceedsrated_amps × (overload_pct / 100). Theextrafield carries the peak phase's loading percentage. A transformer that stays above 95% of its nameplate rating ages its insulation fast enough to measurably shorten its service life.phase_imbalance:(max_phase − min_phase) / max_phaseexceedsimbalance_pct_threshwhile at least one phase is above 2A. The imbalance check is skipped entirely whenphase_count=1(single-leg mode), andimbalance_pctis always 0.0 in both the real-time alert path and the summary average. Withphase_count=2, only L1 and L2 are compared, and the unconnected C channel (always 0A) stays out of the min/max calculation. Theextrafield carries the computed imbalance percentage. On three-phase transformers, phase imbalance generates negative-sequence currents that heat the core, waste capacity, and can push a lightly loaded phase into overvoltage.high_temp: enclosure temperature exceedstemp_alert_c. Theextrafield carries the total current across all phases at the time of the alert. Temperature combined with load is the main driver of accelerated transformer aging, and the Arrhenius rule of thumb says every 10°C above design temperature cuts insulation life in half.
Routed. Both Notefiles go through Notehub, where operators set up routes
that send xfmr_alert.qo to an outage-management system or on-call paging tool
and xfmr_summary.qo to a long-term historian for trending and
transformer-life analytics.
9. Validation and Testing
Expected steady-state behavior. On a healthy, lightly loaded transformer
with well-balanced phases, the Notehub Events tab should show one
xfmr_summary.qo per hour and zero xfmr_alert.qo events. The serial monitor
should print [sample] lines every 5 minutes and [summary] queued once an
hour (the Note reaches Notehub on the next outbound sync).
Bench bring-up without a live transformer. Connect the CTs to a
known-current AC circuit (a 120VAC lamp load on one phase works well) and check
that the i_a_rms field in the summary matches the expected current (within
~5%). With no load on a CT channel, the reading should be 0.0A. If it isn't, the
bias network has a wiring error or the ADC offset is drifting outside the noise
floor.
Triggering alerts for validation. Drop overload_pct to 50.0 in the Fleet
environment, which is the lowest value the firmware allows (it clamps
overload_pct to 50–125; see §7.7). The next
inbound sync (≤2 hours by default) pulls the new value, and the next sample
fires an overload alert on any load above half of rated_amps. Set
overload_pct back to your intended value afterward.
Power validation with Mojo. The Notecard Cell+WiFi (MBGLW) idle current is ~18 µA @ 5V. Cellular transmit sessions draw ~250 mA on average, with brief peaks up to ~2 A. The host MCU sleeps in STOP2 between samples and adds only ~1–2 µA.
Expected power profile at the defaults (5-min sample, hourly transmit):
| Firmware state | Expected current |
|---|---|
| Notecard idle between samples (radio off) | ~18 µA for the Notecard IC per the datasheet. The Mojo reading for the full board at 5V with the host in STOP2 (~1–2 µA) includes additional board quiescent draws, so use the Mojo trace, not the 18 µA figure, for whole-assembly power budgeting. |
| Host active: ADC sampling (~1 second per wake, 3 channels × ~20 mains cycles each) | Measure with Mojo on your actual assembly. Host and Notecarrier board current during ADC sampling depends on the assembly and isn't a published Notecard figure. |
| Cellular session (hourly, ~10–30 seconds) | ~250 mA avg, ~2 A peak @ 5V |
| 24-hour session energy (from published Notecard figures) | Cellular sessions: 24 sessions × 10–30 seconds × 250 mA avg ÷ 3600 ≈ 17–50 mAh/day. Host ADC wakes and board/regulator quiescent current depend on the assembly, so measure both with Mojo on your actual hardware and add them to the cellular figure to get the whole-assembly daily budget before field deployment. |
Here's what to look for on the Mojo trace:
- Healthy: a steady low baseline at the board's quiescent current level, brief ~1 second blips every 5 minutes (host ADC reads across three channels), and one 10–30 second burst at ~250 mA each hour (cellular sync). Pin down the exact baseline with Mojo during commissioning, since it reflects Notecarrier and regulator draws with the host in STOP2.
- Host not sleeping: a sustained baseline well above the quiescent level you
measured with the host asleep. The host isn't entering STOP2. Check the
ATTN→D5jumper (the serial log shows[sleep] ATTN sleep failedwhenATTNnever reachesD5), and confirm the firmware was built with USB support set to None (usb=none). With USB CDC enabled, the host leaves STOP2 within a millisecond. - Weak signal: hourly bursts at the right spacing, but each burst is >60 seconds at peak. Route the cellular antenna away from the transformer casing.
Mojo is a bench-validation and regression tool, not a production requirement. Once a firmware revision passes the trace check, deployed units don't need it.
10. Troubleshooting
| Symptom | Cause | Solution |
|---|---|---|
Serial console shows [sample] lines but no [summary] lines after 60+ minutes | Summary not being queued | Check that the summary_interval_min env var is set (default 60). If you see [summary] queued but no Notehub events, the Notecard isn't syncing. See the "No events in Notehub" row below. |
| No events appearing in Notehub Events tab after 2+ hours | Device not syncing to Notehub | Make sure the Notecard has a valid SIM (Blues prepaid or user-provided). Check the Notecard's cellular signal with a card.wireless request from the Notecard In-Browser Terminal over USB (Notecarrier CX USB-C switch in NC). If the signal is poor, move the antenna away from the transformer core or switch to an outdoor SMA antenna (see §5). |
| Mojo trace shows sustained elevated baseline (not just brief ~1 second spikes every 5 minutes) | Host not sleeping between samples | Check that the ATTN→D5 jumper is seated on the Notecarrier CX header (see §5) and that the firmware was built with USB support set to None (usb=none). The serial log prints [sleep] ATTN sleep failed when cxSleepUntilAttn() can't sleep. If that line never appears but the baseline stays high, the USB CDC stack is usually to blame. |
| Current readings are non-zero even with no load on CTs | Bias circuit error or ADC offset drift | Check that all three bias circuits are wired correctly (see the §5 diagram) and that the capacitor's positive plate points toward the ADC pin. If the wiring is correct, the no-load noise is probably above the 0.5 A noise floor. Raise CT_NOISE_FLOOR_A in the firmware (to 1.0, say) to suppress it. |
| CT readings are consistently wrong (e.g., reading 50 A when the load is 100 A) | Incorrect CT_SCALE constant or ADC resolution mismatch | Confirm analogReadResolution(12) is called before any analogRead (it is, in setup()). If a different ADC resolution was selected before flashing, the scale factor is off by a factor of 2^(12 - new_bits) (a 10-bit setting reads 4× low). Recalculate CT_SCALE = (3.3 / 4096) × (2000 / 22) for 12-bit resolution, and see §7.2 for the formula. |
| Mojo shows correct steady state but summary payload has temp_c = -999.0 | MCP9808 not responding | Check the I²C wiring (SDA/SCL) and confirm the I²C address is 0x18 (the default). Try the Adafruit_MCP9808 example sketch to see whether the sensor responds. |
11. Limitations and Next Steps
A pole-mount monitor that drives operational decisions about live distribution equipment has a high bar to clear in production. It needs surge protection, signed utility approvals, billing-grade accuracy where that matters, and communications that survive when the transformer itself fails. This POC stays narrow on purpose, with three signals (per-phase load, imbalance, enclosure thermal stress) over one cellular path, so the alerting and trending pieces stay readable. The items below show where that scope ends and what a production build would naturally add.
Simplified for this POC
Each limit below was chosen to keep the alerting and trending code easy to follow, and each one notes what a production build would do differently.
The CT current range is bounded by the chosen sensor. The SCT-013-000 (100A)
suits transformers up to about 20 kVA at 240V (~83A full-load). Transformers at
or above 25 kVA at 240V draw ~104A, which is past this CT's rating, so they need
a higher-range current-output CT (200A or 400A). Voltage-output CTs are
not drop-in replacements (see §4). Update the firmware's CT_TURNS_RATIO and
CT_BURDEN_OHMS constants to match the CT you choose, and all the downstream
calculations scale automatically.
RMS accuracy is monitor-grade, not billing-grade. The two-pass ADC algorithm covers about 20 mains cycles per channel (~330 milliseconds per channel, ~1 second total for three channels) at the host ADC's throughput rate. That's accurate enough (~5–10%) for a load-threshold monitor, but it is not suitable for billing-grade energy metering. Extending the sample count to cover ~200 mains cycles per channel would improve RMS accuracy, at the cost of proportionally longer host-active time.
Summary averages reflect loaded intervals only. i_a_rms, i_b_rms,
i_c_rms, i_total, loading_pct, and imbalance_pct in xfmr_summary.qo
are computed only over the sample intervals (samples) where at least one phase
exceeded the 0.5 A noise floor. Intervals with no detectable load are left out
of the denominator. So a lightly or intermittently loaded window reports the
average current during the intervals when load was present, not the true
time-weighted average across the full window. That's the right behavior for
fault detection and threshold alerting, because alert thresholds should be
checked against actual load conditions without idle time diluting them. For
utilization reporting or transformer-life analytics, multiply loading_pct by
samples / total_wakes (both fields are in the summary payload) to recover the
time-weighted window average.
Power factor is not measured. The firmware reports apparent current (A RMS), not real power (watts) or reactive power (VAR). A transformer's nameplate rating is in kVA (apparent power), so the loading percentage is correct as stated. Measuring true power would take voltage sensing on top of current sensing, which would be a meaningful production enhancement for a billing or power-quality application.
Temperature is single-point, an enclosure proxy and not a true ambient probe. This implementation uses an MCP9808 mounted inside the enclosure. It measures the enclosure's internal temperature, which isn't true outdoor ambient air temperature, and it isn't transformer core or winding temperature either. Enclosure temperature moves in the same direction as thermal stress, but it can drift far from outdoor ambient, especially on a sun-exposed pole, and it lags winding temperature by minutes to hours under transient load changes. A true ambient probe (in an external housing shielded from direct sun) or a winding-contact sensor (a PT100 RTD or thermocouple on the transformer case) would give you more actionable data. See Production Next Steps below.
This design uses conventional split-core CTs, not Rogowski coils. This implementation uses conventional current-output split-core CTs (YHDC SCT-013-000) instead of flexible Rogowski-type coils. True Rogowski coils produce a signal proportional to di/dt, so they need an external integrator board in front of the ADC. For power-frequency monitoring they have no inherent accuracy advantage over calibrated current-output CTs, and they add real cost and complexity. Flexible Rogowski coils (such as the Dent Instruments RoCoil or Magnelab RCT series) are the right choice for physically constrained installations where a rigid split-core jaw can't open wide enough to go around a large busbar or conductor bundle. Standard residential and commercial distribution transformer secondary leads don't have that constraint, so this design uses the simpler current-output CT.
There is no inductive power harvest. The project description mentions optional inductive energy harvesting from the transformer. This POC leaves it out, because a validated inductive harvester brings a lot of complexity and installation constraints compared with a direct 120VAC tap on the transformer secondary. Where tapping the secondary is impractical (sealed enclosures, third-party owned equipment), inductive harvesting is the right production next step. It would need its own separate, validated power-stage design, though, and that's out of scope here.
There is no backup power. If the transformer fails, which is exactly the scenario this device is trying to catch early, the monitor loses power too and can't report the outage itself. Adding a Blues Scoop with a small LiPo battery would let the device ride out a brief outage and send a "power lost" event before the battery dies. That's a meaningful production addition for "last gasp" outage detection.
Alert cooldown is cycle-based. The cooldown counter tracks sample cycles,
not wall-clock time. If you change sample_interval_sec through an env var
after an alert fires, the cooldown duration shifts by the same proportion.
That's acceptable for most deployments. For strict SLA compliance, store the
alert timestamp in persistent state instead.
There is no surge or transient protection. The 2A slow-blow fuse on the 120VAC tap protects against a sustained overcurrent fault, but it doesn't suppress lightning-induced transients, switching surges, or capacitor-bank switching events, and all of those are common on distribution transformer secondaries. A permanent pole-mount deployment requires: a surge protective device (SPD) rated for the site's overvoltage category (Category C / lightning-level, per IEC 61643-11 or UL 1449) installed on the Line/Neutral entry ahead of the fuse; enclosure grounding and bonding to the pole ground system per applicable NEC sections and utility rules; verified creepage and clearance in all mains-voltage wiring and connectors; UV and temperature qualification of all external cabling and the enclosure; and written utility approval before the secondary tap is permanently energized. This POC leaves out the SPD and grounding circuit as out of scope for a bench or controlled field-trial context, but do not omit them in any design intended for permanent field deployment.
Production Next Steps
Getting from a controlled trial to permanently energized pole-mount units across a whole fleet takes the additions below.
Surge and grounding protection is mandatory before permanent field deployment. That means a surge protective device (SPD) on the AC mains entry (Category C rated, per IEC 61643-11 or UL 1449), enclosure grounding/bonding to the pole ground system per applicable NEC sections and utility rules, and written utility approval.
Higher-range CTs (roughly 250A to 800A) cover 50–167 kVA distribution
transformers.
Update CT_TURNS_RATIO and CT_BURDEN_OHMS to match.
A contact temperature probe on the transformer case allows direct winding-temperature monitoring. Options include a PT100 RTD through a MAX31865 (SPI, which needs three additional GPIO pins for SCLK/MISO/CS) or a thermocouple with a MAX31856 (also SPI). An I²C digital sensor such as the Texas Instruments TMP117 (±0.1°C from –20°C to 50°C, rated –55°C to 150°C) avoids the extra SPI pins and covers typical transformer case temperatures.
Voltage measurement on the secondary (a resistor divider plus a precision analog input or an external ADC) would let the device compute real power, power factor, and true apparent kVA loading.
Inductive energy harvesting from a current-carrying secondary conductor is an alternative to the 120VAC tap, and it's valuable for sealed enclosures where the tap isn't accessible.
A Blues Scoop with LiPo makes last-gasp outage reporting possible when the primary supply fails.
Over-the-air host firmware updates across the deployed fleet matter a great deal when physically re-flashing hundreds of pole-mount units isn't practical. Notecard Outboard Firmware Update (ODFU) provides this capability. Check the specific wiring and bootloader support for the Notecarrier CX's STM32L433 host against Blues documentation before you commit to a production design.
Per-transformer commissioning would add a one-time rated_amps calibration
through an env var at install time, based on a field measurement of the actual
secondary current at a known load.
12. Summary
The pole-top transformer that used to stay invisible until Friday's peak demand
took down the neighborhood now reports for itself every hour. Three split-core
CTs clamped on the secondary leads and one I²C temperature sensor give the
operations center the three signals that matter most for transformer health:
per-phase loading, phase imbalance, and thermal stress. Hourly summaries feed
capacity planning, and an xfmr_alert.qo Note goes out immediately when a phase
climbs past 95% of rated current or the enclosure runs hot.
One SKU, one SIM, and one firmware image work on a dense urban feeder and on a rural single-wire run alike. That's what lets a pilot that works on paper grow into a fleet program that actually scales. The dispatcher hears about trouble before the streetlights go out, and the planner gets a year of loading history without sending a single truck out to read it.
On This Page
- 1. Project Overview
- 2. System Architecture
- 3. Technical Summary
- 4. Hardware Requirements
- 5. Wiring and Assembly
- 6. Notehub Setup
- 7. Firmware Design
- 7.1 Installing and flashing
- 7.2 Firmware constants and environment variables
- 7.3 Modules
- 7.4 Sensor reading strategy
- 7.5 Event payload design
- 7.6 Low-power strategy
- 7.7 Retry and error handling
- 7.8 Key code snippet 1: Note template definition
- 7.9 Key code snippet 2: CT RMS two-pass algorithm
- 7.10 Key code snippet 3: immediate-sync alert
- 7.11 Key code snippet 4: sleeping until the Notecard raises ATTN
- 8. Data Flow
- 9. Validation and Testing
- 10. Troubleshooting
- 11. Limitations and Next Steps
- 12. Summary