Municipal Wastewater Lift Station 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/lift_station_monitor/ so it can learn how the device works and help you adapt it to your hardware.
This project is a downtime prevention retrofit for municipal wastewater lift stations. It catches pump failures, discharge obstructions, and high-water conditions before they turn into a sanitary overflow. The hardware is a handful of sensors and one Notecard for Skylo, a single module with cellular, Skylo satellite, and WiFi radios that fails over between them on its own. Together they turn a sealed concrete vault into a remotely monitored station, and the on-call crew gets alerts within minutes instead of waiting hours for someone to make a site visit. One SKU and one firmware image cover the whole fleet, from the station three blocks from city hall to the one at the edge of the service territory, past the last cell tower.
1. Project Overview
The problem. A lift station (also called a pump station) is a below-grade concrete vault or roadside cabinet that collects raw sewage from the surrounding gravity sewer system and pumps it uphill toward the treatment plant. Every municipality has dozens of them, often scattered across low-lying neighborhoods, industrial zones, and rural road shoulders. Most have no onsite staff, and nobody knows what's happening inside until a citizen calls to report a smell or, worse, a spill.
When a lift station fails, the wet well (the collection basin that feeds the pumps) fills up and overflows. That's an SSO, or sanitary sewer overflow, and regulators notice right away. An SSO triggers EPA reporting obligations, risks consent decree violations, and calls for an expensive emergency cleanup. If a station fails on a Friday evening and nobody finds out until Monday morning, the city has a public health event, a PR crisis, and a big unplanned bill all at once. The causes are rarely dramatic: a pump fails to start because its float control sticks, a discharge check valve fails and lets backflow clog the impeller, or a wet-well float switch trips but the alarm never reaches anyone because the SCADA dial-up modem lost its phone line. You could catch any of these within minutes of it starting, if someone were watching.
This project is that watcher. It straps to the inside of the station and samples the wet-well level every 60 seconds, measures current draw on each pump, and keeps an eye on the high-water float switch. That's four sensing points from three sensor types. Edge logic on the STM32-based host MCU checks three fault rules every 60 seconds. The instant any rule trips, it sends an alert to the Blues Notehub cloud service, over cellular where a tower is in reach and over the Skylo satellite network where one isn't, so the on-call crew gets paged before the wet well overflows.
Why Notecard for Skylo. Wireless is the only realistic option here. Lift stations sit in underground concrete vaults, often with no AC power outlet in the vault itself (power runs to the pump control panel, not a wall socket). Their locations follow the sewer network rather than the municipal network. No fiber runs out to a roadside pump cabinet, and no corporate WiFi AP can reach through a concrete lid to a sensor inside. To match what one Notecard handles on its own, utility supervisors would have to install and maintain a WiFi access point at every single station.
Notecard for
Skylo
(NOTE-NBGLWX) puts three radios on one M.2 module and picks between them
automatically: cellular (LTE-M / NB-IoT / GPRS), satellite over the
Skylo non-terrestrial network (NTN), and WiFi. The
firmware sets one
card.transport
preference, wifi-cell-ntn. The Notecard uses WiFi if an accessible AP happens
to be in range (rare for a sealed vault), falls back to cellular (the de facto
primary at most municipal infrastructure), and falls back again to Skylo
satellite at stations on the fringe of the service territory, beyond the reach
of any cellular carrier. Failover happens inside the Notecard, so the host
firmware never has to check which network is live. What used to be a two-SKU
decision, with a cellular Notecard for in-coverage stations and a satellite
device for the rest, becomes one part number, one antenna kit, and one firmware
image that deploys unchanged across the fleet. If a station turns out to have
weaker coverage than the survey suggested, there's nothing to swap. The board
that runs on cellular downtown reaches the Skylo network by itself at the rural
lift station.
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.
Deployment scenario. A sealed NEMA 4X enclosure mounts inside the lift station's above-grade control cabinet and runs off the 120 VAC control circuit that already powers the pump starters. The specified Blues hardware and NEMA 4X ABS enclosure are not rated for hazardous (classified) locations. Wet wells and sealed vaults can build up methane and hydrogen sulfide, and either one can potentially create a classified atmosphere under NFPA 820 / NEC Article 820. Don't install this hardware inside the wet well or in any classified-atmosphere zone. If your jurisdiction classifies the vault interior as a hazardous location, any hardware in that zone has to be rated for the classification, so talk to a licensed electrical engineer before you go further. Sensor cables come in through conduit fittings. One multiconductor cable runs to the submersible level transducer in the wet well, two split-core CT jaws clamp around the pump motor supply conductors inside the control panel, and one float switch cable runs to a new, dedicated high-water alarm float switch hung in the wet well next to the station's existing level floats. Beyond those three sensor connections to the existing control wiring, the station needs no modification. Notecard for Skylo's antenna cables exit through a conduit fitting to antennas mounted outdoors on the cabinet exterior or an above-grade access point. The same Skylo-certified antenna carries both cellular and satellite, so if a station ends up relying on the satellite link, you don't have to swap antennas.
2. System Architecture
Device-side responsibilities. Inside the above-grade cabinet, the
Notecarrier CX's STM32L433 host wakes once a minute when the Notecard raises its
ATTN pin (armed with
card.attn). It reads
three sensor types across four inputs (level, CT1, CT2, float switch) and runs
through the three fault rules before the wet well can move more than an inch or
two. The Notecard sits next to it on I²C, so the host never deals with AT
commands, a modem state machine, or serial buffers. Between samples the host
sleeps in STM32 STOP2 (a few µA) with its RAM intact. Anything that has to carry
over to the next cycle, like the previous level reading, alert cooldowns, and
summary accumulators, just stays in memory, and the host picks up right where it
left off.
Notecard responsibilities. Notecard for Skylo does the same job no matter
which radio is live. It queues Notes locally,
ships them on the
hub.set outbound
cadence (60 minutes by default), and moves any sync:true alert to the head of
the queue for the next available session. The card.transport wifi-cell-ntn
preference set at boot picks the path. On cellular, a Note is in flight 15–60
seconds after the host queues it. If the unit has fallen back to satellite, the
Note goes first in line for the next Skylo session, which takes a few minutes
instead of seconds. The Notecard also pulls environment
variables
from Notehub on every inbound sync. That lets a utility supervisor retune level
setpoints, current thresholds, or rising-rate sensitivity for the whole fleet
from a browser, without reflashing firmware or lifting a vault lid.
Notehub responsibilities. Using its embedded global SIM and bundled Skylo
satellite allocation, the Notecard hands events off to
Notehub, which timestamps and stores each one and applies
project-level routes.
Alerts and summaries go to separate
Notefiles on purpose. lift_alert.qo can
go out to PagerDuty or SMS while lift_summary.qo lands in a long-term
analytics store, and the routes don't need any filter logic.
Smart Fleets let you
group stations by service zone or pump type and still set per-station overrides
for the one vault that always reads a little funny.
Routing to the cloud (high level). Notehub can route to HTTP, MQTT, AWS, Azure, GCP, Snowflake, and many other destinations. Route setup depends on your project and isn't covered here. The Notehub routing docs explain how to configure one.
Satellite (NTN) operation considerations
Every station runs the same Notecard for Skylo with the same firmware. At stations that regularly fall back to the satellite link, though, the NTN path behaves differently, and you have to plan for those differences instead of assuming them away. They only apply while the unit is transmitting over satellite. Stations inside cellular coverage behave like any other cellular Notecard.
Alert latency. sync:true Notes get priority for the next available
satellite session, but finding a Skylo satellite and finishing the transmission
can take several minutes. When a station is running over satellite, plan on
alerts arriving in minutes rather than seconds. The firmware's 30-minute alert
cooldown still makes sense, because a satellite station still reports the fault
to the crew before the wet well overflows, just not instantly.
Inbound sync cadence and data cost. Each inbound sync (which pulls updated
environment variables from Notehub) uses about 50 bytes of satellite data. At
the default inbound:120 (every 2 hours), that's ~600 bytes per day, a big
chunk of the bundled 10 KB satellite allocation. For stations you expect to run
over satellite, set inbound_interval_min to 240 or higher in the Notehub
environment-variable panel to cut inbound satellite data use. The firmware
re-issues hub.set whenever inbound_interval_min or summary_interval_min
changes, so neither change needs a firmware reflash (see the env-var table in
Section 6).
Payload discipline. The Skylo NTN link has a hard 256-byte limit per Note,
and the satellite network silently drops anything bigger. This firmware uses
note.template
encoding (with format:"compact" and a numeric port), which keeps both
lift_alert.qo and lift_summary.qo well under that ceiling. Don't add
free-form string fields to these Notefiles, and check the payload size whenever
you change the schema.
Antenna placement. For satellite to work, Notecard for Skylo's main antenna
has to be outdoors and clear of obstructions. In the northern hemisphere, that
means an unobstructed view of the southern sky, where Skylo's GEO satellites sit
above the equator. If the enclosure is entirely below grade or inside a steel
cabinet, you'll need an above-grade antenna cable run, so plan that conduit path
when you install. Use only the Skylo-certified antenna that comes with Notecard
for Skylo, on the MAIN u.FL port. An uncertified substitute risks regulatory
non-compliance and link failure. That antenna also carries the terrestrial
cellular signal, so a placement that works for satellite fallback works for
cellular too.
Power envelope. Notecard for Skylo usually idles at ~8 µA. A network session, whether cellular or satellite, draws around ~250 mA on average from the onboard modem, with brief higher peaks. On a 2G transmit burst the BG95-S5 can pull nearly 2 A for a few milliseconds. The HDR-15-5 (rated for 2.4 A) covers those peaks. The Validation section has a per-state current breakdown.
Mandatory initial non-NTN sync. Notecard for Skylo can't use satellite (NTN)
until it has completed at least one non-NTN sync with Notehub over cellular or
WiFi. That first sync associates it with the project and registers the Notefile
templates. The cold-boot hub.set in periodic mode handles it automatically
over cellular/WiFi, so make sure the unit has cellular (or WiFi) coverage during
initial commissioning, even if the site will rely on satellite day to day.
3. Technical Summary
What you'll have: A Notecarrier
CX
and Notecard for Skylo that sample every 60 seconds and send a
lift_summary.qo event to your Notehub project every hour, plus a
lift_alert.qo whenever a fault rule trips. You can bring it up on the bench
before any field sensors are installed.
- Create a Notehub project at notehub.io and copy its ProductUID.
- Flash the firmware:
(Replace
arduino-cli core install STMicroelectronics:stm32 arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC" cd firmware/lift_station_monitor arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \ --build-property "compiler.cpp.extra_flags=-DPRODUCT_UID=\"com.example:demo\"" \ -u -p /dev/ttyACM0/dev/ttyACM0with your Notecarrier serial port. On macOS use/dev/tty.usbmodem*, and on Windows useCOM*.) Theusb=noneboard option turns off the USB CDC stack, which has to be off for the host to sleep (see Section 7). Thesketch.yamlin that directory sets the same board options. - Open the ST-LINK virtual COM port at 115200 baud. Debug output comes out
of the Notecarrier CX debug jack, not USB. Check that the logs show a
[CONFIG] hub.set outbound=60 inbound=120 minline and a[SENSOR]reading on every wake. - Check Notehub Devices. Your Notecard shows up within 60 seconds. Click it
to see
lift_alert.qoandlift_summary.qoevents in the Events panel (the first summary arrives after about an hour). - Tune thresholds. In the Fleet panel, set environment variables (for
example,
high_level_pct: 50.0). The firmware applies the new values on the first wake after the Notecard's next sync.
For bench-only testing, compile-time flags can inject synthetic sensor values (see "Bench fault simulation" in Section 9).
A single SKU covers every kind of station. Each one uses a Notecard for Skylo (datasheet). Stations within LTE coverage transmit over cellular, and stations beyond reliable cellular reach fall back to the Skylo satellite network automatically, with no second device, different part number, or antenna swap. The firmware and Notehub configuration are the same everywhere. Stations you expect to run over satellite just do better with wider inbound sync intervals, which save bundled satellite data. Satellite (NTN) operation considerations in Section 2 has the details.
Here's a sample Note from this device:
{
"file": "lift_alert.qo",
"body": {
"alert": "pump_fail_to_start",
"level_pct": 87.4,
"pump1_amps": 0.2,
"pump2_amps": 0.1,
"float_sw": false
},
"sync": true
}4. Hardware Requirements
| Part | Qty | Rationale |
|---|---|---|
| Notecarrier CX | 1 | Integrated carrier with an onboard STM32L433 host, so this analog + digital sensor mix doesn't need a separate MCU. The host sleeps in STOP2 between samples, and the Notecard's ATTN pin wakes it. |
| Notecard for Skylo (NOTE-NBGLWX) (datasheet) | 1 | One M.2 module with cellular (LTE-M / NB-IoT / GPRS, Quectel BG95-S5 modem), Skylo satellite (NTN), and WiFi (Silicon Labs WFM200S) radios. The firmware's card.transport wifi-cell-ntn setting makes it use WiFi when an AP is in range (rare in a vault), cellular at in-coverage stations, and fall back automatically to the Skylo satellite network at stations beyond cellular reach, so there's no second device or part-number decision. It also removes per-site network provisioning, since the embedded global SIM and bundled satellite allocation cover the whole fleet from one SKU. Requires the antennas below. |
| Blues Mojo | 1 | Coulomb counter on the power rail for checking the sleep/wake/transmit energy profile on the bench. Not deployed to the field. |
| WIKA LH-10, 0–15 PSI gauge, 4–20 mA 2-wire, 316L SS, IP68, with vented cable | 1 | Submersible hydrostatic level transmitter built for water/wastewater wet-well immersion. (The WIKA S-10 is a general-purpose / sanitary transmitter, while the LH-10 is the family member rated for permanent submersion in raw sewage.) The vented polyurethane cable doubles as the support tether and is secured with a stainless cable grip at the cover plate. An internal vent tube equalizes the sensor's reference side to atmosphere, which makes the measurement gauge pressure = water head. The 316L SS wetted parts tolerate raw sewage, and the 15 PSI range covers ≈ 10 m of wet-well head. Output is 4–20 mA 2-wire loop-powered, wired the same as any other loop-powered transmitter. When ordering, specify "vented cable" and an M20×1.5 or ½ NPT conduit seal fitting. Available from WIKA, instrumart.com, and major industrial distributors. |
| 150 Ω, 1% resistor (level sensor shunt) | 1 | Converts 4–20 mA loop current to 0.6–3.0 V for the 3.3 V STM32 ADC (full-scale within VREF). |
| SCT-013-030 split-core CT, 30 A / 1 V RMS (voltage-output CT with a built-in burden resistor, so don't add one) | 2 | Non-invasive current sensing on each pump motor supply lead, with no break in the power circuit. SparkFun's SEN-11005 is a different 30 A CT with a 15 mA current output, so if you use it, add a 68 Ω burden resistor across its leads to get the same 1 V RMS at 30 A. The 30 A range suits single-phase motors up to about 5 HP at 230 V (FLA ≈ 28 A). Larger or three-phase motors need a higher-ratio CT. See Limitations. |
| TRRS 3.5 mm audio jack breakout (SparkFun BOB-11570) | 2 | The SCT-013's pigtail ends in a TRRS plug, and this breakout exposes Tip and Sleeve for the AC signal and shield/return. |
| 10 kΩ, 1% resistor (CT bias divider, 2 per pump) | 4 | A two-resistor divider centers the AC CT signal at VREF/2 ≈ 1.65 V so the unipolar STM32 ADC only sees positive voltages. |
| 10 µF electrolytic capacitor (CT bias decoupling, 1 per pump) | 2 | Low-pass filter on the bias node that cuts HF noise on the ADC input. |
| Gems Sensors RS-500-Y-PP, SPST N.O., polypropylene float switch | 1 | Sewage-rated polypropylene construction, with a normally-open contact that closes on high water. Mounts through the wet-well cover or on a cable-held hanger bracket. Available from Grainger and industrial distributors. Specify vertical or horizontal actuation to match the wet-well geometry. |
| MeanWell HDR-15-24, 85–264 VAC input, 24 VDC / 0.63 A, DIN-rail | 1 | AC–DC DIN-rail supply that derives 24 VDC from the station's 120 VAC control circuit. It powers the 4–20 mA sensor loop, and 15 W is plenty for the 0.48 W peak loop load. |
| MeanWell HDR-15-5, 85–264 VAC input, 5 VDC / 2.4 A, DIN-rail | 1 | AC–DC DIN-rail supply that derives 5 VDC for the Notecarrier CX USB-C port from the same 120 VAC control leg. |
| Skylo-certified LTE/satellite antenna included with Notecard for Skylo (u.FL, supports the S-Band / L-Band B23 / B255 / B256 bands) | 1 | Connects to the MAIN u.FL port and carries both the terrestrial cellular signal and the Skylo satellite link, so one antenna covers both networks. Use only the Skylo-certified antenna supplied with Notecard for Skylo, because an uncertified substitute risks regulatory non-compliance and link failure. Mount it outdoors on the cabinet exterior or an above-grade access point with an unobstructed view of the sky (in the northern hemisphere, the southern sky), and route it through a liquid-tight fitting. To use an external SMA mag-mount instead of the bare u.FL antenna, add a u.FL-to-SMA-F bulkhead pigtail (e.g. Taoglas CAB.0150.A.01). |
| Passive GPS/GNSS antenna (u.FL) per the Notecard for Skylo datasheet | 1 | Connects to the GPS u.FL port for GNSS time/location. Mount it outdoors next to the main antenna with a clear view of the sky, and route it through a liquid-tight fitting. |
| Hammond 1554N2GCLY NEMA 4X ABS enclosure, 8.07 × 6.10 × 3.94″ | 1 | Polycarbonate-gasketed, splash-resistant housing. Use liquid-tight conduit fittings (Heyco or equivalent) for every sensor cable entry. |
Bundled connectivity (Notecard for Skylo): It ships with an active global SIM that includes 500 MB of cellular data and 10 years of service, plus 10 KB of bundled Skylo satellite data, with no activation fees, no monthly commitment, and no separate satellite provider subscription. Additional satellite data is billed per byte (see the Notecard for Skylo datasheet for current pricing). Syncing inbound less often stretches the bundled satellite allocation, and Section 2 has guidance on that.
5. Wiring and Assembly
Safety. Lift-station control panels contain 120 VAC mains wiring in wet, corrosive, and potentially hazardous-atmosphere environments. Installation must be done by a licensed electrician or qualified instrumentation technician who follows your jurisdiction's electrical code and your utility's lockout/tagout (LOTO) procedures before opening any panel. Wet wells are classified confined spaces, so follow the applicable confined-space entry (CSE) regulations (OSHA 29 CFR 1910.146 in the US) before entering the vault. Test for H₂S and oxygen deficiency, set up an attendant and rescue plan, and use supplied-air or appropriate respiratory protection as required. Raw sewage carries pathogens, so wear appropriate PPE and follow hygiene protocols for all work inside the vault. This reference design is sensor and monitoring only. It never commands pump start/stop and has no safety-critical outputs.
All host I/O lands on the Notecarrier
CX dual 16-pin headers.
Notecard for Skylo seats in the M.2 slot. Its MAIN u.FL port connects to the
included Skylo-certified antenna, which carries both the cellular and satellite
signals. Connect the antenna directly, or if you're using an external SMA
mag-mount, route it through a u.FL-to-SMA-F bulkhead pigtail in the enclosure
wall. The GPS u.FL port connects to the passive GPS/GNSS antenna. Both
antennas mount outdoors with a clear view of the sky (in the northern
hemisphere, the southern sky), so the unit can reach the Skylo satellite network
wherever it falls back from cellular. During bench validation, the Mojo connects
through the Qwiic connector on the Notecarrier
and sits inline between the 5 V supply and the Notecarrier's +VBAT pad.
Level sensor (4–20 mA current loop)
The WIKA LH-10 is a submersible hydrostatic transmitter built for wet-well immersion, with a 4–20 mA two-wire loop-powered output and 316L stainless wetted parts. Some WIKA literature mentions the S-10, which is the family's general-purpose / sanitary variant. Don't substitute it for permanent submersion in raw sewage.
Physical installation. Lower the sensor into the wet well to a stable depth above the lowest feasible pumped level. The vented cable is also the support tether. Secure it with a stainless cable grip or strain-relief clamp at the wet-well cover plate so the sensor's weight is carried mechanically and not by the conductors. Keep the vent tube inside the cable clear and open to atmosphere at the enclosure end, and don't seal or submerge the vent opening. Thread the two conductors through a liquid-tight conduit fitting into the enclosure.
Loop wiring:
+24 Vfrom the MeanWell HDR-15-24 → sensor+conductor (usually red or brown, but confirm with the cable marking).- Sensor
–conductor (black or blue) → 150 Ω, 1% shunt resistor →GND. - Notecarrier CX
A0→ junction between the sensor–conductor and the top of the 150 Ω shunt.
The 150 Ω shunt turns the 4–20 mA loop current into 0.6–3.0 V, which maps
linearly to 0–100% wet-well depth and stays within the STM32L433's 3.3 V ADC
reference. The loop margin is V_supply – V_shunt_max = 24 V – 3.0 V = 21 V,
well above the sensor's minimum loop voltage (normally 10–12 V).
Pump current sensors (SCT-013-030 CTs)
Each CT clips around one supply conductor feeding a pump motor. Clamp only one
leg, because clamping both cancels the fields and reads zero. Wire each CT
circuit the same way. Here's pump 1 on A1:
- Insert the CT's TRRS plug into the SparkFun BOB-11570 breakout.
- Tip (signal) and Sleeve (return/shield) are the active terminals on the BOB.
- Bias circuit on
A1: connect two 10 kΩ resistors in series from+3V3toGND. The junction (midpoint) is the bias node, at VREF/2 ≈ 1.65 V. Put a 10 µF capacitor from the bias node toGNDto filter HF noise. - CT Sleeve → bias node; CT Tip → Notecarrier CX
A1(or the other way around, since the AC signal is symmetric). The CT bridges the bias node andA1, soA1sees 1.65 V DC with the CT's AC swing on top.
Repeat for the pump 2 CT on A2, with its own independent bias circuit.
Float switch
Install the Gems RS-500-Y-PP as a dedicated monitoring float, separate from the station's existing pump-control and SCADA float switches. Don't tap into the existing float-control or alarm wiring. Those circuits often carry 120 VAC or drive pump starter contactors, so they aren't safe or appropriate to connect straight to a GPIO. Hang the new float at the high-water monitoring setpoint you want (generally just below the station's rated overflow level) next to the station's existing level floats. The wet-well cover usually has spare conduit entries or room for another cable grip.
The Gems RS-500-Y-PP gives you a galvanically isolated SPST N.O. dry contact rated for this environment:
- One float switch terminal → Notecarrier CX
D2. - Other terminal →
GND. - The firmware configures
D2asINPUT_PULLUP, so when the switch closes it pulls the pin to GND (logic LOW = alarm active).
Host-sleep jumper (ATTN → D5)
Fit a short jumper between the ATTN and D5 pins on the Notecarrier CX.
Both are on the same 16-pin header (ATTN at position 3, D5 at position 14).
The Notecard pulls ATTN low while the host sleeps and raises it when the next
sample is due, and that rising edge on D5 wakes the host from STOP2. Leave the
Notecarrier CX EN pin unconnected. On the CX it 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.
Power
- 120 VAC from the station's control circuit → MeanWell HDR-15-24 (24 VDC) and MeanWell HDR-15-5 (5 VDC), both DIN-rail mounted inside the NEMA 4X enclosure.
- HDR-15-24
V+→ 4–20 mA level sensor loop positive terminal. - HDR-15-24
V-→ system GND (the same node as Notecarrier CX GND and the bottom of the 150 Ω shunt). This common connection completes the 4–20 mA loop return path. Without it, the loop current has no way back and the shunt voltage will be wrong. - HDR-15-5
V+→ Notecarrier CX USB-C port (or the +VBAT pad if you're not using USB-C). - HDR-15-5
V-→ system GND. - During bench validation, the Mojo sits inline on the 5 V rail between the HDR-15-5 output and the Notecarrier CX power input (see Section 9).
6. Notehub Setup
6.1 Project and Device Claim
- Create a project. Sign up at notehub.io and create
a project. Copy the
ProductUID and paste it
into the firmware as
PRODUCT_UID(inlift_station_monitor_helpers.h). Rebuild and flash. - Claim the Notecard. Power the Notecarrier CX. The Notecard associates with your project on its first cellular sync, so there's no manual claim step. Watch the Devices panel in Notehub, where your Notecard shows up within 60 seconds.
6.2 Environment Variables for Threshold Tuning
Every threshold below is an optional override of a firmware default. Set them in Notehub > Fleet > Environment, not in the firmware. The device picks up any variable you set in Notehub on its next inbound sync, with no firmware reflash.
| Variable | Default | Purpose |
|---|---|---|
pump_on_amps | 3.0 | Current draw (A) at or above which a pump counts as running. Adjust for larger or smaller motors. |
high_level_pct | 85.0 | Wet-well fill level (%) at which the fail-to-start check kicks in. Set it below the float switch trip point. |
rising_rate_pct | 2.0 | Level rise (% per 60-second sample) while a pump is running that triggers a clog alert. |
summary_interval_min | 60 | Minutes between summary Notes. Whenever it changes, the firmware also re-issues hub.set outbound to match, so the Notecard's outbound sync window stays lined up with the summary rate. |
inbound_interval_min | 120 | Minutes between inbound syncs (environment-variable pulls from Notehub). Satellite deployments: set to 240 or higher to save bundled satellite data (~50 bytes per inbound sync). Whenever it changes, the firmware re-issues hub.set inbound to match, so you don't need to reflash. |
To set a variable, click your project's Fleet, then the Environment tab.
Add each variable as a key-value pair (for example, high_level_pct = 75.0) and
click Save. The device pulls the new values on its next inbound sync.
6.3 Routing Events to the Outside World
Add routes in Notehub > Routes to send events to your on-call notification system (PagerDuty, Slack, email, webhook) or analytics backend:
- Alert route: Route
lift_alert.qoto your real-time on-call service (Notehub supports HTTP, MQTT, AWS SNS, Azure Event Hubs, Slack, PagerDuty, and others). - Summary route: Route
lift_summary.qoto a long-term analytics store (Snowflake, BigQuery, TimescaleDB, etc. via HTTPS or JDBC).
Because alerts and summaries live in separate Notefiles, each route handles its own. Alerts go out right away for urgent notification, and summaries batch up for efficient storage. The Notehub routing docs list the supported destination types and walk through setup step by step.
7. Firmware Design
The firmware is split across four files in
firmware/lift_station_monitor/:
| File | Role |
|---|---|
lift_station_monitor.ino | Main sketch: setup(), loop(), runSampleCycle(), runDetectionCycle(), sendAlert(), sendSummary() |
lift_station_monitor_helpers.h | Compile-time constants, AppState struct definition, extern globals, and helper-function prototypes |
lift_station_monitor_helpers.cpp | Helper implementations: parsing, clamping, notecardConfigure(), defineTemplates(), fetchEnvOverrides(), applyHubSetIfChanged(), sensor reads |
cx_sleep.h | Host sleep: card.attn request, STOP2 entry, ATTN wake |
The .ino file is self-contained for the Arduino IDE, which automatically
compiles the .ino + .cpp files in the same sketch folder together. The split
keeps the main sketch readable and gives the reusable utilities their own
compilation unit.
Dependencies:
- Arduino core for STM32
(
stm32duino/Arduino_Core_STM32). Blues Wireless Notecard(note-arduinolibrary). Install it from the Arduino Library Manager or witharduino-cli lib install "Blues Wireless Notecard".STM32duino Low Power(and its dependencySTM32duino RTC), also from the Library Manager. It provides the STOP2 entry and ATTN-pin wake thatcx_sleep.huses.
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
a power-only USB-C feed), 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).
Modules
| Responsibility | Function |
|---|---|
Notecard configuration (hub.set, card.transport wifi-cell-ntn for cellular→satellite fallback, accelerometer disable) | notecardConfigure |
| Notefile template registration | defineTemplates |
| Env-var threshold fetch (every wake) | fetchEnvOverrides |
Re-issue hub.set when summary_interval_min or inbound_interval_min changes (every wake, in loop()) | applyHubSetIfChanged |
| Level sensor ADC read and % conversion | readLevelPct |
| CT-based pump current measurement | readPumpAmps |
| Float switch debounced read | readFloatSwitch |
| Three-rule fault detection with cooldowns | runDetectionCycle |
| Immediate-sync alert emission | sendAlert |
| Hourly aggregated summary emission | sendSummary |
Host sleep (STOP2 until the Notecard raises ATTN) | cx_sleep.h: cxSleepBegin, cxSleepUntilAttn |
| Application state (held in RAM across sleep cycles) | AppState struct |
Sensor reading strategy
-
Level (4–20 mA transducer). The firmware averages 64 ADC samples, with a 500 microsecond delay between them so the STM32 ADC input can settle. It maps the result to a 0–100% scale using calibration constants based on the 150 Ω shunt physics: 4 mA → 745 counts and 20 mA → 3723 counts on a 12-bit, 3.3 V ADC.
setup()callsanalogReadResolution(12)to turn on 12-bit mode on the STM32L433. -
Pump current (SCT-013-030 CT). The bias resistor divider centers each CT's AC signal at VREF/2. The firmware first measures the DC bias over 256 bare
analogRead()calls. Then it accumulates the squared deviation from that bias over 1024analogRead()calls and takes the RMS. How long that window lasts depends on the MCU and ADC rate. The STM32L433's successive-approximation ADC finishes a raw conversion in a few microseconds, but each ArduinoanalogRead()call adds tens of microseconds of pin and ADC setup overhead. So 1024 samples span tens of milliseconds (roughly 100 ms, by the firmware's own estimate), which covers several mains cycles, and the integration window isn't synchronized to the AC mains cycle. That's good enough to tell whether a pump is running, but it isn't a calibrated true-RMS measurement. The SCT-013-030 is specified at 1 V RMS per 30 A RMS, soI_rms = V_rms × 30. The firmware reads both CT channels every cycle and counts a pump as running when its current exceedspump_on_amps. -
Float switch. A 5-reading majority-vote debounce (50 milliseconds total) filters out contact bounce. The result is one boolean,
truewhen the float switch indicates high water andfalsewhen things are normal.
Event payload design
This design uses two template-backed Notefiles. Templates give both files a fixed-width wire encoding, which makes each Note roughly 3–5× smaller than free-form JSON. That matters over a multi-year deployment with 24 summary Notes per day per station.
lift_alert.qo goes out immediately when any fault rule trips (within
seconds on cellular, minutes on satellite). Example:
{
"file": "lift_alert.qo",
"body": {
"alert": "pump_fail_to_start",
"level_pct": 87.4,
"pump1_amps": 0.2,
"pump2_amps": 0.1,
"float_sw": false
},
"sync": true
}Fields:
alert: one of"pump_fail_to_start","pump_clog", or"high_water_alarm".level_pct: wet-well fill at the time of the alert.pump1_amps,pump2_amps: instantaneous current draw on each motor supply when the alert fired.float_sw: whether the high-water float switch was closed (active) when the alert fired.
lift_summary.qo is queued every 60 minutes (or every
summary_interval_min) and goes out in the Notecard's next outbound session.
Example:
{
"file": "lift_summary.qo",
"body": {
"level_pct": 42.1,
"level_avg_pct": 38.6,
"pump1_amps_avg": 4.3,
"pump2_amps_avg": 0.0,
"pump1_run_min": 18.0,
"pump2_run_min": 0.0,
"float_sw": false,
"alert_count": 0,
"level_faults": 0,
"ct1_faults": 0,
"ct2_faults": 0
}
}Fields:
level_pct: instantaneous reading at the end of the summary window.level_avg_pct: mean of the window's valid level samples (60 samples at the default settings, minus any faulted ones).pump1_amps_avg,pump2_amps_avg: average current draw per pump across the window, counting the samples where the pump was off. A pump that ran 18 minutes at about 14 A averages about 4.3 A over the hour.pump1_run_min,pump2_run_min: how many minutes each pump was detected running (handy for checking lead/lag duty balance).float_sw: whether the float switch is closed at summary time.alert_count: how many alerts fired during this window (0 = clean window, >0 = trouble).level_faults,ct1_faults,ct2_faults: count of samples where the sensor ADC returned an out-of-range value (open circuit, short, rail saturation, or other hardware fault). The firmware leaves those samples out of the matching average, which reads-9999if no sample in the window was valid. Any nonzero count means that sensor needs a look.
Low-power strategy
setup() runs once at power-up to configure the Notecard. Each pass through
loop() is one wake cycle and lasts only a few seconds. The host reads the
sensors (~300 milliseconds for CT RMS), evaluates the rules, queues or syncs
Notes, and then 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(). That's why the AppState struct can just live in RAM,
with nothing serialized to the Notecard. If the sleep request fails or ATTN
never goes low (because the jumper is missing), loop() waits out the interval
awake and tries again on the next cycle. Notecard for Skylo sits in its own
low-power idle
state between radio sessions (~8 µA @ 5 V), no matter which radio it used last.
Sync strategy. The Notecard runs in
hub.set mode:"periodic",
which is the right choice for a duty-cycled sensor node. In periodic mode the
radio is fully off between sessions. The Notecard wakes on the configured
outbound timer, ships any queued Notes, and goes back to low-power idle.
notecardConfigure sets outbound:60 (a 60-minute outbound sync interval) and
inbound:120 (a 2-hour env-var pull cadence). Both are deliberately decoupled
from the 60-second sample interval. Sensor reads build up in the in-flight
summary window, and the Notecard opens a radio session for summaries only once
an hour. Alert Notes set sync:true, so the Notecard opens a session as soon as
the host queues one, skipping the outbound timer entirely. notecardConfigure
also issues a one-time
card.transport
{"method":"wifi-cell-ntn"} so the Notecard prefers WiFi, then cellular, then
Skylo satellite (NTN). That fallback is what lets the same firmware serve
stations both inside and beyond coverage.
For stations you expect to run over satellite, outbound:60 is a reasonable
starting point, but take a second look at the inbound:120 cadence. Each
inbound satellite check uses about 50 bytes of bundled satellite data, and
polling every two hours (about 600 bytes a day) eats a big share of the 10 KB
bundled allocation. For
satellite stations, raise inbound_interval_min to 240 or higher in the
Notehub env-var panel. If you're also lowering the outbound summary rate (with
summary_interval_min), set both env vars together. The firmware re-issues
hub.set with the new outbound and inbound values whenever either one changes,
so there's no firmware reflash.
Sampling (every 60 seconds) and transmission (every 60 minutes by default, set
by summary_interval_min) are kept apart on purpose. Each summary window's
sensor reads feed one summary Note, and only alert Notes skip the outbound
timer. If an operator changes summary_interval_min in Notehub, the firmware
re-issues hub.set so the Notecard's outbound window follows the summary rate
automatically. Under normal conditions, one station generates one outbound
session per summary interval plus the occasional alert session. That's a tiny
fraction of Notecard for Skylo's included 500 MB of cellular data. For stations
running over satellite, it's a noticeable but manageable share of the bundled 10
KB satellite allocation.
Retry and error handling
- Env-var fetch failure.
fetchEnvOverridesusesrequestAndResponseand returns quietly on a NULL response. Ifenv.getfails on a given wake, the last valid threshold values stay in RAM. No alert goes out, and the system keeps sampling with the thresholds it already had. - Alert deduplication. Each alert has its own cycle-based cooldown counter (30 cycles × 60 seconds = 30 minutes), so a fault that sticks around doesn't page the on-call engineer over and over. Alert types re-arm independently, which means a pump fail-to-start and a float-switch alarm can page at the same time.
- State after a reset. Application state lives in RAM and survives every
STOP2 sleep. Only a power cycle or reset clears it. When that happens,
setup()zero-initializes all state and re-runshub.setandnote.template, both of which are idempotent at the Notecard. The summary-window accumulators reset to zero, so you lose at most one summary window of data. note.addretry and accumulator preservation.sendAlertarms an alert's cooldown only after a confirmed success. A failed send leaves the cooldown at zero, and the alert is retried on the next 60-second cycle.sendSummarychecks the return value ofnotecard.sendRequest(), and the caller clears the summary accumulators only on success. If the Notecard is temporarily unreachable, the window's data is kept and the send is retried on the next cycle.note.templateregistration is retried on every wake until both templates succeed (seeg_state.templates_registered).sendRequest()returnsfalseboth when no response comes back and when the response carries anerrfield, so a request the Notecard rejects counts as a failed send too. The firmware just doesn't log the error text. If you need it, switch torequestAndResponse()and readerryourself.- Partial sensor open/short detection. The firmware fault-checks the level
sensor path at the ADC. An averaged count below
LEVEL_ADC_FAULT_LO(≈ 645) flags an open loop (sensor unplugged, broken vented cable, lost loop power), and a count aboveLEVEL_ADC_FAULT_HI(≈ 3823) flags a shorted loop or severe overpressure. Either way, the firmware emitsLEVEL_INVALID_SENTINEL(-9999) instead of a clamped 0 % / 100 % reading and incrementslevel_faultsin the hourly summary. The CT channels get similar guards. A bias point outside[CT_BIAS_MIN, CT_BIAS_MAX](1024–3072 counts) flags a broken bias-divider resistor or a CT terminal shorted to a rail, and any sample withinCT_RAIL_MARGINof 0 or 4095 during the RMS window flags rail saturation (shorted secondary, badly over-ranged input). Faulted CT samples emitCT_INVALID_SENTINELand incrementct1_faults/ct2_faults. The remaining gap is an open CT secondary winding with an intact bias divider. That reads ≈ Vref/2 with near-zero variance, which software can't tell apart from a pump that's legitimately idle. Treat a sustainedct*_faultscount, or an unexplained high-water alarm with zero reported pump current, as a cue for a physical inspection.
Key code snippet 1: CT RMS current measurement
The CT read in readPumpAmps() happens in two phases. First it establishes the
bias point, and then it computes the RMS of the AC deviation over
CT_RMS_SAMPLES (1024) reads. Each phase has its own fault check. A bias
outside CT_BIAS_MIN–CT_BIAS_MAX or any sample within CT_RAIL_MARGIN of
either ADC rail marks the reading invalid and returns CT_INVALID_SENTINEL.
CT_AMPS_PER_VOLT is 30, from the SCT-013-030's 30 A per 1 V RMS rating.
// Step 1: measure DC bias (mid-rail, ideally 2048 counts at 12-bit)
long bias_sum = 0;
for (int i = 0; i < CT_BIAS_SAMPLES; i++) {
bias_sum += analogRead(pin);
}
float bias = (float)bias_sum / (float)CT_BIAS_SAMPLES;
// Bias validity check: a reading outside [CT_BIAS_MIN..CT_BIAS_MAX]
// indicates a broken bias network or a shorted CT terminal.
if (bias < (float)CT_BIAS_MIN || bias > (float)CT_BIAS_MAX) {
*valid_out = false;
return CT_INVALID_SENTINEL;
}
// Step 2: RMS integration over CT_RMS_SAMPLES.
// ...
double sq_sum = 0.0;
bool rail_hit = false;
for (int i = 0; i < CT_RMS_SAMPLES; i++) {
int raw = analogRead(pin);
if (raw < CT_RAIL_MARGIN || raw > (4095 - CT_RAIL_MARGIN)) {
rail_hit = true;
}
float s = (float)raw - bias;
sq_sum += (double)s * s;
}
if (rail_hit) {
*valid_out = false;
return CT_INVALID_SENTINEL;
}
*valid_out = true;
// Convert counts → volts → amps
float v_rms = (float)sqrt(sq_sum / CT_RMS_SAMPLES) * (3.3f / 4095.0f);
return v_rms * CT_AMPS_PER_VOLT;Key code snippet 2: three-rule fault detection with cooldown
// Rule 2: Pump fail-to-start — wet well is deep but no pump is drawing
// current.
if (level_valid && ct1_valid && ct2_valid &&
!any_on && level_pct >= g_high_level_pct &&
g_state.cooldown_fail_to_start == 0) {
if (sendAlert("pump_fail_to_start", level_pct, p1_a, p2_a, float_sw)) {
g_state.alert_count++;
g_state.cooldown_fail_to_start = ALERT_COOLDOWN_CYCLES;
}
}
// Rule 3: Pump clog — pump running but level is still climbing.
if (level_valid && g_state.have_prev_level &&
g_state.prev_any_on && any_on) {
float delta = level_pct - g_state.prev_level_pct;
if (delta >= g_rising_rate_pct && g_state.cooldown_clog == 0) {
if (sendAlert("pump_clog", level_pct, p1_a, p2_a, float_sw)) {
g_state.alert_count++;
g_state.cooldown_clog = ALERT_COOLDOWN_CYCLES;
}
}
}Key code snippet 3: immediate-sync alert
sync:true tells the Notecard to skip the hourly outbound window and wake the
radio right away, which is what you want for a fault event where minutes matter.
sendAlert() returns the result of sendRequest(), and the rules in snippet 2
arm the cooldown only when it's true, so a failed send is retried on the next
cycle instead of being suppressed for 30 minutes.
J *req = notecard.newRequest("note.add");
JAddStringToObject(req, "file", NOTEFILE_ALERT);
JAddBoolToObject(req, "sync", true); // bypass outbound interval; wake radio now
J *body = JAddObjectToObject(req, "body");
JAddStringToObject(body, "alert", type);
JAddNumberToObject(body, "level_pct", level_pct); // may be LEVEL_INVALID_SENTINEL
JAddNumberToObject(body, "pump1_amps", p1_a);
JAddNumberToObject(body, "pump2_amps", p2_a);
JAddBoolToObject(body, "float_sw", float_sw);
bool ok = notecard.sendRequest(req);
if (!ok) {
debugSerial.print("[ALERT] note.add failed for: "); debugSerial.println(type);
} else {
debugSerial.print("[ALERT] "); debugSerial.println(type);
}
return ok;8. Data Flow
Collected (every 60 seconds): wet-well fill level (%), pump 1 RMS current (A), pump 2 RMS current (A), and float switch state.
Summarized (every 60 minutes): instantaneous and average level, average current per pump, per-pump runtime minutes in the window, float switch state, and the alert count for the window.
Transmitted:
lift_alert.qo: sent immediately withsync:truewhenever a rule trips. Each alert carries the triggering level, both pump currents, and the float switch state, so the operator can judge severity without waiting for the next summary.lift_summary.qo: queued hourly and sent in the Notecard's next outbound cellular session (60 minutes after the previous one by default). That's one Note per hour per station, or 24 Notes per day.
Alerts trigger on:
high_water_alarm: the float switch contact closes. This is the highest priority, because it's hardware-level confirmation that the wet well is dangerously full, whatever the level sensor says.pump_fail_to_start: the wet-well level is at or abovehigh_level_pctand no pump is drawing current abovepump_on_amps. That points to a pump that isn't responding to its float control signal or has a failed contactor.pump_clog: at least one pump is drawing current (≥pump_on_amps), but the wet-well level is rising at ≥rising_rate_pctper cycle. The pump isn't moving water well, which fits a discharge obstruction, a failed or partially closed check valve, a worn impeller, or high inflow beyond the pump's capacity.
Routed: lift_alert.qo goes to a real-time notification channel, and
lift_summary.qo goes to a long-term store. Notehub's project routes don't need
any filter logic, because splitting the Notefiles at the source already does the
filtering.
9. Validation and Testing
Expected steady-state. A properly functioning lift station in normal
operation sends one lift_summary.qo Note per summary interval (60 minutes by
default) and no lift_alert.qo Notes. The summary should show level_avg_pct
well below high_level_pct, alert_count: 0, and at least one pump with
non-zero runtime in the window. On lead/lag stations it's common for one pump to
carry the whole load in a quiet hour, so zero runtime on the lag pump for a
single interval is normal and not an alarm condition.
Checking events in Notehub. After flashing and powering the board:
- Open Notehub > Devices.
- Click your Notecard. The Events tab shows every
lift_alert.qoandlift_summary.qoNote it has received. - Click any event to expand its JSON payload and inspect the body fields (level, current, alert type, fault counts).
Bench fault simulation. The firmware clamps high_level_pct to a minimum of
1.0 % and rising_rate_pct to a minimum of 0.1 %, so setting either one
to 0.0 in Notehub does nothing, and the firmware keeps its last valid value.
For a guaranteed trigger on the bench, use the compile-time test flags below, or
follow the hardware-assisted procedures:
pump_fail_to_startwith a compile-time flag (recommended). Rebuild the firmware with-DBENCH_FORCE_LEVEL_PCT=50.0in your build flags. In the Arduino IDE, add#define BENCH_FORCE_LEVEL_PCT 50.0at the top of the sketch, or witharduino-cli, pass--build-property "compiler.cpp.extra_flags=-DBENCH_FORCE_LEVEL_PCT=50.0". The macro skips the ADC and injects a synthetic 50 % level reading on every cycle. Withhigh_level_pctset to1.0in the Notehub env vars and both CT channels wired with their bias circuits (the rule needs two valid CT readings) but not drawing current abovepump_on_amps, the rule fires on the next sample. A[BENCH]line in the debug log (ST-LINK virtual COM port) confirms the override is active. Remove the flag before flashing to a deployed station.pump_fail_to_startwith a hardware-assisted bench alternative. Sethigh_level_pctto1.0. Inject a mid-range voltage on A0 with a 10 kΩ / 10 kΩ resistor divider from +3V3 to GND. Tap the midpoint (1.65 V) to A0 with the 150 Ω shunt disconnected from GND, since the shunt would otherwise pull the divider down to about 50 mV. That gives ≈ 2048 ADC counts, or ≈ 44 % fill level. With A1 and A2 reading near zero (CTs clamped on a dead conductor), the rule fires on the next cycle after Notehub pushes the env-var update.pump_clogwith a compile-time flag (recommended). Rebuild with-DBENCH_CLOG_DELTA=2.0. InsiderunDetectionCycle(), the macro swaps the measured level difference for a synthetic +2.0 %/cycle delta. The rule fires oncerising_rate_pctis at its default 2.0 % and a CT reads abovepump_on_ampsfor two consecutive cycles (clamp both CT jaws on a live conductor), with a valid level reading on both cycles (from a real sensor, the A0 divider above, orBENCH_FORCE_LEVEL_PCT). Remove the flag before flashing to a deployed station.high_water_alarm: briefly short pin D2 to GND (with a jumper wire or a bench push-button to GND) to simulate the float switch contact closing. You don't need an env-var change or a firmware rebuild.
Power validation with Mojo. During a bench run, put the Mojo inline on the 5 V rail feeding the Notecarrier CX. Mojo measures the whole Notecarrier subsystem (Notecard + STM32 host + carrier regulators), not just the Notecard. Here's the approximate draw in each state at the 5 V rail (from the low-power design guide):
Notecard for Skylo's draw during a session depends on which radio is active, but the idle and host-active states are the same on any network. Cellular and satellite sessions are similar in size (~250 mA average from the onboard BG95-S5 modem), with brief higher peaks. A 2G transmit burst can pull nearly 2 A for a few milliseconds.
| Operating state | Notecard for Skylo | STM32 host | 5 V rail total |
|---|---|---|---|
| Deep sleep (host in STOP2, Notecard idle) | ~8 µA | ~1–2 µA | ~10 µA |
| Host active for sensor reads + I²C (~300 milliseconds per 60-second cycle) | ~8 µA | ~20–30 mA | ~20–30 mA |
| Network session only, host asleep (batched summary), on cellular or satellite | ~250 mA avg, brief multi-hundred-mA (up to ~2 A for a few ms) peaks | ~1–2 µA | ~250 mA avg |
Host active + immediate network session (alert sync:true) | ~250 mA avg | ~20–30 mA | ~270 mA avg |
24-hour energy budget (cellular operation, powered continuously from the 120 VAC supply, illustrative numbers). Network session length dominates the budget and varies with signal conditions, and satellite sessions land in the same range:
- Idle: ~10 µA × ~24 h ≈ ~0.25 mAh/day (Notecard idle plus carrier-board quiescent draw, over the ~99.5% of the day the host spends in STOP2).
- Host wakes: ~25 mA × 0.3 seconds × 60 wakes/hr × 24 hours ≈ 10,800 mA·s ≈ ~3 mAh/day (10,800 mA·s ÷ 3600 s/hr).
- Hourly outbound session: ~250 mA avg × ~10 seconds × 24 sessions/day ≈ 60,000 mA·s ≈ ~17 mAh/day. This assumes a typical LTE-M session including network registration. On a clean network, a queued-template summary often lands in the 5–15 seconds range, while marginal coverage or a fallback to satellite stretches it well past 30 seconds.
- Occasional alerts (assume 2/day): ~250 mA × ~10 seconds × 2 ≈ ~1.4 mAh/day.
Total: roughly ~22 mAh/day in steady state, with hourly cellular sessions dominating the budget. At weak-signal sites where the modem camps in registration, that can easily run 2–3× higher. The MeanWell HDR-15-5 (85–264 VAC input, 5 VDC 2.4 A output) supplies continuous power from the station's 120 VAC control supply, so energy isn't what limits the deployment. Checking these current draws with Mojo still confirms the sleep/wake architecture works, and it turns up signal-quality problems early.
Expected Mojo trace over 24 hours:
- Dominant pattern: 60-second intervals of near-zero current (~10 µA, host and Notecard idle).
- Every 60 seconds: a 300 millisecond spike at ~25 mA (host wakes, reads sensors, issues I²C).
- Every ~60 minutes: a longer spike at ~250–350 mA lasting ~5–15 seconds (the Notecard's modem session for the summary).
- Occasional taller/longer spikes: alerts firing with
sync:true(timing varies with fault events).
Troubleshooting constant mid-level draw: If Mojo shows a steady ~10–50 mA
instead of this spike pattern, the host isn't entering STOP2. Check the
ATTN→D5 jumper. Without it, the firmware sees that ATTN never went low,
waits out the interval awake, and retries. Also confirm the firmware was built
with the usb=none board option, since with the USB CDC stack enabled the host
leaves STOP2 within a millisecond. The HST/NC DIP switch on the Notecarrier CX
only selects which device connects to the USB-C port, and it has no effect on
host sleep. Mojo is the fastest way to confirm the sleep architecture works
before the unit goes underground.
10. Troubleshooting
Firmware won't compile. Make sure you have the right board package and libraries:
arduino-cli core install STMicroelectronics:stm32
arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC"Notecard not appearing in Notehub. Check the firmware debug log:
- Open a serial terminal at 115200 baud on the ST-LINK virtual COM port (the Notecarrier CX debug jack, covered in Section 7).
- Power the Notecarrier CX and watch for the
[CONFIG] hub.setline and a[SENSOR]line on each wake. - Check that
PRODUCT_UIDinlift_station_monitor_helpers.hisn't empty and matches your Notehub project UID. - If
hub.setsucceeds but the device still doesn't appear, the Notecard may not have cellular or WiFi coverage. Check the antenna connection and signal strength (send acard.wirelessrequest over serial with the Notecard CLI).
No events appearing in Notehub after 5 minutes. Check:
- Network availability. Make sure the Notecard has cellular or WiFi
access, using the serial log or
card.wireless. - Event payload size. When Notecard for Skylo is transmitting over
satellite (NTN), Notes over 256 bytes are silently dropped. Check the serial
log for
note.addsuccess/failure status. - Outbound sync window. Summaries are queued and synced every 60 minutes
by default. Alerts go out immediately with
sync:true, so if you've triggered one and the Notecard has coverage, it should show up within 60 seconds. If it doesn't, check the serial log forsendAlertandnote.addoutput.
Constant non-zero current when powered (Mojo shows ~10–50 mA instead of spike pattern). The host isn't entering STOP2:
- Make sure the
ATTN→D5jumper on the Notecarrier CX header is in place (see Section 5), and leaveENunconnected. - Confirm the firmware was built with USB support set to None
(
usb=none). With the USB CDC stack enabled, the USB wakeup interrupt kicks the host out of STOP2 immediately. - If it's still stuck, try a cold power-off (disconnect 120 VAC for 10 seconds) and then reconnect.
Sensors read all-zero or invalid values. Check:
- Level sensor (A0): Make sure the 150 Ω shunt resistor is wired in series between the sensor's negative wire and GND. The ADC should read 745–3723 counts (0–100 %). A reading <745 means the loop is open or the shunt is missing or damaged.
- CT channels (A1, A2): Check that the 10 kΩ bias divider and 10 µF decoupling cap are installed correctly. The DC bias should read around 2048 counts (VREF/2 ≈ 1.65 V). If the bias is outside 1024–3072, the resistors are mismatched or the CT is shorted.
- Float switch (D2): The pin is
INPUT_PULLUP, so logic LOW (GND) means active (alarm). To test, jumper D2 to GND.
11. Limitations and Next Steps
This reference design covers most typical municipal lift stations, but it deliberately stops short of a full SCADA replacement.
Simplified for the POC
Each simplification below is a scope choice. Once a real utility starts running the monitor, a production deployment will want to add a sensor, a field-tunable setting, or an integration at each of these spots.
Level sensor accuracy assumes a clean, straight wet well. The firmware maps ADC counts linearly to % fill with the transducer's pressure-to-level formula, which assumes a uniform cross-section. Wet wells with irregular geometry or foaming conditions will read inaccurately. The production fix is a field-calibrated offset set through an environment variable.
CT range and motor type. The SCT-013-030 (30 A) suits single-phase motors up to ~5 HP at 230 V. Single-phase motors over 5 HP, and any three-phase motor, need a higher-ratio CT (for example, the SCT-013-060, 60 A / 1 V). Three-phase installations also need one CT per phase. The current sketch reads one CT per pump, which works as a running/not-running indicator on a single leg but does not produce true 3-phase RMS power.
Fail-to-start detection is level-threshold only. The firmware doesn't know
what level the pump float controls are actually set to. The high_level_pct
threshold is the firmware's approximation of the hardware float-control
setpoint, and the two may not match unless you calibrate them after
installation.
No discharge pressure or flow measurement. The pump_clog rule fires on
level-rising-while-running. A clog always produces that pattern, but so do
genuine high-inflow conditions (heavy rain) and times when both pumps are
running and inflow exceeds their combined capacity. A production deployment
would do well to add a discharge pressure sensor that can tell "pump is pumping
but the line is blocked" apart from "pump is pumping but inflow is just
overwhelming."
No SCADA integration. The sketch runs standalone. Most municipal lift stations already have a local RTU or telemetry unit. Integrating with it, whether by reading dry contacts from the existing SCADA outputs or by making the Notecard's data available to the local RTU, is outside the scope of this POC.
Satellite (NTN) operation caveats. The firmware is the same everywhere, but
once Notecard for Skylo falls back to the satellite link, it comes with real
operational differences. Alert and summary Notes queue in the Notecard's local
store and sync on the satellite session schedule. sync:true Notes get priority
for the next available session, but finding a Skylo satellite and completing the
transmission takes several minutes, not seconds. Each Note has to stay within
the NTN 256-byte maximum, and the satellite network silently drops anything
larger without sending it. Inbound syncs (env-var pulls) each use about 50 bytes
of the 10 KB bundled satellite data allocation, so the default 2-hour inbound
cadence costs roughly 600 bytes/day. The satellite link only works if the
Skylo-certified main antenna is mounted outdoors with an unobstructed sky view
(in the northern hemisphere, an unobstructed view of the southern sky), so
stations with the enclosure entirely below grade will need an above-grade cable
run. Satellite operation is opt-in at the Notecard level. The firmware turns it
on by setting card.transport to wifi-cell-ntn. Without an ntn transport
mode, the board would stay on cellular/WiFi only and never reach the satellite
network. Section 2 and Section
9 have the full breakdown.
Mojo is bench-validation only. The firmware doesn't read the Mojo's coulomb
counter register over Qwiic. If fleet-level energy telemetry would be useful,
adding a mojo_mah field to the hourly summary is a simple extension using the
LTC2959 register map.
Production Next Steps
After a utility has the basic monitor running, these extensions are the logical next moves. They're listed roughly from most immediately useful to most dependent on integration work.
Per-station level calibration comes first. A level_offset_pct environment
variable handles wet-well geometry corrections and gets applied after the
ADC-to-percent conversion.
Three-phase current support adds A3 for the third CT leg on 3-phase pumps
and sums the squared contributions for a true 3-phase RMS reading.
A discharge pressure sensor on I²C (for example, a 4–20 mA → I²C transducer) would tell a clog apart from high inflow and cut down on false positives from storm events.
Notecard Outboard DFU enables over-the-air host firmware updates (on cellular/WiFi only), so threshold-algorithm improvements reach the fleet without a truck roll to each underground vault.
Pump cycle-count tracking logs each pump start and stop (the transition from
below to above pump_on_amps) to build up lifetime cycle counts and flag motors
nearing their rated duty-cycle limits.
Integration with the municipal SCADA or CMMS closes the loop. A
lift_alert.qo Notehub route can create a CMMS work order automatically, so the
on-call response starts the moment the Notecard transmits instead of when an
engineer reads an SMS.
12. Summary
The on-call engineer who used to first hear about a sanitary sewer overflow from a citizen complaint on Monday morning now gets paged at 2 AM Saturday. Maybe the wet well is rising while a pump runs, or a float switch has closed, or a pump that should be on isn't. Three sensors, one Notecard for Skylo with cellular, satellite, and WiFi on a single module, and three rules running on a sleeping host turn an opaque concrete vault into a station that calls for help before it overflows. The exact same hardware and firmware go into the station three blocks from city hall and the one at the edge of the service territory. They reach cellular where there's a tower and the Skylo satellite network where there isn't, without swapping a thing.