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

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

Municipal Wastewater Lift Station Monitor

Municipal Wastewater Lift Station Monitor banner
note

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

Building with an AI assistant? Point it at this accelerator's firmware in firmware/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

System architecture: wet well sensors (level transducer, two CTs, float) → above-grade enclosure with Notecarrier CX → Notecard for Skylo (cellular / WiFi / satellite) → Notehub → on-call/analytics

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.

  1. Create a Notehub project at notehub.io and copy its ProductUID.
  2. Flash the firmware:
    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
    (Replace /dev/ttyACM0 with your Notecarrier serial port. On macOS use /dev/tty.usbmodem*, and on Windows use COM*.) The usb=none board option turns off the USB CDC stack, which has to be off for the host to sleep (see Section 7). The sketch.yaml in that directory sets the same board options.
  3. 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 min line and a [SENSOR] reading on every wake.
  4. Check Notehub Devices. Your Notecard shows up within 60 seconds. Click it to see lift_alert.qo and lift_summary.qo events in the Events panel (the first summary arrives after about an hour).
  5. 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

PartQtyRationale
Notecarrier CX1Integrated 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)1One 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 Mojo1Coulomb 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 cable1Submersible 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)1Converts 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)2Non-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)2The 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)4A 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)2Low-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 switch1Sewage-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-rail1AC–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-rail1AC–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)1Connects 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 datasheet1Connects 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″1Polycarbonate-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

Wiring: 4–20 mA loop with 150 Ω shunt → A0; dual SCT-013 CTs with 10 kΩ/10 kΩ bias and 10 µF cap → A1/A2; dedicated Gems float → D2 PULLUP; ATTN → D5 jumper so the Notecard can wake the host from STOP2; HDR-15-24 and HDR-15-5 from 120 VAC

warning

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 V from 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 +3V3 to GND. The junction (midpoint) is the bias node, at VREF/2 ≈ 1.65 V. Put a 10 µF capacitor from the bias node to GND to 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 and A1, so A1 sees 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 D2 as INPUT_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

  1. Create a project. Sign up at notehub.io and create a project. Copy the ProductUID and paste it into the firmware as PRODUCT_UID (in lift_station_monitor_helpers.h). Rebuild and flash.
  2. 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.

VariableDefaultPurpose
pump_on_amps3.0Current draw (A) at or above which a pump counts as running. Adjust for larger or smaller motors.
high_level_pct85.0Wet-well fill level (%) at which the fail-to-start check kicks in. Set it below the float switch trip point.
rising_rate_pct2.0Level rise (% per 60-second sample) while a pump is running that triggers a clog alert.
summary_interval_min60Minutes 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_min120Minutes 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.qo to your real-time on-call service (Notehub supports HTTP, MQTT, AWS SNS, Azure Event Hubs, Slack, PagerDuty, and others).
  • Summary route: Route lift_summary.qo to 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/:

FileRole
lift_station_monitor.inoMain sketch: setup(), loop(), runSampleCycle(), runDetectionCycle(), sendAlert(), sendSummary()
lift_station_monitor_helpers.hCompile-time constants, AppState struct definition, extern globals, and helper-function prototypes
lift_station_monitor_helpers.cppHelper implementations: parsing, clamping, notecardConfigure(), defineTemplates(), fetchEnvOverrides(), applyHubSetIfChanged(), sensor reads
cx_sleep.hHost 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-arduino library). Install it from the Arduino Library Manager or with arduino-cli lib install "Blues Wireless Notecard".
  • STM32duino Low Power (and its dependency STM32duino RTC), also from the Library Manager. It provides the STOP2 entry and ATTN-pin wake that cx_sleep.h uses.

Build settings: The Notecarrier CX's host is the same STM32L433 as the Blues Cygnet, so select Blues boards → Cygnet (pnum=CYGNET) and set USB support (if available) to None (usb=none). If the USB CDC stack is enabled and no USB host is attached (the Notecarrier CX USB-C switch in NC, or 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

