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_rightHeavy Equipment Hours-of-Use & Utilization Tracker

Heavy Equipment Hours-of-Use & Utilization Tracker

Heavy Equipment Hours-of-Use & Utilization Tracker 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/equipment_hours_tracker/ so it can learn how the device works and help you adapt it to your hardware.

This project is a retrofit asset location tracking solution for excavators, generators, compactors, light towers, and any other mobile heavy equipment that a rental company or OEM needs to bill by the hour and service on schedule. A magnetically mounted, solar-trickle-charged enclosure uses a 3-axis accelerometer to pick up engine on/off transitions from their vibration signature. It keeps a software hour meter and reports location and utilization to the Blues Notehub cloud service over cellular or satellite, without a wiring harness, changes to the equipment, or a job-site network. Skylo-supported satellite fallback keeps the device reporting from remote pipeline corridors, open-pit mines, and wind-farm construction zones where terrestrial coverage runs thin. The hardware is a Notecarrier CX with a Notecard for Skylo and an external IMU (§4 has the BOM).

1. Project Overview

The problem. Rental heavy equipment earns its money by the hour. The excavator a contractor rented on Monday morning needs an accurate engine-hour count when it's billed on Friday afternoon. It also needs a warranty-hours check before its next delivery and a predictive-maintenance flag at each 250-hour service interval. Hardwired telematics (OBD-II interfaces, CAN-bus taps, hour-meter relays) work well on newly acquired machines. On older ones they're expensive to retrofit, installing them often takes the equipment out of service, and they can void the warranty when they involve getting into the engine control unit.

Vibration-based hour detection sidesteps the retrofit problem entirely. A small enclosure stuck magnetically to the equipment frame asks one question on every sample: is this machine's engine running right now? That's harder to answer than it sounds. An excavator riding to a job site on the back of a flatbed has its engine off, but to a simple threshold-based accelerometer, the truck's diesel vibration and road shock look a lot like engine idle. This project handles that with a two-parameter vibration signature. The root-mean-square (RMS) amplitude of the net acceleration residual measures how much activity there is, and the coefficient of variation (CV, or σ/μ) separates steady, periodic engine vibration from the irregular, bursty shock of transport. Engine idle gives a low CV and road vibration gives a high one. Together they sort the machine into three reliable states (engine running, in transport, and idle/stopped) without a single wire to the equipment.

Why Notecard for Skylo. This equipment moves from one customer job site to the next. Those sites have no WiFi, and cellular coverage there is often marginal or missing. Open-pit mines, remote pipeline corridors, and offshore wind-farm construction zones are typical. Notecard for Skylo (NOTE-NBGLWX) puts LTE-M, NB-IoT, GPRS, Skylo satellite NTN (non-terrestrial network), and WiFi fallback on a single M.2 module, so you don't need a separate satellite modem or a Starnote companion board. When cellular is available, Notes go out over LTE-M with the low latency and high throughput you'd expect. When the equipment sits at the bottom of a quarry or behind a ridge no tower can reach, the Notecard falls back on its own to a satellite uplink through Skylo's NTN service. The operator gets unbroken location and hours telemetry no matter what the site looks like, and the firmware never needs to know which network carried it.

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. The enclosure sticks magnetically to any ferrous chassis surface, such as a frame rail, a tool-box lid, or a battery tray. You don't drill holes, run a wiring harness, or need anything from the OEM. A small solar panel epoxied or bolted to the top of the enclosure trickle-charges a 2000 mAh LiPo through the Notecarrier CX's built-in solar charging circuit. The equipment can sit unused on a lot for weeks while the tracker holds its charge. When the operator fires up the machine on a remote site, the vibration classifier catches the engine start and the Notecard sends the telemetry over whatever network it can reach.

2. System Architecture

System architecture: LSM6DSOX accelerometer → Notecarrier CX with STM32 host and NOTE-NBGLWX → cellular or Skylo NTN satellite → Notehub → billing / CMMS / alerts

Device-side responsibilities. Three jobs happen on the equipment itself, and all of them have to fit in a 30-second wake budget. The Notecarrier CX's STM32 host wakes when the Notecard raises its ATTN pin at the end of a card.attn sleep. It reads the Adafruit LSM6DSOX accelerometer over I2C and grabs a 2-second burst of 3-axis samples at 104 Hz. The vibration classifier turns that burst into one of three states (IDLE, RUNNING, or TRANSPORT), feeds the hour-meter accumulator, and compares the result with the previous wake. A state change fires an event right away, and an elapsed summary window fires a summary Note. Between wakes the host sleeps in STM32 STOP2 (a few µA) with its RAM intact. The hour meter and event queue just stay in memory until the Notecard's ATTN pin wakes the host for the next sample.

Notecard responsibilities. Once the host hands off, Notecard for Skylo handles the network side. It queues Notes locally and opens cellular or satellite sessions on two cadences set in hub.set. A daily outbound sync carries the queued summaries, and an 8-hour inbound check-in (inbound: 480) pulls environment-variable updates from Notehub. After each state-change event lands in the queue, the firmware sends a separate hub.sync so the billing record doesn't sit waiting for the next scheduled outbound window. Periodic GPS location sampling (every 15 minutes) and ringfence setup both go through card.location.mode, and a separate card.location.track call turns on the 4-hour _track.qo heartbeat record. If you configure a ringfence through environment variables, the Notecard checks it on its own and emits a _track.qo event the moment the equipment leaves the job site. After the ringfence parameters are applied, the host doesn't have to do anything.

Notehub responsibilities. The Notecard's embedded global SIM handles cellular and Skylo NTN satellite sessions on supported carriers worldwide and delivers events to Notehub over the Internet. From there, Notehub ingests and stores them and applies your project-level routes. Operators never touch firmware to retune the fleet. Fleet-level environment variables let you adjust vibration thresholds and ringfence parameters from the web console without a truck roll, and Smart Fleets group devices by rental customer, equipment class, or geographic territory so routing and alerting can differ for each group.

Routing to the cloud (high level only). Notehub can route to HTTP, MQTT, AWS IoT Core, Azure IoT Hub, GCP Pub/Sub, Snowflake, and other destinations. State-change events (equip_event.qo) make sense to send to an on-call webhook or work-order system, and daily summaries (equip_summary.qo) fit a time-series historian or billing database. This project doesn't ship a specific downstream endpoint, so see the Notehub routing docs to set one up.

3. Technical Summary

  1. Create a Notehub project and copy your ProductUID.
  2. Wire up the bench: Notecarrier CX + NOTE-NBGLWX + LSM6DSOX on I2C, plus the ATTN→D5 jumper for host sleep (§5 has the pinout).
  3. In firmware/equipment_hours_tracker/equipment_hours_tracker_helpers.h, replace the empty #define PRODUCT_UID "" with your project's value (format: com.your-company.your-name:tracker).
  4. Install the board core: arduino-cli core install STMicroelectronics:stm32 --additional-urls https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
  5. Compile and upload (the full arduino-cli commands with your port are in §7.1).
  6. Open a serial monitor at 115200 baud on the ST-LINK virtual COM port from the Notecarrier CX debug jack (see §7.1). You should see [BOOT] Notecard configured, followed by [VIB] log lines every 30 seconds.
  7. Power the device, then open Notehub → your project → Events tab. _session.qo should show up within ~1 minute on cellular (longer on satellite, depending on sky view). Tap the enclosure to make some vibration, and state-change events will appear in equip_event.qo.

Here's a sample Note from this device:

{
  "file": "equip_summary.qo",
  "body": {
    "run_h":       6.25,
    "run_h_total": 1253.75,
    "transport_h": 1.08,
    "bat_v":       4.07,
    "fault_ct":    0
  }
}

