Untethered Trailer and Chassis Fleet Tracker
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/trailer_fleet_tracker_starnote/ so it can learn how the device works and help you adapt it to your hardware.
This project is a solar-trickle-charged, tractor-independent GPS and motion tracker for asset location tracking on freight trailers and intermodal chassis. It sends telemetry over cellular first and falls back to Iridium LEO satellite, which covers the whole globe. That keeps a trailer or chassis visible on trans-oceanic container routes and polar corridors where geostationary satellite networks have no coverage at all. The hardware is a Blues Notecarrier XI with a Swan host, a cellular Notecard, and a Starnote for Iridium (§4 has the BOM).
The unit mounts on the trailer roof or chassis frame and runs a parked/moving state machine entirely on the device. Departure, arrival, and position Notes go to the Blues Notehub cloud service over cellular, with automatic Iridium satellite fallback, so the tracker doesn't need a tractor hookup, site IT, or terrestrial coverage.
1. Project Overview
The problem. Long-haul trucking fleets have had the same blind spot for decades. The tractor has an ELD (electronic logging device), a telematics unit, and a driver, and the 53-foot box it drags around has none of those. An intermodal chassis is worse. The same steel frame might be owned by a chassis pool, leased to a carrier, loaded by a shipper, and pulled by three different tractors in a single week. At any moment, a fleet operator's dispatch system knows where the power unit is, but the trailer is wherever the last driver left it.
This matters because trailers and chassis tie up a lot of capital. When a $100,000+ refrigerated trailer sits dark for five days at a shipper's dock, the fleet can't see it. The carrier can't bill for detention time it can't prove, can't recover the equipment without calling around, and can't stop the slow drain of assets leaking out of rotation. For chassis pools it's an even bigger problem. Intermodal equipment management is still mostly a phone-call business, because there's no cheap, tractor-independent way to know where each piece of equipment is.
Why Notecard. A trailer changes tractors every day and can change carriers every few days. Anything that depends on the tractor, whether it's a J1939 tap, a cab-mount device, or a driver's phone, stops working the moment the trailer unhooks. Notecard fits here for three reasons that build on each other:
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.
First, cellular is tractor-independent. The Notecard on the trailer roof has its own prepaid SIM and its own cellular session, and it doesn't care which tractor is pulling it.
Second, satellite fallback fills coverage gaps, from remote land corridors to mid-ocean. North American freight routes cross the Great Plains, mountain passes, and border zones where LTE-M coverage has real holes. Container chassis go even further. On trans-Atlantic and trans-Pacific segments, they're completely out of reach of any terrestrial signal and outside every geostationary satellite footprint. This project covers all of those cases by pairing a standard cellular Notecard with a Starnote for Iridium. Delivery is cellular-first, with Iridium LEO satellite fallback that works pole-to-pole, every ocean segment included. After two cellular failures in a row, the Notecard routes through the Starnote automatically, and the firmware doesn't need to know which path is active.
Third, power is scarce. Trailers have no reliable 12V auxiliary hookup. The
7-way connector only carries power when a tractor is connected, and that's
exactly the condition this design can't count on. A small solar panel and a LiPo
battery are the only workable power source, so the whole system has to be
strict about sleep. The Swan goes into deep sleep between checks and wakes on
the ATTN interrupt. The firmware explicitly turns GPS on at each PARKED→MOVING
departure (by issuing card.location.mode {"mode":"periodic"}) and off at each
MOVING→PARKED arrival (by issuing card.location.mode {"mode":"off"}), so the
GPS module never runs while the trailer is parked. The result is a device that
can run for days without solar input and get month-scale battery life on
trickle solar.
Deployment scenario. A weatherproof IP67 enclosure mounts flat on the trailer roof, with the solar panel on the same roof surface. Two SMA bulkhead fittings in the enclosure lid bring the cellular antenna and the combined Iridium+GPS antenna out to the lid surface, so there's no GPS patch inside. For chassis installations, Chassis deployment variant covers chassis-specific mounting. Either way, there's no tractor hookup and no wiring into the existing trailer or chassis systems.
2. System Architecture
2.1 Swan host responsibilities
The tracker's brain is the Swan STM32L4R5 host.
The XI has no Feather socket, so the Swan mounts alongside the Notecarrier XI
and is wired to its 0.1" headers by hand. On each wake, the Swan asks the
cellular Notecard's built-in accelerometer one question through
card.motion: moving
or parked? If a transition happened, the Swan queues the right
Note over I²C. PARKED→MOVING fires
card.location.mode {"mode":"periodic"}
to start GPS, and MOVING→PARKED fires
card.location.mode {"mode":"off"}
to shut it down again for the dwell. Each transition Note is stamped at the
wake where the change was first seen (with the current cached GPS fix and
epoch), and that snapshot goes into the pending-event queue right away. Every
delivery attempt, retries on later wakes included, reuses the stored snapshot,
so a retried departure Note never picks up a stale location from a later
parking period. Once the queue is handled, the Swan drops into STM32 STOP2 (a
few µA, RAM intact) until the Notecard raises its ATTN pin, which is wired to
Swan D5, for the next check. Execution resumes in place, so AppState just
stays in RAM between wakes.
Departure Notes carry sync:true. On cellular, that wakes the radio right away
to deliver them. On NTN/satellite paths it marks the Note as high priority, but
it can't interrupt an Iridium orbital pass on demand. The Notecard queues the
Note and sends it at the next scheduled satellite transmission opportunity,
which is usually minutes away depending on LEO geometry. Position and heartbeat
Notes wait in a batch for the next outbound window on either transport.
2.2 Notecard and Starnote for Iridium responsibilities
Queued Notes live on the cellular Notecard (e.g., NOTE-WBEX) between syncs. It
manages the cellular session on the cadence set by
hub.set. When two
cellular attempts in a row fail,
card.transport "method":"cell-ntn"
has it route through the Starnote for
Iridium module instead.
The Starnote handles the Iridium LEO satellite session and GPS/GNSS through a
single Iridium-certified combined antenna, and the standard
card.location API
returns the Starnote's fix with no special handling. The Notecard also passes
environment
variables
down from Notehub, so the fleet can be retuned without a truck roll.
2.3 Notehub responsibilities
Once Notes leave the device, the Notecard's embedded global SIM carries them over supported carriers worldwide to Notehub. From there, Notehub ingests and stores the events and routes them downstream. Fleets let the operator group trailers by carrier, lane, or equipment type and share threshold configurations across each group, and Smart Fleets assign trailers to the right fleet automatically by region or device attribute.
Routing to the cloud (high level only). Notehub can route to HTTP, MQTT, AWS, Azure, GCP, Snowflake, and other destinations. Route setup depends on your project (see the Notehub routing docs), and this project doesn't ship a specific downstream endpoint.
3. Technical Summary
What you'll have when you're done: A solar-powered trailer or chassis tracker that reports motion-triggered departure/arrival events and position updates to Notehub over LTE-M (primary) or Iridium LEO satellite (automatic fallback). It covers the globe pole to pole and doesn't need a tractor hookup or any site IT.
- Notehub: Create a Notehub project and copy its ProductUID.
- Wire the bench rig: Notecarrier XI + Swan (including the XI
ATTN→ SwanD5wake wire) + cellular Notecard + Starnote for Iridium + solar chain. The full pinout is in §5.1. - Edit one line of
firmware/trailer_fleet_tracker_starnote/trailer_fleet_tracker_starnote_helpers.hto setPRODUCT_UIDto your Notehub project's ProductUID (in Notehub: Project Settings → ProductUID). - Flash with the Arduino IDE or
arduino-cli(see §7.1). - Watch: Open Notehub → Events tab. A
_session.qoshould show up within a few minutes of power-on, which confirms the first cellular session and clock sync. The firmware then queues atrailer_heartbeat.qolocally on the next parked-check wake (~5 minutes in). Heartbeat Notes are not markedsync:true, so instead of going out right away they wait for the next scheduled outbound sync window. That's ~60 minutes at a full battery (voutbound high:60) or ~120 minutes at nominal charge (voutbound normal:120). Give it 1–2 hours from first power-on before the first heartbeat appears in Notehub.
Here's a sample Note from this device:
{
"file": "trailer_event.qo",
"body": {
"type": 1,
"dwell_h": 18.5,
"gps_valid": 1,
"lat": 41.8781,
"lon": -87.6298,
"evt_time": 1746182400
}
}4. Hardware Requirements
Hardware stack note. This design uses Notecarrier XI + Swan instead of the default Notecarrier CX stack (with its onboard STM32 host). The ocean-capable Iridium path needs the dedicated Starnote connector on the Notecarrier XI. The Notecarrier CX doesn't have that interface, so it can't support Starnote for Iridium or Iridium NTN satellite fallback.
| Part | Qty | Rationale |
|---|---|---|
| Notecarrier XI | 1 | Carrier board with M.2 Notecard slot, Starnote for Iridium connector, +VBAT screw terminal, Qwiic port, and 0.1" headers exposing I²C, ATTN, GPIO, and power rails for an external host MCU. No Feather socket, so the Swan is wired to these headers by hand. |
| Standard cellular Notecard, e.g., NOTE-WBEX | 1 | Global LTE-M / NB-IoT cellular + GPS/GNSS in an M.2 module. The Notecard handles the cellular session and motion detection, and the Starnote for Iridium provides satellite fallback and the combined GPS+satellite antenna. |
| Starnote for Iridium | 1 | Iridium LEO satellite module with a single Iridium-certified antenna that handles both satellite and GPS/GNSS. It plugs into the Notecarrier XI Starnote connector, and you don't need another GPS antenna. |
| Swan (STM32L4R5) | 1 | Blues Feather-format STM32L4R5 host MCU. The Notecarrier XI has no Feather socket, so the Swan mounts alongside the carrier and is wired by hand to the XI's 0.1" headers (3V3, GND, SDA, SCL, ATTN → D5). It sleeps in STM32 STOP2 between wake cycles until the Notecard's card.attn ATTN pin wakes it. |
| Blues Mojo (bench-only, not field-deployed) | 1 | Coulomb counter for power validation during bench bring-up. It connects inline between the LiPo and the Notecarrier XI +VBAT pad via Qwiic. Remove it before field deployment. |
| SparkFun Sunny Buddy MPPT Solar Charger | 1 | LT3652-based MPPT charger that accepts 6–20V solar input and charges a single-cell LiPo at up to 450 mA. MPPT keeps the panel at its maximum power point across the wide irradiance range a trailer roof sees. |
| SparkFun Small Solar Panel, 0.6W, 6V ETFE | 1 | Flexible, weather-resistant ETFE panel, 6V nominal. At 4 peak sun hours it delivers roughly 250–300 mAh/day. Whether that beats the system's parked quiescent draw depends on the Starnote for Iridium board's standby current, which isn't published, so measure the real floor with Mojo (§9) before you settle on sizing. Mount it flat on the trailer roof above the enclosure. |
| SparkFun Lithium Ion Battery, 2Ah (PRT-13855) | 1 | 3.7V / 2000 mAh cell with a 2mm JST-PH connector. It gives you several days of backup through cloudy dwell periods. |
| Cellular antenna (included with NOTE-WBEX) | 1 (included) | Ships in the NOTE-WBEX kit. It connects to the Notecard MAIN u.FL port through the u.FL-to-SMA pigtail and SMA bulkhead fitting below, and covers the LTE-M / NB-IoT frequency bands for the regions you deploy in. Route it through the first SMA bulkhead to the outside of the enclosure lid. |
| Iridium-certified combined Iridium+GPS antenna (included with Starnote for Iridium) | 1 (included) | Ships in the Starnote for Iridium kit. It's a single u.FL antenna that covers both the Iridium satellite uplink (1616–1626.5 MHz) and GPS/GNSS, so you don't need a separate GPS antenna. Must use only the included antenna: Starnote for Iridium is certified on Iridium's network exclusively with this antenna. Swapping in another antenna voids the Iridium network certification and risks getting blocked from the network. Route it through the second SMA bulkhead to the outside of the enclosure lid and mount it with an unobstructed 360° sky view. |
| u.FL to SMA pigtail, ~100 mm, e.g., SparkFun Interface Cable SMA to U.FL, WRL-09145 | 2 | Two pigtails: one for the NOTE-WBEX cellular MAIN u.FL port (runs to the cellular antenna through the first SMA bulkhead) and one for the Starnote's Iridium+GPS u.FL port (runs to the combined Iridium+GPS antenna through the second SMA bulkhead). |
| SMA female panel-mount (bulkhead) connector, 50Ω | 2 | Two fittings in the enclosure lid, one for the cellular antenna (NOTE-WBEX MAIN path) and one for the Iridium+GPS combined antenna (Starnote path). Both antennas exit through the lid, so there's no GPS patch mounted inside. The Amphenol RF 132289 is a standard panel-mount SMA female chassis connector (50Ω, through-hole), and any electrically equivalent 50Ω SMA female panel-mount fitting with a compatible flange size works as a substitute. |
| IP67 weatherproof enclosure, ~130×80×50 mm | 1 | Protects the electronics from road spray, rain, and the wide temperature swings on a trailer roof. Both antennas route through SMA bulkheads, so the lid material does not need to be RF-transparent. ABS, polycarbonate, and aluminum all work. Mount it flat on the trailer roof, with a cable entry gland in the side wall for the solar panel leads. |
Antenna note. The Starnote for Iridium ships with an Iridium-certified antenna that connects to its single u.FL port and covers both the Iridium satellite link and GPS/GNSS. You don't need a separate GPS antenna, and the NOTE-WBEX GPS u.FL port goes unused. The NOTE-WBEX ships with a cellular antenna for its MAIN u.FL port. Each antenna runs through its own SMA bulkhead fitting to the outside of the enclosure lid. Mount the Iridium+GPS antenna with an unobstructed 360° sky view. Iridium LEO satellites pass over every azimuth, so it doesn't matter which direction the antenna faces.
The cellular Notecard (e.g., NOTE-WBEX) ships with an active Blues SIM that includes 500 MB of data and 10 years of global cellular service, with no activation fees and no monthly commitment. The Starnote for Iridium needs a separate Iridium satellite service plan that the cellular SIM doesn't cover. Contact Blues for current Iridium service details and activation.
5. Wiring and Assembly
5.1 Notecarrier XI + Swan
The cellular Notecard seats in the M.2 slot, and the Starnote for Iridium plugs into the XI's dedicated Starnote connector. Since the Notecarrier XI has no Feather socket, the Swan mounts alongside the carrier and is wired by hand to the XI's 0.1" headers. Power, Qwiic, and both antennas land on headers and u.FL ports that are easy to reach.
Notecarrier XI interface reference:
| Interface | Location | Notes |
|---|---|---|
| +VBAT | Screw-terminal block, carrier edge | 3.4–4.2V single-cell LiPo input. Red wire to +VBAT, black wire to the adjacent GND. During bench bring-up, the Mojo LOAD output connects here through its JST-PH pigtail. |
| GND | Screw-terminal block, adjacent to +VBAT | Shared power and signal return for the battery, solar charger, and Qwiic. |
| Host MCU headers | 0.1" header rows along the carrier edge | The XI has no Feather socket. Mount the Swan alongside the carrier and wire it by hand to these headers: Swan 3V3 ↔ XI 3V3, Swan GND ↔ XI GND, Swan SDA (PB9) ↔ XI SDA, Swan SCL (PB8) ↔ XI SCL, and the XI ATTN pin to Swan D5 (the HOST_ATTN_PIN wake input, covered in the ATTN wire note below). Leave the XI's EN pin unconnected. |
| M.2 slot | M.2 Key-E connector, carrier center | The cellular Notecard (e.g., NOTE-WBEX) goes in at a 45° tilt, then pivots flat and is secured with the M.2 retaining screw. The M.2 key prevents reverse insertion. |
| Starnote connector | Dedicated multi-pin connector, carrier face | Starnote for Iridium plugs straight down into this connector, which carries power, I²C, and the Iridium+GPS antenna passthrough. Line the Starnote module up over the connector and press firmly until it's fully seated. No screws needed. |
| Qwiic | JST-SH 1mm 4-pin, carrier face | Mojo connects here with a Qwiic cable for I²C mAh reporting during bench bring-up. The XI has onboard I²C pull-ups, so you don't need extra resistors. |
| Swan USB-C | USB-C port on the Swan module itself | Programs the Swan host through its DFU bootloader. The firmware is built with USB support set to None (required for STOP2), so this port does not give you a serial debug console. Debug output comes out of the Swan's STLINK debug connector instead (see §7.1). |
| Swan STLINK debug connector | 14-pin connector on the Swan module | Attach an ST-LINK V3 for SWD programming and for the virtual COM port that carries the firmware's 115200-baud debug output. |
Power chain for bench bring-up (Mojo inline):
The Mojo sits inline between the LiPo and the Notecarrier XI +VBAT screw terminal and measures only the device-side discharge current.
- Solar panel → Sunny Buddy SOLAR IN terminal (6–20V).
- LiPo battery → Sunny Buddy LIPO terminal (JST-PH 2mm 2-pin; charging connection).
- LiPo battery → Mojo
BATinput (JST-PH Y-adapter from the battery pigtail; same node as the Sunny Buddy LIPO connection). - Mojo
LOADoutput → Notecarrier XI +VBAT screw terminal (red to+VBAT, black toGND). - Mojo Qwiic → Notecarrier XI Qwiic port.
Power chain for field deployment (Mojo removed):
Once bench bring-up is done (see §9), take the Mojo out before you seal the enclosure:
- Solar panel → Sunny Buddy SOLAR IN (unchanged).
- LiPo battery → Sunny Buddy LIPO terminal (unchanged).
- LiPo battery → Notecarrier XI +VBAT screw terminal directly.
Polarity note. Check red-to-positive on every JST-PH connection before you apply power. The Notecarrier XI +VBAT accepts 3.4–4.2V, and going above 4.3V risks damage.
Antenna routing:
There are two external antennas, so the enclosure lid needs two SMA female bulkhead fittings:
- NOTE-WBEX cellular antenna (MAIN u.FL): Run a ~100 mm u.FL-to-SMA pigtail to one SMA female bulkhead fitting on the enclosure lid, then thread the included cellular antenna onto the bulkhead from outside.
- Starnote Iridium+GPS combined antenna (Starnote u.FL): Connect it to the Starnote's u.FL port with a second ~100 mm u.FL-to-SMA pigtail and route it to the second SMA female bulkhead fitting on the enclosure lid. Mount this antenna on the lid surface facing the sky, with a clear 360° view. Iridium LEO satellites pass over every azimuth, so it doesn't matter which direction the antenna faces. No separate GPS antenna is needed. The Starnote's combined antenna handles GPS, and the NOTE-WBEX GPS u.FL port goes unused.
Space the two SMA bulkhead fittings at least 25 mm apart to keep coupling between the two antenna paths low. If any trailer structure overhangs the spot you planned, shift the enclosure fore or aft to clear it, because even partial sky obstruction above roughly 8° elevation hurts Iridium link margin and GPS acquisition.
ATTN wire (host sleep):
Run a wire from the Notecarrier XI's ATTN header pin to the Swan's D5
pin. The Notecard pulls ATTN low while the Swan sleeps and raises it when the
next check is due, and the rising edge on D5 wakes the STM32L4R5 from STOP2.
Do not use the XI's EN pin for this. On the Notecarrier XI, EN is the
Notecard's own enable (pulling it to GND turns the Notecard off), so it can't
gate the host and must be left unconnected. Putting a Host to Sleep Between
Sensor
Readings
shows the same STOP2-plus-ATTN pattern on the Notecarrier CX. The only
differences here are the hand-run wire and the Swan's pin name.
I²C / debug:
The Swan talks to the cellular Notecard over the Notecarrier XI's onboard I²C bus. The Notecard manages the Starnote for Iridium through the Starnote connector, so satellite and GPS operation need no extra host wiring. During development, attach an ST-LINK to the Swan's STLINK debug connector. Its virtual COM port carries the firmware's debug output at 115200 baud. (USB CDC is disabled in this build, so the Swan's USB-C port is for DFU programming only.)
Chassis deployment variant
An intermodal chassis has no roof. It's a flat, low steel frame sized for a 20- or 40-foot ISO container. The BOM is the same as the trailer installation, but the mounting, solar, and antenna plan has to fit the chassis geometry.
Mounting location. Mount the enclosure on the front bolster or gooseneck cross-member at the kingpin end of the chassis. This area sits directly under the container floor plate, which partly shields it from horizontal impacts during container handling. Use stainless M6 hardware with split lock washers through pre-drilled holes. With the constant road vibration, it's a good idea to put thread-locking compound (e.g. Loctite 243) on every fastener. Stay away from the rear cross-members, which get more road spray from the axle group and more forklift contact near the rear twist locks.
Solar panel. Once a container is loaded, the chassis roof surface is completely blocked. Mount the solar panel on a bracket welded or bolted to the inboard face of the front bolster rail, angled 30–45° toward the sky and facing south in the northern hemisphere. Check that the bracket clears the container twist-lock castings and doesn't stick out past the chassis rail envelope, where it would get in the way of loading or break transport width limits. Budget conservatively. Assume 2–3 peak sun hours a day for a chassis-mounted panel (versus 4 on an unobstructed trailer roof), and consider moving up to a 4 Ah LiPo for high-latitude or winter deployments.
Antennas. The cellular antenna and the combined Iridium+GPS antenna both need a clear view of the sky. Put both SMA bulkhead fittings on the top or outboard face of the enclosure lid, and orient the enclosure so that face points up or outboard instead of being buried against the chassis rail. On a loaded chassis, any antenna inside the container envelope has the container floor plate right overhead, so mount the enclosure far enough forward that the antennas clear the front edge of the container.
Enclosure protection. A chassis is a rougher place than a trailer roof. Road spray hits the frame from below constantly, forklift tines pass close to the frame rails, and container handling knocks and scuffs any hardware nearby. Use an IP67 enclosure with stainless fasteners throughout, protect the cable entry glands with reinforced strain-relief fittings rated for abrasion and UV, and consider adding a steel guard plate over the enclosure if forklift traffic can reach the mount location.
6. Notehub Setup
Creating the project and provisioning the device
- Create a project. Sign up at notehub.io and create
a new project. Copy the
ProductUID, which
looks like
com.your-company.your-name:trailer-tracker. - Set the ProductUID in firmware. Open
firmware/trailer_fleet_tracker_starnote/trailer_fleet_tracker_starnote_helpers.hand replace the empty string on the#define PRODUCT_UID ""line with your value. - Power the unit. On its first cellular session, the Notecard associates itself with your project automatically, so there's no manual claim step. The device shows up in your project's Devices tab within a minute or two.
Fleet organization
Create one Fleet per equipment
category, such as reefer-trailers, dry-vans, and flatbed-trailers.
Fleet-level environment
variables
reach every device in the fleet on its next inbound sync, so you can tune
heartbeat cadence or motion sensitivity for a whole category at once. If your
fleet follows a consistent naming scheme, Smart
Fleets can sort trailers
automatically by device attribute.
Environment variables
Go to Fleet → Environment (or Device → Environment for per-unit
overrides) and add any of the variables below. Changes take two steps to
arrive. First, Notehub delivers the updated values to the Notecard on the next
inbound sync window (2 hours at high battery and 4 hours at normal battery,
set by the vinbound profile). Then the host firmware picks them up on its next
hourly env poll (ENV_POLL_SECS = 3600). At normal battery, expect new
values to take effect within roughly 3–5 hours of saving them in Notehub,
without a reflash or a truck roll.
| Variable | Default | Purpose |
|---|---|---|
parked_check_mins | 5 | How often the host wakes to poll the accelerometer while parked. Lower values catch departures sooner (this interval bounds transition detection accuracy) but raise the average sleep current. Range: 1–60. |
moving_ping_mins | 15 | GPS position report interval while moving, which also bounds arrival detection accuracy. When it changes, the firmware reissues card.location.mode so the Notecard's GNSS fix cadence keeps matching the new report interval (right away if the trailer is moving, otherwise at the next departure). Range: 5–60. |
heartbeat_hours | 6 | Alive-ping interval while parked. Set it higher (e.g. 12) for long-dwell equipment at known yards, or lower (e.g. 2) for high-value assets or demurrage monitoring. Range: 1–24. |
Routing
Add two routes in Notehub:
trailer_event.qo→ real-time delivery to a downstream HTTP endpoint, MQTT broker, or cloud function. These departure/arrival events drive detention billing, recovery workflows, and missed-connection alerts.trailer_location.qo+trailer_heartbeat.qo→ a long-term store (e.g. Snowflake, AWS S3, or a time-series database) for dwell-time analytics, lane utilization, and battery trending.
Keeping the two routes separate means bulk-load throughput never affects alert latency. The Notehub routing docs list the supported destination types.
What you'll see in Notehub
Your project's Events tab shows every Note type:
-
_session.qo: Notecard housekeeping on each cellular or satellite session, which confirms connectivity. -
trailer_event.qo: fires on every departure and arrival and is delivered withsync:true. The body looks like this:{ "type": 1, "dwell_h": 18.5, "gps_valid": 1, "lat": 41.8781, "lon": -87.6298, "evt_time": 1746182400 }type1 = departed, 2 = arrived.dwell_his how many hours the trailer sat parked before this departure, and it's 0 for arrival Notes.gps_validis 1 when a valid GPS fix was available at detection time and 0 when there was no fix (for example, on the first departure from a freshly installed unit). Ignorelat/lonwhengps_validis 0.lat,lon, andevt_timeare the GPS coordinates and Unix epoch captured at transition detection time, meaning the wake cycle where the state change was first seen.parked_check_minsbounds the timestamp accuracy for departures, andmoving_ping_minsbounds it for arrivals. The location is the Notecard's most recent cached fix at detection time. The firmware stores these values in the event queue at detection time and keeps them across retried deliveries, so a Note retried on a later wake always carries the original detection-time data instead of the post-transition GPS state. -
trailer_location.qo: queued everymoving_ping_minswhile the trailer is rolling. The compact envelope carries the GPS fix the Notecard embeds, and you can see it on a map in Notehub's device view. -
trailer_heartbeat.qo: fires everyheartbeat_hourswhile parked. Body:{ "volt": 3.84, "gps_valid": 1 }If
volttrends down across consecutive heartbeats, that's an early warning that the solar panel or charge path needs attention.
7. Firmware Design
The firmware lives in
firmware/trailer_fleet_tracker_starnote/.
setup() runs once at power-up, and each pass through loop() is one wake
cycle that ends with the Swan asleep in STOP2 until the Notecard raises ATTN.
The sleep helpers (the card.attn request, STOP2 entry, and ATTN wake) are in
host_sleep.h.
7.1 Dependencies and flashing
Install the libraries:
Install note-arduino
(Blues Wireless Notecard) and
STM32duino Low Power (plus its
dependency STM32duino RTC) from the Arduino Library Manager, or run
arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC".
The note-arduino releases
page lists the available
versions. STM32duino Low Power provides the STOP2 entry and ATTN-pin wake that
host_sleep.h uses. It supports the Swan's STM32L4R5, where deepSleep()
enters STOP2.
Add the STM32 boards index:
Add
https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
under File → Preferences → Additional Boards Manager URLs, then install
"STM32 MCU based boards" from the Boards Manager.
Build settings: Select Blues boards → Swan R5 (pnum=SWAN_R5)
and set USB support (if available) to None (usb=none). If the USB CDC
stack is enabled and no USB host is attached (solar/LiPo power in the field),
the STM32's USB wakeup interrupt fires continuously and the Swan never stays in
STOP2. Debug output goes to the UART on the Swan's STLINK debug connector
instead, which an ST-LINK V3 exposes as a virtual COM port (see Serial Logging
With STLINK).
Flashing with the Arduino IDE: Open trailer_fleet_tracker_starnote.ino,
apply the build settings above, and click Upload. Use the Swan's USB-C
port (on the Swan module itself) for the DFU bootloader (hold BOOT, tap
RST), or upload over SWD with an ST-LINK on the debug connector.
Flashing with arduino-cli:
# Compile and upload — usb=none disables the USB CDC stack, which must be
# off for the Swan to sleep.
arduino-cli compile -b STMicroelectronics:stm32:Blues:pnum=SWAN_R5,usb=none firmware/trailer_fleet_tracker_starnote/
arduino-cli upload -b STMicroelectronics:stm32:Blues:pnum=SWAN_R5,usb=none \
-p /dev/cu.usbmodem* firmware/trailer_fleet_tracker_starnote/firmware/trailer_fleet_tracker_starnote/sketch.yaml sets the same board
options, so if you run arduino-cli compile in that directory without -b, it
picks them up too.
Debug serial logging: The #define usbSerial debugSerial line in
trailer_fleet_tracker_starnote_helpers.h is commented out by default. To turn
on logging during development, uncomment that line, recompile, and open a
serial monitor at 115200 baud on the ST-LINK's virtual COM port. That's the
Swan's STLINK debug connector, not its USB-C port, which has no CDC serial in
this build. You'll see [heartbeat], [departed], [location], and [sleep]
log lines on each wake cycle. Comment the line back out before deploying to the
field.
7.2 Module responsibilities
| Responsibility | Where in sketch |
|---|---|
Notecard config: hub.set, card.transport, card.location.mode, card.motion.mode | notecardConfigure() |
| Compact Note templates (all three Notefiles) | defineTemplates() |
| Env-var overrides from Notehub | fetchEnvOverrides() |
| Motion state query | isMoving() |
| Time and voltage reads | getEpoch(), getBatteryVoltage() |
| GNSS state capture for transition events | captureGnssState() |
| GPS validity gate (location and heartbeat Notes) | hasValidGnssFix() |
| State machine, sleep/wake scheduling | loop() |
| Note emission | sendTransitionEvent(), sendLocationNote(), sendHeartbeatNote() |
| Transition event FIFO queue and retry | enqueuePendingEvent(), drainPendingQueue() |
Host sleep (STOP2 until the Notecard raises ATTN) | host_sleep.h: cxSleepBegin(), cxSleepUntilAttn() |
| Application state (held in RAM across sleep cycles) | AppState |
7.3 Motion and GPS strategy
The Notecard's built-in accelerometer handles motion detection, configured with
card.motion.mode.
The firmware uses motion:5, seconds:60, so the Notecard calls the trailer
"moving" when five or more motion events pile up in a single 60-second bucket,
and "stopped" when the bucket goes quiet. sensitivity:2 (25 Hz / ±8G) is
tuned to catch the low-frequency road vibration of a loaded trailer without
tripping on wind buffeting or dock impacts while it's parked.
The state machine manages GPS mode explicitly instead of relying on the
Notecard's implicit periodic-mode motion gating. That gating is only documented
for the Notecard's own GPS module and isn't guaranteed on the Starnote for
Iridium's combined GPS hardware path. On first boot, notecardConfigure()
issues
card.location.mode {"mode":"off"}
because the unit always starts out parked. On each PARKED→MOVING departure the
firmware issues
card.location.mode {"mode":"periodic", "seconds":<moving_ping_secs>} to start
GPS acquisition, and on each MOVING→PARKED arrival it issues
card.location.mode {"mode":"off"} to shut the GPS module down for the parked
dwell. Toggling it explicitly guarantees the GPS module never runs while the
trailer sits parked, however the Starnote for Iridium's combined GPS hardware
interacts with the Notecard on this path. On this hardware, GPS comes from the
Starnote for Iridium's combined Iridium+GPS antenna, and the standard
card.location API returns the fix with no special handling.
If you later change the moving_ping_mins environment variable in Notehub,
fetchEnvOverrides() stores the new moving_ping_secs value right away. If
the trailer is MOVING at the time, it also reissues
card.location.mode {"mode":"periodic"} with the updated seconds field
immediately. If the trailer is parked, the new period takes effect on the next
PARKED→MOVING departure. The compact template's _lat / _lon keywords embed
the most recent fix in location and heartbeat Notes, while transition event
Notes use explicit lat/lon/evt_time fields captured at transition
detection time (see §7.4).
GPS fix validity gating. Before queuing any Note that embeds location data,
the firmware calls hasValidGnssFix(), which issues card.location and checks
whether the Notecard reports a non-zero lat/lon with no error. card.location
returns the last cached fix whatever the current GPS mode is (periodic or off),
so this check doesn't add any GPS-on time. It keeps a freshly installed unit,
where GPS has never gotten a fix, from sending Notes with silently zeroed
coordinates.
trailer_location.qo: suppressed entirely when no valid fix is available. A location Note with zeroed coordinates is useless to the fleet. Since suppression doesn't advancelast_location_at, the firmware tries again on the next moving-state wake once it has a fix.trailer_event.qoandtrailer_heartbeat.qo: always sent, because departure/arrival events and battery voltage matter too much to suppress. Instead, they carry agps_validfield (1= confirmed fix,0= no fix available). Downstream receivers can use that flag to tell a confirmed location from an invalid placeholder and skip map plotting or geofence checks when there's no fix.
GPS fix capture on transition events. On the wake cycle where a
PARKED→MOVING or MOVING→PARKED transition is first detected, the firmware calls
captureGnssState(), a single card.location query that returns the
Notecard's currently cached lat/lon. Again, card.location returns the last
cached fix whatever the GPS mode, so this costs no extra GPS-on time. The
captured coordinates, validity flag, and current epoch go into the
PendingEvent struct. Every delivery attempt for that event (including retries
on later wakes after communication failures) uses that stored snapshot, not the
Notecard's GPS state at retry time. So a departure Note retried two hours later
still carries the location and timestamp from when the departure was detected,
not the current parked position.
For arrival events, captureGnssState() runs while GPS is still in
periodic mode (the step that turns GPS off comes right after), so the cached fix
is within one moving_ping_mins interval of the stop. For departure events
after a long parked dwell, captureGnssState() runs before GPS is turned back
on, so the cached fix is from the trailer's last trip and could be hours or
days old. gps_valid is still 1 in that case, because the fix is
structurally valid even if it's aged. If you need fresh departure coordinates,
you can extend the firmware to turn on GPS, poll card.location until a new fix
comes in, and then call captureGnssState. That costs 30–90 seconds of extra
GPS-on time and battery draw on every departure event.
7.4 Event payload design
All three Notefiles use
"format":"compact"
templates to keep the over-the-air size small. Every satellite packet has a
maximum size.
The compact format strips the JSON envelope and stores Notes as fixed-length
binary records, and at the field sizes used here (uint8, float16, float32,
int32), each Note comes to about 14–20 bytes on the wire, well under the limit.
Payload Reference (for downstream integrators):
| Notefile | Trigger | Fields | Notes |
|---|---|---|---|
trailer_event.qo | Departure or arrival | type (1=departed, 2=arrived), dwell_h (parked duration in hours), gps_valid (1=fix, 0=none), lat, lon, evt_time | sync:true, delivered over cellular right away. Falls back to Iridium on the next window if cellular is unavailable. |
trailer_location.qo | Moving state, interval elapsed | None in the body, just the GPS fix and time the Notecard embeds (_lat, _lon, _time). | Batched and sent at the next outbound window (60–360 minutes, depending on battery state). |
trailer_heartbeat.qo | Parked state, interval elapsed | volt, gps_valid | Batched and sent every 6 hours (default, overridable). Use it to confirm solar charging (declining volt = charging failure). |
Sample trailer_event.qo (departed, 18.5 hours of dwell, GPS fix confirmed at
detection time):
{
"file": "trailer_event.qo",
"body": {
"type": 1,
"dwell_h": 18.5,
"gps_valid": 1,
"lat": 41.8781,
"lon": -87.6298,
"evt_time": 1746182400
},
"sync": true
}type 1 = departed, 2 = arrived. dwell_h is the hours parked before this
departure, and it's 0 for arrival Notes. gps_valid is 1 if a valid GPS fix
was available at detection time and 0 if not (a freshly installed unit, for
example). When gps_valid is 0, ignore lat/lon. lat and lon are the
GPS coordinates captured at transition detection time, and evt_time is the
Unix epoch from that same wake. Timestamp accuracy is bounded by
parked_check_mins (for departures) or moving_ping_mins (for arrivals), and
the location is the Notecard's most recently cached fix at detection time. The
host writes these fields explicitly at detection time so they survive retried
deliveries intact. trailer_location.qo and trailer_heartbeat.qo use the
Notecard's auto-populated _lat/_lon/_time keywords. Event Notes use
explicit host-supplied fields instead, so a Note retried on a later wake never
picks up a stale post-transition GPS state.
Sample trailer_heartbeat.qo (parked, battery healthy, GPS fix available):
{
"file": "trailer_heartbeat.qo",
"body": {
"volt": 3.84,
"gps_valid": 1
}
}Watch volt across consecutive heartbeats, since a steady decline is your early
warning that the solar panel or charge path needs attention. Seeing
gps_valid: 0 on a new unit's first few heartbeats is normal, and it clears
once the Notecard gets its first fix.
trailer_location.qo has no application body fields. Its only payload is the
GPS fix, which the Notecard embeds from its last acquired fix. The firmware
suppresses location Notes entirely when there's no valid fix, so every
trailer_location.qo that reaches Notehub carries real coordinates.
7.5 Low-power strategy
Host sleep. After every state-machine cycle, loop() calls
cxSleepUntilAttn() (in host_sleep.h). It sends a
card.attn sleep
request for the computed interval, waits for the Notecard to pull ATTN low,
and puts the Swan into STM32 STOP2, where it draws a few microamps and keeps its
RAM. When the interval is up, the Notecard raises ATTN, and the rising edge on
Swan D5 wakes the host. Execution picks up on the next line of loop(). Since
the Swan resumes in place instead of cold-booting, the AppState struct 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 wire), loop() waits out the interval awake and tries again on the next
cycle, so the cadence holds either way.
GPS suppression. The firmware issues card.location.mode {"mode":"off"} on
each MOVING→PARKED arrival and card.location.mode {"mode":"periodic"} on each
PARKED→MOVING departure, so a parked trailer never runs the GPS module.
Trailers spend most of their lives at docks, yards, and distribution centers,
which makes this explicit GPS gating the biggest single power saving in the
system.
Voltage-variable sync. hub.set is configured with
voutbound:"high:60;normal:120;low:360;dead:0". As the solar battery drains,
the cellular outbound sync interval stretches from 1 hour to 6 hours, and it
stops entirely if the battery gets critically low. Transition events use
sync:true to ask for priority delivery no matter what the outbound schedule
says. On cellular that wakes the radio right away. On NTN it queues the Note
for the next satellite transmission opportunity instead of suspending delivery
entirely.
7.6 Retry and error handling
isMoving(),getEpoch(), andgetBatteryVoltage()all guard against a NULL response and return safe defaults instead of crashing the state machine.fetchEnvOverrides()checks theerrfield on the Notehub response before trusting the body. If the Notecard hasn't set up a session yet, the env response contains an error, and the firmware carries on with its last known defaults.- Notecard configuration is gated on
config_version, which only gets set oncenotecardConfigure()anddefineTemplates()both succeed. A transient failure leaves the tracker retrying everyCONFIG_RETRY_SECS(sleeping in between) instead of emitting events against an unconfigured Notecard. Every reset zeroesAppState, so a firmware update always starts from a clean state and reapplies the Notecard-side configuration. - The firmware ignores integer env var inputs outside their documented ranges and keeps the current value, so a typo in Notehub can't set the ping interval to 0 or 65535 minutes.
7.7 Key code snippet 1: transport configuration
After two cellular failures in a row, the Notecard routes through the Starnote
for Iridium module on its own. The firmware's only involvement is the single
API call in notecardConfigure() that sets up the fallback. If the Notecard
rejects it, configuration is marked incomplete and retried on the next wake.
req = notecard.newRequest("card.transport");
if (!req) return false;
JAddStringToObject(req, "method", "cell-ntn");
if (!sendAndCheck(req, "card.transport")) return false;7.8 Key code snippet 2: compact template definition
All three Notefiles use the compact format so Notes are small enough to go out
over satellite intact. trailer_location.qo and trailer_heartbeat.qo use the
Notecard's auto-populated _lat/_lon/_time keywords. The host adds each of
those Notes once, on the wake it describes, so the Notecard's GPS state at
note.add time is the right value to embed. trailer_event.qo uses
explicit host-supplied lat/lon/evt_time fields because transition
events may be retried on later wakes. Capturing and storing the values at
detection time means retries always carry the original departure or arrival
location, not a post-transition GPS state. In the code below, NOTEFILE_EVENT
is "trailer_event.qo" and PORT_EVENT is 50. The location and heartbeat
templates use ports 51 and 52.
// trailer_event.qo — departure / arrival transitions (sync:true, immediate)
J *req = notecard.newRequest("note.template");
if (!req) return false;
JAddStringToObject(req, "file", NOTEFILE_EVENT);
JAddNumberToObject(req, "port", PORT_EVENT);
JAddStringToObject(req, "format", "compact");
J *body = JAddObjectToObject(req, "body");
JAddNumberToObject(body, "type", 21); // TUINT8: 1=departed, 2=arrived
JAddNumberToObject(body, "dwell_h", 12.1); // TFLOAT16: parked dwell hours
JAddNumberToObject(body, "gps_valid", 21); // TUINT8: 1=valid fix, 0=no fix
// lat, lon, evt_time are explicit fields written from values captured at
// transition time rather than auto-populated keywords (_lat/_lon/_time).
// This ensures retried events carry the original transition location and
// timestamp rather than the Notecard's current GPS state at retry time.
JAddNumberToObject(body, "lat", 14.1); // TFLOAT32: GPS lat at transition
JAddNumberToObject(body, "lon", 14.1); // TFLOAT32: GPS lon at transition
JAddNumberToObject(body, "evt_time", 14); // TINT32: Unix epoch at transition
if (!sendAndCheck(req, "note.template " NOTEFILE_EVENT)) return false;7.9 Key code snippet 3: voltage-variable sync with host sleep
The voutbound string stretches the sync interval as the battery drains.
notecardConfigure() sends this hub.set right after card.voltage sets the
lipo thresholds that define the voltage buckets. VOUTBOUND_PROFILE is
"high:60;normal:120;low:360;dead:0" and VINBOUND_PROFILE is
"high:120;normal:240;low:720;dead:0", both in minutes:
// hub.set — periodic sync with voltage-variable outbound cadence.
// The voutbound / vinbound profiles stretch sync intervals as the solar
// battery drains, keeping the trailer visible even in extended cloudy
// dwell periods. Transition events use sync:true to bypass this schedule.
req = notecard.newRequest("hub.set");
if (!req) return false;
JAddStringToObject(req, "product", PRODUCT_UID);
JAddStringToObject(req, "mode", "periodic");
JAddStringToObject(req, "voutbound", VOUTBOUND_PROFILE);
JAddStringToObject(req, "vinbound", VINBOUND_PROFILE);Between wakes, cxSleepUntilAttn() puts the Swan into STOP2, and the
Notecard's ATTN pin brings it back. setup() arms the wake once, and every
loop() pass ends with the sleep request:
// Arm the ATTN wake: the Notecard raises ATTN (Swan D5) to end each sleep.
cxSleepBegin();
// ...
#ifdef usbSerial
Stream *log = &usbSerial;
#else
Stream *log = NULL;
#endif
if (!cxSleepUntilAttn(notecard, sleep_secs, NULL, log)) {
// The Notecard didn't take the sleep request, or ATTN never went low
// (check the XI ATTN -> Swan D5 wire). Keep the cadence and retry.
// ...
delay(sleep_secs * 1000UL);
}7.10 Key code snippet 4: motion-triggered state transition
Every departure starts with a card.motion query in isMoving(), which
returns false when the Notecard doesn't answer or reports an error, so a failed read suppresses
transitions for that wake instead of faking one. The firmware calculates the
dwell time from the stored parked_since epoch and attaches it to the Note, so
fleet managers can measure detention without any outside tracking.
captureGnssState() runs once, on the wake where the transition is detected,
and the GPS switch to periodic (elided below) comes right after it. The
captured fix goes into the PendingEvent FIFO and is passed to every
note.add attempt, so retried events always carry the original detection-time
location and timestamp. drainPendingQueue() then tries to deliver the event
on the same wake. §7.3 covers GPS freshness on
departures after long parked dwells.
bool moving = false;
bool motion_ok = isMoving(moving);
uint8_t prev = state.current_state;
// ...
if (motion_ok) {
if (prev == STATE_PARKED && moving) {
float dwell_h =
(state.parked_since > 0 && time_ok && now > state.parked_since)
? (float)(now - state.parked_since) / 3600.0f
: 0.0f;
// ...
// Capture departure GNSS state BEFORE enabling GPS. GPS mode
// is currently "off" (parked), so card.location returns the
// Notecard's last cached fix — the best available position at
// departure time. That fix may be stale after a long dwell;
// gps_valid=1 only means a fix exists, not that it is fresh.
float cap_lat = 0.0f, cap_lon = 0.0f;
uint8_t cap_gps_valid = 0;
captureGnssState(cap_lat, cap_lon, cap_gps_valid);
// ...
state.current_state = STATE_MOVING;
if (time_ok && now > 0) state.last_location_at = now;
enqueuePendingEvent(state, EVENT_DEPARTED, dwell_h,
(time_ok && now > 0) ? now : 0U,
cap_lat, cap_lon, cap_gps_valid);
// ...
}
}
// ...
// ── Attempt immediate delivery of any newly-queued event ─────────────
drainPendingQueue(state);8. Data Flow
Collected. Every wake reads the Notecard accelerometer's moving/stopped status. While the trailer is moving, the firmware also collects GPS coordinates from the Notecard's periodic GPS module. While it's parked, it collects the LiPo battery voltage from the Notecard's ADC.
Transmitted.
| Notefile | Trigger | Cadence | Transport |
|---|---|---|---|
trailer_event.qo | State transition (depart or arrive) | On event, sync:true | Cellular (immediate); NTN satellite (next transmission window) |
trailer_location.qo | While moving, interval elapsed | Every moving_ping_mins (default 15 minutes) | Batched, outbound window |
trailer_heartbeat.qo | While parked, interval elapsed | Every heartbeat_hours (default 6 hours) | Batched, outbound window |
On cellular, queued Notes flush at the outbound window, which is 60 minutes
at high battery and 120 minutes at normal battery, stretching to 360 minutes at
low battery (matching VOUTBOUND_PROFILE). Transition events skip the queue
with sync:true, which wakes the radio right away to deliver them.
On NTN/satellite, sync:true marks transition events as high priority, but
the Notecard can't interrupt a satellite orbital pass on demand. Instead of
triggering an immediate radio wake, events are queued and delivered at the next
scheduled Iridium transmission opportunity. This is documented Notecard
behavior. Expect delivery
within the next satellite window (usually minutes, depending on Iridium LEO
geometry), not the sub-60-second latency you'd get on cellular.
Routed. All three Notefiles go to Notehub. From there, set up routes to send them on to your fleet-visibility platform, TMS webhook, or analytics store (see the Notehub routing docs).
Triggers and alerts. A trailer_event.qo with type:1 (departed) is your
signal to close a detention clock, log an outbound scan, or send a "tractor has
hooked" notification. An arrival (trailer_event.qo with type:2) starts the
detention clock. If volt in trailer_heartbeat.qo keeps dropping below ~3.6V
over consecutive reads, it's time for a yard check on the solar charging path.
9. Validation and Testing
Expected event cadence. On first boot, last_heartbeat_at is zero, so the
heartbeat condition fires on the first wake where the Notecard has a valid clock
(now > 0). The Notecard syncs its clock during the initial cellular session,
which finishes within a few minutes of first power-on, so the heartbeat gets
queued locally on the next parked-check wake (~5 minutes in). Heartbeat Notes
are not marked sync:true, so the Notecard doesn't send them right away.
They ride along on the next scheduled outbound sync window, which opens after
60 minutes at full battery (voutbound high:60) or 120 minutes at
nominal charge (voutbound normal:120). Under typical battery conditions,
expect the first trailer_heartbeat.qo to show up in Notehub
roughly 1–2 hours after first power-on.
Commissioning state. AppState lives in RAM, so every power cycle, reset,
or reflash starts fresh. On the first wake after that, the firmware seeds
parked_since with the current Unix epoch from the Notecard's clock. If a unit has already been running in the
field and then gets reflashed, its clock is already synced, and the first
departure carries an accurate dwell measured from the moment of the reflash. A
brand-new unit at its very first power-on has no time reference until the
Notecard completes its first cellular session (usually within a few minutes).
If the clock isn't available yet on that first wake, parked_since stays at
0, and the first departure reports dwell_h: 0. To avoid that edge case on a
new unit, let the device sit powered and stationary until a _session.qo
appears in Notehub (which confirms clock sync) before the trailer moves.
A trailer that's already been running and is sitting in the yard with a charged battery should produce:
- Zero
trailer_event.qoNotes (no hookups) - Zero
trailer_location.qoNotes (not moving) - One
trailer_heartbeat.qoevery six hours (four per day) - Periodic
_session.qoevents confirming cellular connectivity
After a hookup, departure, run, and drop, you should see two trailer_event.qo
events (one departed, one arrived), plus trailer_location.qo events spaced 15
minutes apart for the length of the trip.
Bench validation with Mojo. The Blues Mojo is a precision coulomb counter that sits inline between the LiPo and the Notecarrier XI +VBAT pad, and it reports cumulative mAh to the Notecard over Qwiic at 1% accuracy. §5.1 shows where it goes inline.
Expected current draw (from the Notecard low-power design guide, the NOTE-WBEX datasheet, and the Iridium 9704 modem datasheet):
| Phase | Expected draw |
|---|---|
| Notecard idle (between syncs, radio off) | ~8–18 µA @ 3.7V |
| Iridium 9704 modem between satellite sessions (cellular available, NTN not active) | Powered off by Starnote; see the board-level row below |
| Starnote for Iridium board supporting circuitry quiescent (regulators, level shifters, supervisory MCU) | Not published by Blues, so measure with Mojo before sizing |
| Swan host in STOP2 (woken by ATTN on D5) | ~2–10 µA |
| Swan active (brief wake cycle, ~1–2 seconds) | ~5–25 mA |
| GPS acquisition via Starnote antenna (first fix, ~30–90 seconds) | ~20–50 mA |
| Cellular session (LTE-M data transfer, NOTE-WBEX) | ~200–300 mA avg, ≤2 A peak |
| Iridium satellite session (Starnote uplink, ~15–30 seconds) | ~300–500 mA avg, peaks up to 8.5 A at the modem (supplied by the XI's supercapacitors) |
In cell-ntn mode, Starnote cuts power to the Iridium modem whenever it isn't
sending, so the modem itself adds almost nothing while cellular is available.
Blues doesn't publish a board-level quiescent figure for the Starnote for
Iridium's supporting circuitry (voltage regulators, level shifters, and
supervisory MCU). That board-level number is the biggest unknown in this
design's parked-mode power budget, and you can't state a reliable daily energy
total or size the solar panel until you've measured it. Use the Mojo bench
procedure (§5.1) to measure the full system's quiescent floor before you pick
the final panel and battery.
Important: The Iridium modem can draw up to 8.5 A during a transmit burst. The Notecarrier XI's supercapacitor bank supplies those bursts, and the LiPo keeps the bank topped up between transmissions, so the battery must stay connected at all times. Don't run a first satellite session on a heavily discharged cell (below ~3.6 V).
For high-latitude (above 60°N), winter, or chassis-mounted deployments where panel harvest drops to 2–3 peak sun hours a day, upgrade to a 3–5 W panel and a 4 Ah LiPo so there's enough reserve to get through several cloudy days in a row.
Here are three Mojo trace signatures worth recognizing:
- Healthy (parked, cellular available): a near-continuous quiescent floor (the measured Starnote board draw, so use this run to establish that value), a brief ~2 seconds blip every 5 minutes (motion check + Swan wake), and one 10–30 seconds cellular burst at ~200 mA every 2 hours at normal battery.
- Satellite fallback session: a 15–30 seconds burst at ~300–500 mA with a short high peak. That's the Iridium satellite exchange, and you'll see it when cellular has failed and the Notecard routes through the Starnote.
- Stuck awake: continuous current well above the quiescent floor means the
Swan isn't entering STOP2. Check the XI
ATTN→ SwanD5wire (without it, the firmware logs[sleep] ATTN sleep failedand waits out each interval awake). Also confirm the firmware was built with USB support set to None (usb=none), because with the USB CDC stack enabled the Swan leaves STOP2 within a millisecond.
Functional test without a real trailer. You can check the departure/arrival
state machine without driving anywhere. Shake or tap the Notecarrier gently to
set off accelerometer motion events. The Notecard counts events in 60-second
buckets, but the host only wakes to query card.motion on the
parked_check_mins cadence (5 minutes by default). After shaking, give it up
to parked_check_mins + 60 seconds + cellular sync time (roughly 6–7
minutes at the defaults) before you expect a trailer_event.qo with type:1 in
Notehub. To speed up bench testing, temporarily drop parked_check_mins to 1
with a Notehub environment variable, and the motion check fires within about 90
seconds of the shake.
For the arrival event, note the asymmetric detection latency. Once the
tracker switches to STATE_MOVING, it sleeps for moving_ping_secs (15 minutes
by default) between wakes. Departure detection is bounded by parked_check_mins
(5 minutes by default), while arrival detection is bounded by moving_ping_mins
(15 minutes by default). To see a type:2 Note after letting the unit sit
still, wait up to moving_ping_mins + sync time (roughly 16–17 minutes at
the defaults).
10. Troubleshooting
Device won't stay asleep (stuck awake, draining battery fast):
- Check that the ATTN wire from the Notecarrier XI's
ATTNheader pin to SwanD5is intact, and that the XI'sENpin is left unconnected. - Confirm the firmware was built with USB support set to None
(
usb=none, see §7.1). If the USB CDC stack is enabled and no USB host is attached, the STM32's USB wakeup interrupt pulls the Swan out of STOP2 within a millisecond. - Watch the debug output with
usbSerialenabled (uncomment it in helpers.h and use the ST-LINK virtual COM port). You should see a[sleep] sleeping Nsline every cycle, then nothing until the next wake. - If
[sleep] ATTN sleep failedfollows it, either the Notecard didn't accept thecard.attnrequest orATTNnever reachedD5. Recheck the wire and the Notecard's I²C connection.
No departure/arrival events in Notehub:
- Confirm the accelerometer is working by checking Notehub for
_session.qoevents (connection handshakes). If none show up, the Notecard isn't connecting. - Check that
PRODUCT_UIDis set correctly inhelpers.h(Notehub: Project Settings → ProductUID). - Move the device physically to trigger a departure. Departure detection is
bounded by
parked_check_mins(default 5 minutes), so wait up to 6 minutes and then check Notehub. - For arrival (the device must be in MOVING state), wait up to
moving_ping_mins+ sync time (default ~16 minutes) for the Note to appear.
Position always shows as GPS invalid (gps_valid=0):
- GPS is off on purpose while the trailer is parked (see §7.3). Trigger a departure so the device enters MOVING state and turns on periodic GPS.
- Once it's moving, GPS takes 30–90 seconds to get a first fix. Position Notes
embed the most recent fix, so the first Notes of a trip may show
gps_valid=0if there's no fix yet. - Check the antenna routing. The Starnote's u.FL port has to run through the SMA bulkhead fitting to the combined Iridium+GPS antenna outside the enclosure.
Low battery warnings (volt < 3.6V) persist:
- The 0.6 W solar panel provides ~250–300 mAh/day in good conditions (4 peak sun hours). In cloudy weather or a high-latitude winter, harvest can fall below the daily draw. Most of that draw is radio sessions, since a ~30–60 µA quiescent floor only adds up to about 0.7–1.4 mAh a day.
- Run a Mojo current-draw trace (§9) to confirm the measured quiescent floor. If it's well above the expected 30–60 µA range, a supporting regulator may be failing.
- For harsh environments, upgrade to a 3–5 W panel and a 4 Ah LiPo (see §11).
Iridium fallback isn't triggering (device only sends on cellular):
- Iridium satellite mode only kicks in automatically when cellular is unavailable. On a bench with cellular available, events always go over LTE-M.
- To test Iridium, move the device somewhere with no cellular coverage (check with a phone). On the next transmission window, the Notecard routes through the Starnote.
- Check that the Starnote firmware is up-to-date.
Template registration fails (see error in serial log):
- The firmware registers the compact templates (ports 50, 51, 52) once on first boot. If registration fails, the device retries after 60 seconds (CONFIG_RETRY_SECS).
- Confirm Notehub connectivity by checking for
_session.qoevents. - Template port collisions happen when two Notefiles share a port number. Each Notefile needs its own port (50, 51, and 52 here), so give any templates you add unused ports. Every device in a project shares one PRODUCT_UID, and that's expected.
11. Limitations and Next Steps
Every reference design has to stop somewhere, and this one stops at the core problem: tractor-independent visibility with global coverage on trickle solar. The items below are deliberate trade-offs and the places where a production deployment will add hardening. All of them were chosen on purpose, and all of them are worth knowing before you scale past a pilot.
Simplified for this POC
These are the trade-offs to understand before a pilot grows into a fleet.
Transition detection latency and GPS freshness are wake-bound. The firmware
only samples motion at wake boundaries, which means every parked_check_mins
while parked (for departure detection) and every moving_ping_mins while moving
(for arrival detection). Transition events get stamped with the wake time and
the Notecard's cached GPS fix at that moment, not the exact instant of the
hookup or drop. For departure events after a long parked dwell, the GPS module
has been off the whole time, so the cached fix may be the trailer's last known
pre-dwell location and potentially hours or days stale. gps_valid is
still 1, because the fix is structurally valid and only its freshness is in
question. Retried deliveries always carry the original detection-time capture
and are never re-stamped with the current state. If you need fresh departure
coordinates, the firmware can be extended to issue
card.location.mode {"mode":"continuous"} and poll until a valid fix comes in
before enqueuing the departure event. That costs 30–90 seconds of extra GPS-on
time per departure.
Satellite sync introduces latency. Over Iridium NTN, sync:true event
Notes are queued instead of sent right away, because the Notecard can't
interrupt a satellite orbital pass on demand the way it can wake a cellular
modem. Departure and arrival events go out at the next Iridium transmission
opportunity, which may be anywhere from seconds to a few minutes away depending
on LEO geometry. This is documented Notecard
behavior.
The first-boot dwell baseline depends on clock sync. On the first wake
after any power cycle, reset, or reflash, the firmware seeds parked_since
with the current epoch if the Notecard's clock is available. A
brand-new unit has to finish its first cellular session to sync its clock, and
if the trailer moves before then, the first departure reports dwell_h: 0.
Wait for a _session.qo in Notehub (which confirms clock sync) before the first
trip, so the first departure carries a meaningful dwell value.
There is no motion-event persistence check, so short moves are a blind spot.
The Notecard's 60-second / 5-event motion bucket
(card.motion.mode motion:5, seconds:60) is the only debounce layer. It
declares a window "moving" only when five or more accelerometer events pile up
within 60 seconds, which filters out brief impulses from dock impacts or
vibration from nearby equipment. The host firmware, though, acts on the first
parked-state wake where card.motion reports moving, with no second-sample
persistence check at the host level. A single moving read enqueues a
departure event right away. It follows that a move that starts and ends
entirely within one parked_check_mins interval is invisible to this
firmware. The trailer can leave, travel, and park again between two host
wakes without the host ever seeing a moving reading. At the default 5-minute
parked-check cadence, short yard moves and brief tractor hookup attempts that
resolve before the next wake can slip by unnoticed. Production deployments that
need to see short moves should lower parked_check_mins (e.g., to 1–2 minutes
with the env var), extend the firmware to require two moving reads in a row
before enqueuing a departure event, or both. The motion and seconds
parameters in card.motion.mode can also be tuned per equipment type to adjust
bucket sensitivity.
Each position Note carries a single GPS fix. Location Notes embed the
Notecard's most recent periodic GPS fix, so on a fast highway run the fix in any
given Note may be up to moving_ping_mins old. Dropping moving_ping_mins to 5
minutes with the env var gets you more frequent fixes, at the cost of more Notes
per trip.
Solar sizing is minimal. The 0.6 W panel is sized to trickle-charge a parked trailer in normal operating conditions. Long stretches of cloudy weather, high-latitude winter deployments, or a shaded mounting spot may not give enough solar input to cover even the modest quiescent draw. In harsh environments, a 3–5 W panel and a larger LiPo (4 Ah or more) make more sense.
There is no tamper or cargo detection. This build only covers location and dwell. Sensors for door-open detection, cargo weight, temperature, or tire pressure are natural extensions, but this POC doesn't include them.
Mojo is bench-only. The firmware doesn't read Mojo's coulomb counter registers, and power just flows through the Mojo's power path. Adding a cumulative mAh field to the heartbeat Note would be a simple extension.
DFU is not wired up. This POC doesn't configure Notecard Outboard Firmware Update on the Swan, so field firmware updates currently need physical access to the Swan's USB-C port.
An alternative hardware path uses Skylo NTN for
land routes only. For fleets that stay on North American land-route corridors
inside Skylo's geostationary footprint, Notecard for Skylo
(NOTE-NBGLWX) on a Notecarrier
CX
puts cellular, Skylo NTN satellite, GPS, and the accelerometer in a single M.2
module, without a Starnote or a separate MCU board. The same Notefile schemas
(trailer_event.qo, trailer_location.qo, trailer_heartbeat.qo) and the
same Notehub project still apply. Skylo's service area covers defined
land-route corridors only, with no ocean-route or polar coverage. The
Choosing Between Skylo and Iridium
guide has the full coverage comparison.
Production Next Steps
Past location and dwell, the next steps tie the tracker into the trailer's own systems and the back office.
J1939 / trailer ABS hookup through the 7-way trailer connector would read brake and ABS status from the trailer's onboard systems whenever a tractor is connected.
A door sensor (magnetic reed switch on the rear doors) would detect
load/unload events and report them in a trailer_cargo.qo Notefile.
Detention billing automation would use a Notehub route that fires a webhook
into the TMS on every trailer_event.qo with type:1 (departed). The dwell_h
field would drive automatic detention invoices for any dwell past the
contracted free-time allowance.
Outboard DFU, which means wiring the Notecard's DFU GPIO to the Swan RESET/BOOT0 pins, would allow over-the-air host firmware updates across the whole fleet through Notehub.
12. Summary
The carrier whose $100,000 reefer used to vanish at a shipper's dock can now see it directly, with no tractor involved. A cellular Notecard plus Starnote for Iridium on a Notecarrier XI with a Swan host sends telemetry over cellular first and falls back to pole-to-pole Iridium LEO satellite. The same hardware works on a regional dry-van running I-80 and on a chassis crossing the Pacific. GPS stays off during parked dwells, the Swan sleeps between motion checks, and voltage-variable sync stretches the cellular window as the battery drains, which lets the whole device live comfortably on trickle solar. Transition events are stamped at detection time and replayed exactly across retries, so the dispatch view shows the moment a trailer left, not wherever it happened to be when the Note finally landed. The same architecture carries over to chassis pools, container yard management, and intermodal drayage, or anywhere else the question is where the unpowered equipment is, no matter who's pulling it today.
On This Page
- 1. Project Overview
- 2. System Architecture
- 3. Technical Summary
- 4. Hardware Requirements
- 5. Wiring and Assembly
- 6. Notehub Setup
- 7. Firmware Design
- 7.1 Dependencies and flashing
- 7.2 Module responsibilities
- 7.3 Motion and GPS strategy
- 7.4 Event payload design
- 7.5 Low-power strategy
- 7.6 Retry and error handling
- 7.7 Key code snippet 1: transport configuration
- 7.8 Key code snippet 2: compact template definition
- 7.9 Key code snippet 3: voltage-variable sync with host sleep
- 7.10 Key code snippet 4: motion-triggered state transition
- 8. Data Flow
- 9. Validation and Testing
- 10. Troubleshooting
- 11. Limitations and Next Steps
- 12. Summary