ResponsibilityFunction
Notecard configuration (hub.set, card.transport wifi-cell-ntn for cellular→satellite fallback, accelerometer disable)notecardConfigure
Notefile template registrationdefineTemplates
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 % conversionreadLevelPct
CT-based pump current measurementreadPumpAmps
Float switch debounced readreadFloatSwitch
Three-rule fault detection with cooldownsrunDetectionCycle
Immediate-sync alert emissionsendAlert
Hourly aggregated summary emissionsendSummary
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() calls analogReadResolution(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 1024 analogRead() 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 Arduino analogRead() 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, so I_rms = V_rms × 30. The firmware reads both CT channels every cycle and counts a pump as running when its current exceeds pump_on_amps.

  • Float switch. A 5-reading majority-vote debounce (50 milliseconds total) filters out contact bounce. The result is one boolean, true when the float switch indicates high water and false when 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 -9999 if 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. fetchEnvOverrides uses requestAndResponse and returns quietly on a NULL response. If env.get fails 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-runs hub.set and note.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.add retry and accumulator preservation. sendAlert arms 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. sendSummary checks the return value of notecard.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.template registration is retried on every wake until both templates succeed (see g_state.templates_registered). sendRequest() returns false both when no response comes back and when the response carries an err field, 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 to requestAndResponse() and read err yourself.
  • 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 above LEVEL_ADC_FAULT_HI (≈ 3823) flags a shorted loop or severe overpressure. Either way, the firmware emits LEVEL_INVALID_SENTINEL (-9999) instead of a clamped 0 % / 100 % reading and increments level_faults in 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 within CT_RAIL_MARGIN of 0 or 4095 during the RMS window flags rail saturation (shorted secondary, badly over-ranged input). Faulted CT samples emit CT_INVALID_SENTINEL and increment ct1_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 sustained ct*_faults count, 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

Data flow: 60s sample of level/CT1/CT2/float → three rules (high_water_alarm, pump_fail_to_start, pump_clog) with 30-min cooldowns → lift_alert.qo (sync:true) and lift_summary.qo (hourly) → Notehub routes

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 with sync:true whenever 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 above high_level_pct and no pump is drawing current above pump_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_pct per 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:

  1. Open Notehub > Devices.
  2. Click your Notecard. The Events tab shows every lift_alert.qo and lift_summary.qo Note it has received.
  3. 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_start with a compile-time flag (recommended). Rebuild the firmware with -DBENCH_FORCE_LEVEL_PCT=50.0 in your build flags. In the Arduino IDE, add #define BENCH_FORCE_LEVEL_PCT 50.0 at the top of the sketch, or with arduino-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. With high_level_pct set to 1.0 in the Notehub env vars and both CT channels wired with their bias circuits (the rule needs two valid CT readings) but not drawing current above pump_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_start with a hardware-assisted bench alternative. Set high_level_pct to 1.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_clog with a compile-time flag (recommended). Rebuild with -DBENCH_CLOG_DELTA=2.0. Inside runDetectionCycle(), the macro swaps the measured level difference for a synthetic +2.0 %/cycle delta. The rule fires once rising_rate_pct is at its default 2.0 % and a CT reads above pump_on_amps for 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, or BENCH_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 stateNotecard for SkyloSTM32 host5 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:

  1. Open a serial terminal at 115200 baud on the ST-LINK virtual COM port (the Notecarrier CX debug jack, covered in Section 7).
  2. Power the Notecarrier CX and watch for the [CONFIG] hub.set line and a [SENSOR] line on each wake.
  3. Check that PRODUCT_UID in lift_station_monitor_helpers.h isn't empty and matches your Notehub project UID.
  4. If hub.set succeeds but the device still doesn't appear, the Notecard may not have cellular or WiFi coverage. Check the antenna connection and signal strength (send a card.wireless request over serial with the Notecard CLI).

No events appearing in Notehub after 5 minutes. Check:

  1. Network availability. Make sure the Notecard has cellular or WiFi access, using the serial log or card.wireless.
  2. 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.add success/failure status.
  3. 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 for sendAlert and note.add output.

Constant non-zero current when powered (Mojo shows ~10–50 mA instead of spike pattern). The host isn't entering STOP2:

  1. Make sure the ATTN→D5 jumper on the Notecarrier CX header is in place (see Section 5), and leave EN unconnected.
  2. 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.
  3. 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:

  1. 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.
  2. 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.
  3. 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.

On This Page

  • 1. Project Overview
  • 2. System Architecture
    • Satellite (NTN) operation considerations
  • 3. Technical Summary
  • 4. Hardware Requirements
  • 5. Wiring and Assembly
  • 6. Notehub Setup
    • 6.1 Project and Device Claim
    • 6.2 Environment Variables for Threshold Tuning
    • 6.3 Routing Events to the Outside World
  • 7. Firmware Design
    • Modules
    • Sensor reading strategy
    • Event payload design
    • Low-power strategy
    • Retry and error handling
    • Key code snippet 1: CT RMS current measurement
    • Key code snippet 2: three-rule fault detection with cooldown
    • Key code snippet 3: immediate-sync alert
  • 8. Data Flow
  • 9. Validation and Testing
  • 10. Troubleshooting
  • 11. Limitations and Next Steps
    • Simplified for the POC
    • Production Next Steps
  • 12. Summary

Resources

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