4. Hardware Requirements

PartQtyRationale
Notecarrier CX1Integrated carrier with an onboard STM32L433 host. Solar/LiPo charging circuits, ATTN on the host header for sleep/wake, and I2C break-outs all fit on one compact board, so you don't need a separate MCU. The host sleeps in STOP2 between samples until the Notecard's ATTN pin wakes it.
Notecard for Skylo (NOTE-NBGLWX)1Single M.2 module that combines LTE-M/NB-IoT/GPRS, Skylo satellite NTN, WiFi, and GPS/GNSS. It's cellular-first and falls back to satellite automatically. Check the datasheet for regional coverage. The device comes with 500 MB of cellular data and 10 KB of satellite data.
Blues Mojo1Coulomb counter that sits inline on the load rail (LiPo → VBAT) to measure real consumption during bench bring-up. It confirms the host sleep/wake pattern and the whole-device energy draw. It only measures the load side, not the solar charger input, and you don't need it in production.
Adafruit LSM6DSOX 6-DoF IMU breakout (#4517)1ST LSM6DSOX 3-axis accelerometer + gyroscope on I2C. The firmware only uses the accelerometer axes and explicitly shuts the gyroscope down. The STEMMA QT/Qwiic connector turns the bench wiring into a single cable.
Adafruit 6V 1W solar panel (#3809)1The 6V output falls within the Notecarrier CX solar input range (4.5–7V). 1W is enough to trickle-charge a 2000 mAh LiPo with ≥2–3 hours of sun a day.
3.7V LiPo, 2000 mAh, JST-PH 2-pin (e.g. Adafruit #2011)1Single-cell LiPo for overnight and cloudy-day runtime. §9 estimates whole-device consumption at 65–120 mAh/day, which accounts for three daily inbound check-in sessions plus one outbound sync. At that rate, with an 80% practical depth of discharge, 2000 mAh gives you roughly 13–24 days of operation with no solar input at the default 30-second sample cadence. Real runtime depends on network type (satellite draws more than LTE-M), signal quality, and how often GNSS fixes succeed, so validate it with Mojo at your own site.
K&J Magnetics MM-C-36 neodymium pot magnet + MM-RC-36 rubber cover136 mm face, M6 × 1.0 external threaded stud, and 41 kg (90 lb) of pull force, far more than the ~250–350 g assembled enclosure weighs. The nickel-coated steel cup concentrates the flux, and the MM-RC-36 silicone over-cap (~1 mm thick) protects the chassis finish and cuts down on sliding. The max continuous operating temperature is 80 °C. That's fine for frame rails and battery trays, but keep it off exhaust and turbocharger surfaces. Regional alternative: any rubber-coated 36–42 mm M6-stud neodymium pot magnet rated ≥15 kg from RS Components or Grainger is a direct substitute.
IP67 weatherproof enclosure, ~130 × 80 × 55 mm (e.g. Hammond 1554B2GY)1Polycarbonate / ABS housing that protects the electronics from rain, jet wash, and splashing. GPS signals pass through the plastic lid, so the GPS antenna can go inside. It's sized to fit the Notecarrier CX, the LiPo, and cable management.
Passive GPS/GNSS patch antenna, u.FL connector (e.g. Adafruit #2460)1Attaches to the NOTE-NBGLWX GPS u.FL port. Must be passive, because the GPS port doesn't supply DC bias and the datasheet explicitly requires a passive antenna. The 50 mm pigtail is long enough to put the 9 mm × 9 mm ceramic patch against the inside face of the polycarbonate lid, and GPS L1 signals get through the plastic without a cable gland. Point the patch at the sky for the best fix rate.
u.FL-to-SMA female pigtail cable, ~150 mm (e.g. Adafruit #851)1Brings the NOTE-NBGLWX MAIN u.FL port through the enclosure wall to the Skylo-certified MAIN antenna. The u.FL end (female, clips onto the Notecard's MAIN port) stays inside the enclosure. The other end is an SMA female jack (panel-mount style). Pass it through a drilled or punched hole in the enclosure sidewall, secure it with the hex nut, and run a thin bead of silicone sealant under the nut face so the enclosure keeps its weatherproof rating at the penetration. The Skylo-certified MAIN antenna's SMA male plug screws straight onto this SMA female from outside the enclosure, so you don't need a separate bulkhead adapter. The MAIN port carries both LTE-M/NB-IoT/GPRS cellular and Skylo NTN satellite on a single antenna path (S-Band / L-Band, bands B23/B255/B256). The Skylo-certified MAIN antenna included with the NOTE-NBGLWX must be used on this port. A different antenna loses the Skylo certification, and Skylo may block the device from its network. Mount the MAIN antenna outside the enclosure with an unobstructed view of the sky. The NOTE-NBGLWX datasheet has the full antenna requirements.

Notecard for Skylo ships with a factory-provisioned SIM that includes global cellular and satellite service, 500 MB of cellular data, 10 KB of satellite data, and 10 years of connectivity in the device price, with no activation fees and no monthly minimum. That service comes with Notecard for Skylo only. The Notecarrier CX and Mojo are hardware, with no connectivity service attached.

5. Wiring and Assembly

Wiring: LSM6DSOX accelerometer on I²C; ATTN → D5 jumper so the Notecard can wake the host from STOP2; MAIN antenna for cellular/satellite plus GPS antenna via Notecard u.FL; solar 6V 1W → LiPo 2 Ah → Mojo (bench) → +VBAT

Notecard for Skylo seats in the Notecarrier CX's M.2 Key E slot. The Adafruit LSM6DSOX connects to the Notecarrier CX's I2C header. If you have the matching Qwiic/STEMMA QT cable, that's a single snap at each end. Otherwise, use four separate wires. The Mojo sits inline on the main power feed during bench validation.

Pin-by-pin:

  • +3V3 → LSM6DSOX VCC (3.3 V logic. The sensor is 3.3V only, so don't use 5V.)
  • GND → LSM6DSOX GND
  • SDA → LSM6DSOX SDA (the Notecarrier CX has 4.7 kΩ pull-ups on-board)
  • SCL → LSM6DSOX SCL
  • Solar JST → 6V solar panel (match the polarity marked on the connector, where red = positive)
  • LiPo JST → 3.7V 2000 mAh LiPo cell (JST-PH 2-pin, same polarity convention)
  • ATTN → D5 jumper (host sleep). Fit a short jumper between the ATTN and D5 pins on the Notecarrier CX. Both are on the same 16-pin header (ATTN at position 3 and D5 at position 14, per the Notecarrier CX datasheet header description). The Notecard pulls ATTN low while the host sleeps and raises it when the next sample is due. The rising edge on D5 wakes the host from STOP2, and loop() picks up where it left off. 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. Without the ATTN→D5 jumper, the firmware sees that ATTN never went low and waits out each 30-second interval awake. Sampling still works, but baseline current lands at ~5–10 mA instead of the ~20–60 µA target, and the solar/battery budget in §9 won't hold. Putting a Host to Sleep Between Sensor Readings walks through the full pattern, and the card.attn API reference covers the request itself.

Antenna placement. The NOTE-NBGLWX has exactly two u.FL antenna ports:

  • MAIN u.FL: carries both LTE-M/NB-IoT/GPRS cellular and Skylo NTN satellite on a single path. Clip the u.FL end of the pigtail (Adafruit #851 or an equivalent u.FL-to-SMA-female-jack cable) onto the MAIN u.FL port, then route the cable to a drilled or punched hole in the enclosure sidewall. Push the pigtail's SMA female jack (the panel-mount end) through the hole from inside, thread the hex nut on from outside to clamp it against the wall, and run a thin bead of silicone sealant under the nut face. That bead is the weatherproof seal at the RF penetration. From outside the enclosure, screw the SMA male plug of the Skylo-certified MAIN antenna (included with the NOTE-NBGLWX) onto the exposed SMA female, and point the antenna up with a clear, unobstructed view of the sky. Skylo NTN needs the antenna outdoors, so don't mount it under steel overhead panels or inside equipment compartments.
  • GPS u.FL: passive GNSS only, so don't connect an active (bias-powered) antenna. Attach the Adafruit #2460 passive GPS patch here and set it against the inside face of the polycarbonate lid. GPS L1 signals get through the plastic without a cable gland.

The MAIN antenna is Skylo-certified as a unit with the NOTE-NBGLWX. Don't swap in a different antenna, and don't add any extension cable beyond the short u.FL-to-SMA-female pigtail that brings the MAIN port through the enclosure wall.

Mojo placement (bench only). During bench measurements, Mojo replaces the normal battery feed for the whole test. It doesn't splice in alongside the LiPo. Before you insert Mojo, disconnect the LiPo from the Notecarrier CX's LiPo JST. With that JST unplugged, route power through the Mojo: bench supply (or the LiPo brought out on a bare lead) → Mojo BAT input → Mojo LOAD output → Notecarrier CX VBAT pad. If you leave the battery on the JST while also feeding VBAT through the Mojo, you create a parallel power path that skips the intended measurement point and risks backfeeding through the charger circuit. Disconnect the solar panel too. The solar input is a separate rail, and if it's left connected it will offset the load draw.

Accelerometer orientation. You can mount the LSM6DSOX any way up. The firmware computes the combined 3-axis magnitude and subtracts the 1g gravity baseline, so the axes don't need to line up with anything. What does matter is rigidity. Fix the breakout board solidly to the inside of the enclosure (hot glue or standoffs) so it vibrates with the equipment chassis instead of floating on its wires.

Mounting safety. Heavy equipment works in rough conditions. Treat the magnetic mount as the primary attachment, and add extra protection before it goes into the field:

  • Secondary retention. Add a safety tether, a short loop of steel wire or nylon strap tied between an M6 bolt on the enclosure and a nearby chassis fixture. If the magnet lets go of a painted or lightly corroded surface, the tether catches the enclosure before it drops into moving linkages or the undercarriage.
  • Surface temperature. Neodymium magnets start losing pull force above ~80 °C and can demagnetize permanently above ~150 °C. Don't mount on exhaust manifolds, muffler housings, turbocharger bodies, or any chassis surface that radiates heat from the engine bay. Frame rails, battery trays, and tool-box lids usually stay within a safe temperature range.
  • Moving parts and pinch zones. Stay clear of pivot pins, hydraulic cylinder clevises, boom linkages, and undercarriage rails, where the enclosure could get crushed or snagged as the machine articulates.
  • Washdown exposure. The enclosure must be IP67-rated, with every cable gland properly seated and the lid gasket undamaged. A pressure washer (common on construction sites) can exceed IP67 limits if it's aimed straight at a gland, so point the glands away from the main washdown direction.

6. Notehub Setup

  1. Create a project. Sign up at notehub.io and create a project. Copy the ProductUID (format: com.your-company.your-name:equipment-tracker).

  2. Set PRODUCT_UID in firmware. Open equipment_hours_tracker_helpers.h and replace the empty string on the #define PRODUCT_UID "" line with your value. You can also pass it as a build flag: -DPRODUCT_UID=\"com.your-company:tracker\".

  3. Claim the Notecard. Power up the assembled unit. The first time it reaches cellular or satellite, the Notecard claims itself to your project automatically, with no manual step. On cellular it shows up in the Devices tab within a few minutes. Give it longer on NTN while the Notecard sets up a satellite session, since Skylo availability depends on sky visibility and signal conditions, not on orbital pass timing.

  4. Create a Fleet. Fleets group devices that share configuration. The natural units here are rental customer, equipment class, and geographic territory, so you might have a fleet for compact equipment, one for large iron, or one per region. Smart Fleet rules can auto-assign devices by tag. Tag units in Notehub and they'll move into the right customer or site fleet as their assignments change.

  5. Set environment variables. In Notehub, go to Fleet → Environment (or Device → Environment to override a single unit). Every variable is optional, and the table shows the firmware defaults. The device pulls them on its next inbound sync (every 8 hours by default), with no reflash needed.

    VariableDefaultPurpose
    vib_run_mg15.0RMS activity threshold in milli-g. Anything below it is classified as IDLE, whatever the CV. Lower it for smooth engines, and raise it in high-vibration environments to avoid false positives.
    vib_cv_max0.40Coefficient of variation ceiling for "engine running." CV below this value (steady vibration) → RUNNING, and CV above it (bursty vibration) → TRANSPORT. Engine idle usually has a CV of 0.1–0.25, while truck-bed bounce runs 0.5–1.0+.
    summary_interval_min1440Minutes between daily summary Notes. Changing it also re-applies hub.set outbound so the Notecard's sync cadence matches. Values outside 60–44640 min are ignored.
    geofence_lat0.0Latitude of the job-site ringfence center (decimal degrees, –90 to 90). Must be set together with geofence_lon and geofence_radius_m. The default 0.0 means "not configured." A non-zero radius with lat/lon left at the 0,0 default won't turn on ringfencing, because the firmware only applies the ringfence once all three parameters are non-zero and in range.
    geofence_lon0.0Longitude of the job-site ringfence center (decimal degrees, –180 to 180). Must be set together with geofence_lat and geofence_radius_m.
    geofence_radius_m0Radius in meters. When all three ringfence parameters (geofence_lat, geofence_lon, geofence_radius_m) are non-zero and in range, the Notecard watches the device's position against the ringfence center and triggers a _track.qo event when the equipment leaves the site. The device picks up a change to any of the three values (even the radius alone) on its next wake and re-applies it. Non-zero values are clamped to 50–50,000 m. Setting it to 0 clears an active ringfence on the next wake, and does nothing if no ringfence was configured before.
  6. Configure routes. Add one route for equip_event.qo (state-change events: low volume, immediate delivery to billing or dispatch) and a second for equip_summary.qo (rolling summary window Notes, delivered to a utilization dashboard or historian). A third route on _track.qo handles the ringfence-exit location breadcrumbs. Because the Notefiles are split at the source, each one can be routed with its own urgency, and the routes themselves need no filter logic.

What to expect in Notehub

  • _session.qo: automatic Notecard housekeeping on every cellular or satellite session. If you see it, the radio is reaching Notehub. If it's missing while the Notecard's LED shows activity, the usual cause is a PRODUCT_UID mismatch or a coverage gap.

  • equip_event.qo: one per state transition, sent right away. The event field encodes the whole from→to transition, not just the new state:

    Transitionevent tagsession_min
    IDLE / TRANSPORT → RUNNINGengine_start0
    RUNNING → TRANSPORTtransport_startnon-zero, the duration of the engine run before transport
    IDLE → TRANSPORTtransport_start0
    RUNNING → IDLEengine_stopnon-zero, the duration of the run that just ended
    TRANSPORT → IDLEtransport_stop0

    Here's an example engine_stop body, which is one form of billing record. For RUNNING→TRANSPORT, see transport_start with session_min > 0:

    {
      "event":       "engine_stop",
      "session_min": 94.5,
      "run_h_total": 1253.75,
      "epoch":       1746023400
    }
  • equip_summary.qo: one per summary_interval_min (1440 minutes by default). Each one covers a rolling window since the last report, not a calendar day. The first summary after boot may cover a shorter window if the Notecard's clock wasn't valid yet at startup. Body:

    {
      "run_h":       6.2,
      "run_h_total": 1253.7,
      "transport_h": 1.1,
      "bat_v":       4.07,
      "fault_ct":    0
    }

    A bat_v below ~3.5V is a low-battery warning. transport_h gives you a second utilization metric, the time spent moving between sites. fault_ct counts the state-change events dropped to event-queue overflow since the last summary. Anything non-zero means the Notecard was unreachable for several wakes in a row.

  • _track.qo: the Notecard's automatic location heartbeat (every 4 hours) plus ringfence events. Firmware code doesn't generate these, because the Notecard's GPS subsystem owns them. Here's an example ringfence-exit event, from when the equipment leaves the configured job site:

    {
      "status":  "fence",
      "fence":   "-_ring",
      "motion":  3,
      "time":    1746023400,
      "voltage": 3.98
    }

    The key fields: status is "fence" only on ringfence transition Notes, which sets them apart from routine heartbeats. fence is "-_ring" when the equipment left the ringfence and "+_ring" when it came back. inside_fence is true on any _track.qo Note recorded inside the ringfence and left out of Notes recorded outside it. Notehub appends the fix as where_lat/where_lon. To alert when equipment leaves the ringfence, use status and fence in a Notehub route. See _track.qo for details.

7. Firmware Design

The firmware is split across four files that have to live together in the same Arduino sketch folder:

  • equipment_hours_tracker.ino: setup() / loop() entry points and per-wake sequencing.
  • equipment_hours_tracker_helpers.h: type definitions, constants, and function prototypes.
  • equipment_hours_tracker_helpers.cpp: all the helper-function implementations.
  • cx_sleep.h: host sleep for the Notecarrier CX, covering the card.attn request, STOP2 entry, and ATTN-pin wake.

The Arduino build tools compile every .ino, .h, and .cpp file in the sketch folder together, so you don't need a manual include path or a Makefile.

7.1 Installing and flashing

Dependencies:

  • Arduino core for STM32: stm32duino/Arduino_Core_STM32. Install it from the Arduino Boards Manager (search for "STM32 MCU based boards"). The Notecarrier CX's host uses the Cygnet board definition (see the build settings below).
  • Blues Wireless Notecard: note-arduino. Install it from the Arduino Library Manager (arduino-cli lib install "Blues Wireless Notecard"), or pick the latest stable version in the IDE Library Manager. The note-arduino releases page lists the available versions.
  • Adafruit LSM6DS: install it from the Library Manager (arduino-cli lib install "Adafruit LSM6DS"). This also pulls in the Adafruit Unified Sensor dependency if you don't have it yet.
  • STM32duino Low Power (and its dependency STM32duino RTC): stm32duino/STM32LowPower. Install it from the Library Manager (arduino-cli lib install "STM32duino Low Power" "STM32duino RTC"). 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 (battery power, or the Notecarrier CX USB-C switch in NC), the STM32's USB wakeup interrupt fires continuously and the host never stays in STOP2. Debug output goes to the LPUART on the Notecarrier CX debug jack instead, which an ST-LINK V3 exposes as a virtual COM port (see Serial Logging With STLINK).

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

Flashing with arduino-cli:

# One-time: install the STM32 board core
arduino-cli core install STMicroelectronics:stm32 \
    --additional-urls https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json

# Libraries
arduino-cli lib install "Blues Wireless Notecard" "Adafruit LSM6DS" "STM32duino Low Power" "STM32duino RTC"

# Find your device's USB port
arduino-cli board list

# Compile and upload (replace port with your value from 'board list' above).
# usb=none disables the USB CDC stack, which must be off for the host to sleep.
arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
    firmware/equipment_hours_tracker/
arduino-cli upload  -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none \
    -p /dev/cu.usbmodem* \
    firmware/equipment_hours_tracker/

Replace /dev/cu.usbmodem* with the port that arduino-cli board list shows. On Windows it's usually COMx, and on Linux it's /dev/ttyACM*. firmware/equipment_hours_tracker/sketch.yaml sets the same board options, so if you run arduino-cli compile in that directory without -b, it picks them up too.

After flashing, open a serial monitor at 115200 baud on the ST-LINK virtual COM port. You'll see [BOOT] Notecard configured at power-on, then a [VIB] line on each 30-second wake with the live RMS and CV readings.

7.2 Modules

ResponsibilityFunction
Notecard one-time initialization (hub.set, card.location.*, template registration)notecardConfigure, defineTemplates
Environment-variable fetch and hub.set re-applyfetchEnvOverrides
Ringfence reconfiguration on env-var changeapplyGeofenceIfChanged
Accelerometer burst sampling + RMS/CV classifierclassifyVibration
Hour-meter accumulation per state bucketupdateHourAccumulator
State-change event dequeue and delivery (Note.add + hub.sync, at-least-once retry)sendNextPendingEvent
Daily summary emissionsendSummary
Time and voltage from NotecardgetEpoch, getBatteryVoltage
Host sleep (STOP2 until the Notecard raises ATTN)goToSleep → cxSleepUntilAttn (cx_sleep.h)

7.3 Sensor reading strategy

On every wake, the host collects 208 accelerometer samples at 104 Hz (about 2 seconds of data) from the LSM6DSOX. For each sample it computes the 3-axis vector magnitude in m/s² and subtracts the 1g gravity baseline (~9.806 m/s²) to get the net dynamic acceleration. That residual is expressed in milli-g so it can be compared against the thresholds.

Then it computes two statistics over the 208-sample window:

  • RMS: the root-mean-square of the residual magnitudes. A low RMS means the equipment is sitting still and undisturbed, and a high RMS means there's energy in the measurement.
  • CV (coefficient of variation, σ/μ): the standard deviation normalized by the mean. A diesel engine idling at ~700 RPM produces ~11.7 Hz periodic vibration that's repetitive and steady in amplitude, so its CV is low (typically 0.10–0.25). Road shock through a truck chassis is aperiodic, with spikes at pothole crossings and relative quiet in between, so its CV is high (typically 0.50–1.0+). CV is what separates the two high-activity states.

Telling engine vibration apart from transport vibration was the main design challenge here. A simple amplitude threshold would fire on both. CV adds the second dimension that makes the problem solvable without training data or a heavy signal-processing stack.

7.4 Event payload design

Both Notefiles use compact templates. Notecard for Skylo requires them, because satellite data packets are capped at 256 bytes and every byte past the included 10 KB costs money. Compact templates store Notes on the Notecard as fixed-length binary records instead of free-form JSON, which cuts the wire size 3–5× compared to untemplated Notes.

equip_summary.qo (rolling summary window, queued):

{
  "file": "equip_summary.qo",
  "body": {
    "run_h":       6.25,
    "run_h_total": 1253.75,
    "transport_h": 1.08,
    "bat_v":       4.07,
    "fault_ct":    0
  }
}

equip_event.qo (queued on each state transition, with a separate hub.sync for prompt delivery):

{
  "file": "equip_event.qo",
  "body": {
    "event":       "engine_stop",
    "session_min": 94.5,
    "run_h_total": 1253.75,
    "epoch":       1746023400
  }
}

The Notecard automatically attaches the coordinates of its last valid GPS fix to both Note types, because the compact templates declare them with the _lat/_lon metadata keywords. No firmware code handles coordinates in the Note bodies.

7.5 Low-power strategy

Power matters on a solar-trickle-charged deployment, and the firmware pulls four levers to save it:

  1. Host asleep between samples. After each sample cycle, loop() calls cxSleepUntilAttn() (in cx_sleep.h). That function sends a card.attn sleep request for SAMPLE_INTERVAL_SEC seconds, waits for the Notecard to pull ATTN low, and puts the STM32 host into STOP2, where it draws a few microamps and keeps its RAM. When the interval is up, the Notecard raises ATTN. The rising edge on D5 (the jumper from §5) wakes the host, and execution picks up on the next line of loop(). Since the host resumes in place instead of cold-booting, the AppState struct (hour meter, pending-event ring buffer, and ringfence cache) just stays in RAM. Nothing gets serialized to the Notecard, and setup() runs only once per power-up. If the sleep request fails or ATTN never goes low (a missing jumper), loop() waits out the interval awake and tries again on the next cycle, so the 30-second cadence holds either way.

  2. Gyroscope shut down. sox.setGyroDataRate(LSM6DS_RATE_SHUTDOWN) turns the gyroscope off right after init. This application doesn't need it, and shutting it down saves ~0.5 mA during the 2-second sampling window.

  3. Notecard sync decoupled from samples. The Notecard runs in periodic mode with a daily outbound sync. Once the Notecard accepts a state-change event Note, the firmware sends a separate hub.sync to ask for prompt delivery outside the scheduled outbound window (usually 2–6 extra sessions per work day). Summaries queue up and go out in the daily outbound session. The firmware also sets an 8-hour inbound cadence (inbound: 480), so the Notecard checks Notehub for environment-variable updates three times a day. At default settings, that works out to roughly 4 radio sessions per day at minimum (1 outbound + 3 inbound), plus one more for each state-change event.

  4. GPS fix cadence. The firmware sets card.location.mode to periodic mode with a 900-second (15-minute) interval. In a typical deployment, GNSS fix attempts happen on or around that 15-minute cadence whenever the Notecard decides a new fix is needed. Each attempt usually draws 20–50 mA for 10–60 seconds. At up to 96 attempts a day, that can add roughly 15–30 mAh/day to the power budget, which is a real share of the total. If solar input is marginal or the device sits still much of the time, increase GPS_PERIOD_SECONDS to cut GNSS power consumption.

Notecard for Skylo idles at about 8–18 µA @ 5V between sessions (see the low-power firmware design guide). An LTE-M session for a small queued payload lasts on the order of tens of seconds at ~100–300 mA peak. The Blues low-power guide measured Skylo NTN satellite sessions at roughly 27 mAh per 12-hour period with hourly syncs, and a daily sync cadence will use a lot less. On the bench, expect a device at default settings to total about 65–120 mAh per 24 hours, depending on network type, signal quality, how often the state changes, and how often GNSS fixes succeed.

7.6 Retry and error handling

  • If sox.begin_I2C() fails (an unplugged or miswired accelerometer), the firmware calls goToSleep() right away instead of running with a broken sensor. The problem shows up in the serial monitor, and the firmware retries the IMU init on the next wake.
  • cxSleepUntilAttn() returns false when the Notecard doesn't accept the card.attn request, or when ATTN never goes low because the ATTN→D5 jumper is missing. In that case goToSleep() waits out the 30-second interval awake with delay() and tries again on the next cycle. Sampling keeps its normal cadence, at a much higher power cost.
  • The firmware checks the err field on both card.time and card.voltage responses before trusting the returned value. getEpoch() returns 0 if the Notecard doesn't have valid time yet (no cellular/GPS sync), and all epoch-dependent logic gates on now > 0. getBatteryVoltage() returns 0.0 on an error response. The summary Note carries that as a sentinel you can tell apart from a healthy ~3.6–4.2 V reading.
  • When summary_interval_min changes, the firmware ignores any value below 60 minutes and keeps the current interval. That keeps an operator from accidentally setting a 1-minute summary that would burn through the satellite data budget in hours.
  • State-change events aren't de-duplicated. If the classifier flips back and forth between RUNNING and TRANSPORT on rough terrain, every transition fires. If that causes alarm fatigue in a particular deployment, add a minimum-dwell counter as a production tuning step (for example, require 3 matching classifications in a row before accepting a new state).

7.7 Key code snippet 1: vibration classifier

The CV threshold is what lets the classifier tell an engine from transport. A 2-second burst at 104 Hz is fast enough to catch multiple engine-combustion cycles. A 700 RPM diesel fires every ~0.086 seconds, and 208 samples at 9.6 milliseconds spacing span ~23 combustion events.

const float n = (float)VIB_SAMPLE_COUNT;
float mean = sum / n;
float var = (sum_sq / n) - (mean * mean);
float cv = (mean > 1.0f) ? sqrtf(var > 0.0f ? var : 0.0f) / mean : 1.0f;
float rms = sqrtf(sum_sq / n);
// ...
if (rms < g_vib_run_mg)
    return ST_IDLE;
return (cv < g_vib_cv_max) ? ST_RUNNING : ST_TRANSPORT;

7.8 Key code snippet 2: compact template with GPS metadata

The _lat/_lon keywords in a compact template body tell the Notecard to embed its most recent GPS fix in the Note automatically, so the note.add calls never have to pass coordinates themselves.

How compact templates encode values (the numeric code parameter):

  • 14.1 = IEEE 754 4-byte float (32-bit). The .1 just marks the field as a float, not a number of decimal places. Good for large dynamic ranges (hours, totals, coordinates).
  • 12.1 = IEEE 754 2-byte float (16-bit). Good for smaller ranges with moderate precision (voltage, relative measurements).
  • 12 = 2-byte signed integer (16-bit), no decimal. Good for counters, small integers, or flags.
// equip_summary.qo — daily engine-hours summary
// 14.1 = 4-byte IEEE-754 float; 12.1 = 2-byte float; 12 = 2-byte integer
J *req = notecard.newRequest("note.template");
JAddStringToObject(req, "file", "equip_summary.qo");
JAddNumberToObject(req, "port", 50);
JAddStringToObject(req, "format", "compact");
J *body = JAddObjectToObject(req, "body");
JAddNumberToObject(body, "run_h", 14.1);
JAddNumberToObject(body, "run_h_total", 14.1);
JAddNumberToObject(body, "transport_h", 14.1);
JAddNumberToObject(body, "bat_v", 12.1);
JAddNumberToObject(body, "fault_ct", 12); // event-queue overflow counter (2-byte int)
JAddNumberToObject(body, "_lat", 14.1);
JAddNumberToObject(body, "_lon", 14.1);
if (!checkedRequest(req))
    return false;

The Notecard compact template documentation has the full list of format codes and their ranges.

7.9 Key code snippet 3: immediate event with session duration

The event tag comes from both the previous and the new state, not just the new one. That way engine_stop only ever marks a transition out of RUNNING (the billing record), and transport_stop marks the end of a transport leg:

const char *tag;
if      (new_state == ST_RUNNING)   tag = "engine_start";
else if (new_state == ST_TRANSPORT) tag = "transport_start";
else  /* ST_IDLE */                 tag = (g_s.prev_state == ST_RUNNING)
                                          ? "engine_stop" : "transport_stop";

session_min is non-zero on engine_stop (and on transport_start when the previous state was RUNNING), and it records how long the engine ran since the last engine_start. The first non-RUNNING transition uses up run_session_start and clears it to zero. That's why any later non-RUNNING transition (transport_stop, or any transition from a non-RUNNING prior state like IDLE → TRANSPORT) always emits session_min = 0.

Each transition goes into a small ring buffer first, with its tag, epoch, session_min, and run_h_total captured at that moment. sendNextPendingEvent() sends the oldest record (e) with note.add (without sync:true), making up to two attempts. Only once the Notecard accepts it does the firmware send a separate hub.sync to ask for prompt delivery. That keeps the network session decoupled from the Note acknowledgement:

for (int attempt = 0; attempt < 2; attempt++)
{
    J *req = notecard.newRequest("note.add");
    JAddStringToObject(req, "file", "equip_event.qo");
    J *body = JAddObjectToObject(req, "body");
    JAddStringToObject(body, "event", e.tag);
    JAddNumberToObject(body, "session_min", e.session_min);
    JAddNumberToObject(body, "run_h_total", e.run_h_total);
    JAddNumberToObject(body, "epoch", (JNUMBER)e.epoch); // transition timestamp
    J *rsp = notecard.requestAndResponse(req);
    // ...
    if (ok)
    {
        // ...
        // Request prompt delivery as a separate fire-and-forget call so
        // time-critical billing events are not held until the next
        // scheduled outbound window.  Delivery is ultimately the
        // Notecard's responsibility; this call failing is non-fatal.
        J *syncReq = notecard.newRequest("hub.sync");
        J *syncRsp = notecard.requestAndResponse(syncReq);
        if (syncRsp)
            notecard.deleteResponse(syncRsp);
        return true;
    }
}

7.10 Key code snippet 4: sleeping until the Notecard raises ATTN

cxSleepUntilAttn() asks the Notecard to hold ATTN low for exactly SAMPLE_INTERVAL_SEC seconds, confirms the pin went low, and puts the STM32 into STOP2. Because the host stays alive, the firmware checks the result of the card.attn request instead of firing it off and forgetting about it. The Notecard's ATTN pin brings the host back on schedule, and loop() moves on to the next sample.

setup() arms the ATTN wake once with cxSleepBegin(), and every loop() pass ends in goToSleep():

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

// ...

// Sleep in STOP2 until the Notecard raises ATTN SAMPLE_INTERVAL_SEC from now.
static void goToSleep(void) {
    if (!cxSleepUntilAttn(notecard, SAMPLE_INTERVAL_SEC, NULL, &debugSerial)) {
        // The Notecard didn't take the sleep request, or ATTN never went low
        // (check the ATTN -> D5 jumper).  Keep the sample cadence and retry.
        debugSerial.println("[SLEEP] ATTN sleep failed — waiting out the interval awake");
        delay(SAMPLE_INTERVAL_SEC * 1000UL);
    }
}

8. Data Flow

Data flow: 30-s accelerometer burst → RMS + CV vibration classifier → idle/running/transport state machine → equip_event.qo (hub.sync on state change) and equip_summary.qo (daily templated) → Notehub

Collected on each 30-second wake: the 3-axis acceleration magnitude of each sample at 104 Hz for 2 seconds → RMS and CV → one of three equipment states (IDLE, RUNNING, or TRANSPORT).

Accumulated in host RAM (retained through STOP2): today's running hours, lifetime running hours, today's transport hours, and the session start timestamp.

Transmitted:

  • equip_event.qo: one Note per state transition. After the Notecard acknowledges the queued Note, the firmware sends a hub.sync request so it goes out ahead of the scheduled outbound window. Expect 2–6 of these per work day (engine start, maybe a midday idle, engine stop, plus transport start/stop on delivery days). On cellular they reach Notehub as soon as a session comes up (~15–60 seconds). On NTN they go out once satellite service is available, which depends on sky visibility and session setup and can take longer than cellular.
  • equip_summary.qo: one Note per summary_interval_min (1440 minutes by default), queued and shipped in the Notecard's next outbound session. It covers the rolling window since the last report, not a calendar day, and the first one after boot may cover only part of a window if the Notecard's clock wasn't valid at startup. It carries the window's run hours, the lifetime total, transport hours, battery voltage, and a fault counter (fault_ct) for any event-queue overflows since the last summary.
  • _track.qo: the Notecard's GPS subsystem emits these on its own, every 4 hours as a heartbeat location record and on ringfence exit if geofence_radius_m is set. The firmware doesn't generate them directly.

Routed. Both application Notefiles go to Notehub, then on to whatever downstream endpoints the project's routes name. A typical setup sends equip_event.qo to a billing/dispatch system or a CMMS (computerized maintenance management system) webhook, equip_summary.qo to a time-series database for trending and predictive maintenance scheduling, and _track.qo to a mapping/GIS layer.

Alert triggers:

  • engine_start: a rental company watching for unauthorized after-hours use can act on this right away.
  • Any equip_event.qo with session_min > 0: a run session just closed. The event tag tells you what came next. engine_stop means the machine ran and then went idle, and transport_start means it ran and then started moving (RUNNING → TRANSPORT). Both carry the run duration in session_min. Billing systems must key off session_min > 0 across both tags, because keying only off engine_stop misses completed run sessions that end in transport.
  • transport_start: the equipment is moving. It's useful for confirming scheduled deliveries or catching unplanned moves. A non-zero session_min means the engine was running right before transport began.
  • transport_stop: a transit leg ended. Paired with the preceding transport_start timestamp, it gives you the transit duration for dispatch and mileage tracking.
  • Battery voltage below 3.5V in equip_summary.qo: solar input isn't keeping up at this site, so move the panel or add capacity.
  • No equip_event.qo for several days while _track.qo shows a steady position: the equipment may be sitting idle on a lot, which makes it a candidate for redeployment or servicing.

9. Validation and Testing

Expected steady-state on an active job site. In normal operation, expect 2–4 equip_event.qo Notes a day (engine start, engine stop, maybe a midday shutdown) and one equip_summary.qo per summary window (every 24 hours by default). You'll also see six _track.qo location heartbeats a day (one every 4 hours, matching GPS_HEARTBEAT_HOURS = 4), plus any extra _track.qo Notes that ringfence events trigger. On a delivery day, extra transport_start and transport_stop events bracket the trip. If the engine was running right before transport, the transport_start Note carries a non-zero session_min with the run duration.

Bench validation. Sitting on a desk, the equipment is IDLE. The accelerometer should report a low RMS, the classifier should output ST_IDLE, and the serial monitor shows [VIB] rms=X.X mg cv=Y.YYY → IDLE. To simulate an engine:

  • Tap the enclosure rhythmically against a surface at ~10 Hz. Don't keep a perfect beat. Vary the interval a little to mimic the irregularity of engine combustion. The CV should drop below 0.40 and the classifier should output ST_RUNNING.
  • Shake the enclosure sharply, then at random (slap → pause → slap → pause). That models road bounce, so you should see a high CV and ST_TRANSPORT.

Tuning the classifier on your equipment. If the classifier misfires at the default thresholds (false RUNNING during transport, or idle going undetected):

  1. Tap the enclosure in the three states and write down the RMS and CV values the serial monitor prints.
  2. If idle is triggering as RUNNING, raise vib_run_mg a little (try 18.0 or 20.0) to lift the activity floor.
  3. If transport is triggering as RUNNING, lower vib_cv_max (try 0.35 or 0.30) to tighten what counts as "engine-like vibration."
  4. If engine start is missed entirely, lower vib_run_mg (try 12.0 or 10.0) or raise vib_cv_max (try 0.45).

Update the environment variables in Notehub (Fleet → Environment). The device pulls the new thresholds on its next inbound sync (~8 hours by default, or right away if you trigger a manual sync in Notehub). On the next wake, check that the new values show up in the serial output, where the firmware logs [ENV] vib_run_mg=X.X vib_cv_max=Y.YY whenever either threshold changes. Keep iterating on live equipment until classification is reliable for your engine type and chassis.

Power validation with Mojo. The Mojo reports cumulative mAh over its Qwiic link. For bench measurements, Mojo takes the place of the normal battery feed. Leave the LiPo JST unplugged and power the carrier's VBAT pad only through the Mojo: bench supply (or the LiPo with its JST lead brought out directly) → Mojo BAT input → Mojo LOAD output → Notecarrier CX VBAT pad. Disconnect the solar panel too. The Notecarrier CX's solar charger input is a separate rail. If it stays connected during bench testing, solar current offsets the load draw, so you under-measure the real consumption and can't repeat the measurement under different light. Once the LiPo JST and the solar panel are both disconnected and all power runs through the Mojo, every bit of load current passes through its coulomb counter and the reading reflects the true whole-device draw.

Important measurement scope. The Mojo is spliced into the main power rail (LiPo → Notecarrier CX VBAT), so it measures the whole-device subsystem: Notecard for Skylo, the Notecarrier CX's onboard regulators and solar charger, and the LSM6DSOX IMU. It doesn't measure the Notecard alone. The published Notecard idle figure (~8–18 µA @ 5V) applies to the Notecard's own power domain by itself, and the Notecarrier CX adds its own quiescent current from the onboard power management IC and charger. Treat the Mojo figures below as a realistic whole-system deployment budget, not something to compare directly against a datasheet. To isolate the Notecard's draw, you'd have to separate its VMODEM rail from the carrier board. The Notecard low-power design guide explains how to isolate the rail.

Here's the expected current profile at default settings (30-second sample interval, 1 daily outbound + 3 inbound sessions):

PhaseExpected current (whole-device rail)
Host in STOP2 between samples (Notecard + carrier idle)~20–60 µA @ 5V (Notecard idle + host STOP2 ~1–2 µA + carrier quiescent)
Host awake + accelerometer sampling (~2–3 seconds)~5–15 mA
GNSS fix attempt (up to every 15 minutes, 10–60 seconds duration)~20–50 mA
Notecard cellular session (LTE-M, small payload)~100–300 mA average, brief bursts
Notecard NTN session (satellite, small payload)~150–400 mA average during transmission window

At default settings, a healthy Mojo trace looks like this:

  • Flat near-zero baseline (~20–60 µA) between wakes: Notecard idle plus the Notecarrier CX quiescent draw while the host sits in STOP2.
  • Brief ~5–15 mA blip every 30 seconds, ~2–3 seconds long: the host waking on ATTN, running the IMU sample burst, and classifying vibration before it calls goToSleep().
  • ~20–50 mA pulses at up to 15-minute intervals, 10–60 seconds long: the Notecard GNSS module getting a location fix. It's in periodic mode, so the real cadence may be lower when the device is stationary.
  • Three scheduled inbound sessions per day: every 8 hours the Notecard checks in with Notehub briefly to pull environment-variable updates (inbound: 480), usually for 10–30 seconds on LTE-M. There's also one daily outbound session that carries the queued equip_summary.qo (usually 10–60 seconds on LTE-M). On NTN, both inbound and outbound sessions can take longer, depending on satellite availability.
  • Additional radio sessions for each state-change event: once the Notecard accepts an equip_event.qo Note, the firmware sends a hub.sync request that triggers an immediate sync outside the scheduled cadence. A typical active work day has 2–6 state-change events (engine start, stop, maybe some midday transport legs), so expect 2–6 sessions on top of the baseline. All told, that's roughly 6–10 total sessions per active work day at default settings.
  • Ringfence-triggered transmissions (if geofence_radius_m is set): the Notecard emits a _track.qo on its own when the equipment exits the ringfence, and syncs it right away.

If the baseline sits at ~5–10 mA the whole time, the host isn't entering STOP2. Make sure the ATTN → D5 jumper from §5 is physically there and seated, and that the firmware was built with USB support set to None (usb=none). The serial output logs [SLEEP] ATTN sleep failed whenever cxSleepUntilAttn() can't sleep. If a cellular or NTN session runs unusually long (>60 seconds on LTE-M), the radio is fighting poor signal. Check the MAIN antenna placement, make sure it has an unobstructed view of the sky, and confirm it's the Skylo-certified unit that ships with the NOTE-NBGLWX.

Solar viability estimate. The Mojo sits on the load rail, so it only measures consumption. It can't see current flowing through the Notecarrier CX's separate solar charger input. With the solar panel disconnected and the unit running from a known LiPo or bench supply, run the Mojo for a full 24 hours and write down the total mAh consumed. Then compare that number against the theoretical harvest for your panel size and site. A 1W panel with 4 effective sun-hours produces 4 Wh (4000 mWh) raw. After typical derating for panel temperature and incidence angle (~80%), charger conversion efficiency (~85%), and soiling (~90%), the usable harvest is roughly 2.4 Wh, or about 480 mAh at 5V. At default settings the GPS cadence alone adds ~15–30 mAh/day. Add host wakes, three daily inbound check-ins, one outbound sync, and the usual 2–6 event sessions on active days, and whole-device consumption comes to about 65–120 mAh/day. On cellular, a 1W panel with ≥3 effective sun-hours a day usually covers that. Satellite sessions each draw more, so size the panel for each site. If Mojo shows a growing deficit across repeated 24-hour tests (solar disconnected), consider a 2–5 W panel, a higher-capacity LiPo, or a longer GPS period (GPS_PERIOD_SECONDS). To check the real panel and charger harvest instead of relying on the derating model, put a DC current meter inline on the solar cable itself and log it over a representative sunny day.

10. Troubleshooting

SymptomLikely CauseSolution
Serial monitor shows no output after uploadMonitoring the wrong port, or board options not appliedDebug output goes to the LPUART on the Notecarrier CX debug jack, not USB, so open the ST-LINK virtual COM port at 115200 baud (see §7.1). Check that Blues boards → Cygnet is selected with USB support set to None. On Windows, install the ST-Link drivers.
[IMU] Not found messageAccelerometer not powered or miswiredCheck the I2C connections (SDA, SCL, +3V3, GND) and run an I2C scanner sketch to make sure the LSM6DSOX answers. See §5.
No _session.qo appearing in Notehub after several minutesPRODUCT_UID mismatch, missing antenna, or no cellular/NTN coverageCheck that PRODUCT_UID matches the value you created in Notehub (copy and paste it from Notehub → your project settings). Confirm the MAIN antenna is mounted outside with an unobstructed sky view. Watch the Notecard's LED, which should flash briefly during sync attempts. On satellite, allow 5+ minutes for Skylo to set up a session, depending on sky visibility.
Classifier always outputs IDLE even when tappingThresholds too highLower vib_run_mg to 10.0 (in Notehub Fleet → Environment), then wait for the next inbound sync (~8 hours) or trigger a device fetch manually.
Classifier falsely triggers RUNNING during idle periodsThresholds too lowRaise vib_run_mg to 20.0 and lower vib_cv_max to 0.30.
[SLEEP] ATTN sleep failed in the log, or baseline current 5–10 mA (host never sleeping)ATTN → D5 jumper not installed or miswired, or firmware built with USB CDC enabledCheck that the jumper physically connects the ATTN and D5 pins on the Notecarrier CX header (see the "ATTN → D5 jumper" line in §5), and use a multimeter to confirm continuity. Make sure the firmware was built with USB support set to None (usb=none), because with USB CDC enabled the host leaves STOP2 within a millisecond. If either one is wrong, the host runs continuously and the power budget falls apart.
Battery voltage in equip_summary.qo declining over daysSolar input inadequateMake sure the 6V solar panel gets enough direct sunlight (≥3 effective sun-hours per day in temperate climates). If the site is shaded, or at high latitude in winter, upgrade to a 2–5 W panel. Put a DC meter inline on the solar cable to confirm the real harvest.
Duplicate equip_event.qo Notes in NotehubI2C acknowledgement lost after Notecard accepted NoteThis edge case is by design (at-least-once delivery). Downstream routes should dedupe on the epoch + event pair, since the Note's timestamp and event type are unique for each transition.
Arduino-cli reports "board not found" or an invalid board optionCore not installed or board name wrongRun arduino-cli core list to confirm STMicroelectronics:stm32 is installed. If it isn't, run the full core install command from §7.1. Check that the board options are exactly STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none (they're case-sensitive).

11. Limitations and Next Steps

This is a reference build, not a finished fleet product. A few things are scoped down on purpose so the core idea (engine-hour billing from a magnetic stick-on with no wiring) stays easy to follow. Below are the places you'll want to harden before this goes on a thousand machines, followed by the natural production extensions.

Simplified for this POC

These are the corners this build cuts on purpose, each with what you'd do differently on a production fleet.

The vibration classifier is heuristic, not trained. The RMS + CV algorithm does a good job of telling engine idle from transport vibration on diesel construction equipment at typical idle RPMs. Gasoline-powered equipment idles more smoothly, though, so its CV can come in lower and may overlap with transport at certain speeds. On very rough terrain, engine-running CV can also creep above the default threshold. The vib_run_mg and vib_cv_max environment variables are the tuning knobs, but dialing them in precisely requires per-equipment-class calibration from logged data. A production deployment would instrument a representative sample of each equipment type, log raw RMS/CV values over several shifts, and work out a threshold pair for each class.

Hour accumulation granularity is 30 seconds. Each wake credits the time since the previous wake (from card.time, capped at three sample intervals) to the running bucket if the previous state was RUNNING. Before the Notecard has valid time, it credits a nominal SAMPLE_INTERVAL_SEC instead. An engine that starts halfway through a 30-second sleep interval gets picked up on the next wake, so the worst-case rounding error is one sample interval (30 seconds). That's usually fine for billing. If you need sub-minute precision, drop SAMPLE_INTERVAL_SEC to 10, which costs you ~3× as many host wakes.

The first run session after power-on may report session_min: 0 if the Notecard doesn't have valid time yet. getEpoch() returns 0 when card.time has no cellular or GPS sync, and the firmware refuses to record a start timestamp of 0. So if the device goes to RUNNING before time is valid, run_session_start stays at 0, and the closing engine_stop or transport_start event carries session_min: 0. That makes it useless as a standalone billing record. Once the Notecard has time, later sessions compute correctly. During the affected cold-start window, the daily equip_summary.qo still adds up run hours in the hour-meter buckets regardless of session boundaries, so use the summary to reconcile any gap. Billing integrations should treat a session_min: 0 on the first event after a device power cycle as an incomplete record.

The hour counter is software-based and lives in host RAM. The run_h_total field in AppState survives every STOP2 sleep/wake cycle, but nothing writes it to non-volatile storage. A power cycle, a reset, or a firmware update resets the lifetime total to zero. A production version should keep the authoritative total server-side in Notehub (for example, as a device-level environment variable updated on each engine_stop event) so it survives a hardware replacement.

There is no ringfence alerting in the application firmware. The Notecard handles ringfence exit on its own (_track.qo), and the application firmware only sets the ringfence center through card.location.mode. The _track.qo Notes that result carry location data but no application-level label. A production deployment should add a Notehub route that fires an alert when a _track.qo shows up outside the ringfence.

The ringfence cannot be centered at the equator or prime meridian. The firmware treats geofence_lat = 0.0 and geofence_lon = 0.0 as the "not configured" sentinel. It refuses to apply a ringfence if either coordinate is within ≈0.0001° of zero (the check is fabsf(lat) > 0.0001f and fabsf(lon) > 0.0001f). That means a site sitting right on the equator (lat ≈ 0°) or the prime meridian (lon ≈ 0°), say in southern Ghana, the Republic of Congo, or the English Channel, can't use the ringfence feature as it's written today. To lift the restriction, replace the 0,0 sentinel with an explicit enable flag (for example, a geofence_enable environment variable set to 1) and let lat/lon take any in-range value, zero included.

Solar panel sizing is for temperate climates. A 1W panel + 2000 mAh LiPo gives enough runtime in most regions with ≥3 effective sun-hours per day. At higher latitudes in winter, or with the enclosure on a shaded part of the chassis, you may need a larger panel (2–5W) or a higher-capacity LiPo (5000 mAh). The bat_v field in equip_summary.qo is your early warning. A voltage that keeps dropping over several days means the panel isn't harvesting enough.

Mojo is not read in firmware. The firmware doesn't poll the Mojo's LTC2959 coulomb counter over Qwiic. If the operator wants fleet-level energy telemetry, adding a mojo_mah field to equip_summary.qo is a simple extension.

Production Next Steps

Taking the tracker to a real fleet usually starts with tuning it on real data, then moves on to over-the-air updates and more sensing.

Per-equipment-class threshold calibration means shipping a "learning mode" firmware build that logs raw RMS/CV data at 1-minute intervals for several shifts, then switches over to production classification.

Lifetime hour counter persistence in Notehub environment variables would keep the running total intact when a device gets replaced.

A ringfence-exit alert route in Notehub would fire a webhook when a _track.qo Note shows up with a location outside the configured ringfence, so you'd hear about unauthorized moves.

Over-the-air firmware updates to the Notecarrier CX's STM32 host through Notecard Outboard DFU would let you ship threshold algorithm improvements and new features without sending a technician to every machine.

Tamper detection would pick out an abrupt, very high-amplitude single-axis spike (like the magnet base being pulled off) from normal equipment vibration and flag it as a tamper event.

Multi-sensor fusion would pair the accelerometer with an engine temperature sensor (an NTC thermistor on the exhaust manifold) or a current clamp on the alternator output. That gives you a second, independent check on engine state and makes the classifier more reliable on unusual equipment types.

12. Summary

The rental excavator from the start of this story now reports its own hours. A magnetic enclosure goes onto the frame rail in under ten minutes, without drilling, a wiring harness, or help from the OEM. From then on, the machine sends engine hours, location, and work-session events to Notehub over whatever network it can reach. The two-parameter RMS + coefficient-of-variation classifier is what makes vibration-only detection trustworthy. It can tell a diesel idle from a flatbed delivery without any wiring to the engine, and Notecard for Skylo keeps the data moving whether the machine is in a regional yard or at the bottom of a quarry. On the rental company's side, the data is simple to use. Any equip_event.qo with session_min > 0 is a billing record, run_h_total drives maintenance scheduling, and a transport_start at 2 AM is the unauthorized-use alert. The whole fleet runs the same firmware and the same hardware out of one Notehub project.

On This Page

  • 1. Project Overview
  • 2. System Architecture
  • 3. Technical Summary
  • 4. Hardware Requirements
  • 5. Wiring and Assembly
  • 6. Notehub Setup
    • What to expect in Notehub
  • 7. Firmware Design
    • 7.1 Installing and flashing
    • 7.2 Modules
    • 7.3 Sensor reading strategy
    • 7.4 Event payload design
    • 7.5 Low-power strategy
    • 7.6 Retry and error handling
    • 7.7 Key code snippet 1: vibration classifier
    • 7.8 Key code snippet 2: compact template with GPS metadata
    • 7.9 Key code snippet 3: immediate event with session duration
    • 7.10 Key code snippet 4: sleeping until the Notecard raises ATTN
  • 8. Data Flow
  • 9. Validation and Testing
  • 10. Troubleshooting
  • 11. Limitations and Next Steps
    • Simplified for this 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