Returnable Container / Tote Pool 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/tote_pool_tracker/ so it can learn how the device works and help you adapt it to your hardware.
This project is an asset location tracking solution for pools of reusable containers like plastic totes, pressurized kegs, and gas cylinders. It reports location on motion events and daily heartbeats, using cellular connectivity and the Notecard's built-in accelerometer. Each container sends a motion event when it starts or stops moving and a daily check-in while it sits idle, all over LTE Cat-1 bis. The hardware is a Notecarrier CX with a Notecard Cell+WiFi (§4 has the BOM). Operators tune the heartbeat interval and motion sensitivity from the Blues Notehub cloud service, with no reflashing.
1. Project Overview
The problem. Supply chains run on reusable containers. Plastic totes carry produce from the farm to the distribution center, pressurized cylinders shuttle gas between filling plants and customer sites, and stainless kegs make the brewery-to-bar loop thousands of times before they're retired. The economics only work if the containers keep moving through the loop, and they leak out of their pools all the time. One gets left on a loading dock past its pickup window. Another ends up in a back corner of a customer's warehouse, on the wrong carrier, or forgotten at a rail interchange for six months. Industry estimates put pool shrinkage anywhere from 5% to 20% a year per container type. It's a quiet, spread-out cost that rarely shows up as one dramatic incident, but it steadily eats into the pool's working capacity and the replacement budget.
Underneath it all is a visibility problem, and the usual fixes each break down somewhere in the supply chain. Passive RFID tags and barcodes need scanner infrastructure at every gate, and active RFID needs per-site readers the container owner doesn't control. Manual cycle counts are expensive, slow, and never quite in sync with reality. GPS plus cellular looks like the obvious answer, but pure GPS is a poor fit for an asset that spends 90% of its time sitting still in a warehouse. The receiver draws tens of milliamps waiting for a fix that adds very little over just knowing the container is "at the Memphis DC."
In this project, the Notecard's built-in accelerometer watches for motion while the device sleeps at microamp levels. When it sees the container move (a forklift picks it up, it gets loaded onto a truck, or it's shunted across a rail yard), it wakes the host MCU. The host queues a motion event, and the Notecard delivers it over cellular, using cell-tower and WiFi AP triangulation for location. Triangulation adds no real power cost beyond the cellular session that has to happen anyway to deliver the Note. It's accurate enough at the site level to tell you whether a tote is at Supplier A or Customer B, and it doesn't need GPS hardware or wait on a GPS cold start. A daily heartbeat confirms the device is alive even when the container sits untouched for a week.
Why Notecard. Containers crisscross supplier warehouses, customer sites,
truck yards, and rail interchanges, and most of those places aren't on any WiFi
network the container owner controls. You can't realistically ask every
customer to share their wireless network, and there's no consistent AP
infrastructure across a national supplier base. Cellular removes all of those
dependencies and works the same way at a Chicago cold-storage facility as it
does at a rural agri-distribution depot. Location comes from cell-tower and WiFi
AP triangulation (mode:"wifi,cell"), so there's no GPS hardware and no GPS
cold-start latency. On each Notehub session the Notecard scans the surrounding
cell towers, plus any WiFi APs it can see, and Notehub resolves both into a
latitude/longitude that it appends to every event automatically. In AP-dense
places like warehouses and distribution centers, the WiFi data can tighten
accuracy from the kilometer scale to tens of meters. In rural or outdoor areas
with no APs, the device falls back to cell-only triangulation automatically. The
WiFi scan adds a little time to the start of each session (usually a few
seconds, depending on AP density and radio conditions), which is negligible next
to the cellular session itself. Where APs are around, that's an accuracy boost
that costs you next to nothing. This is asset location
tracking built around the
supply chain's real geography instead of lab conditions.
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.
Bench and limited field-trial assembly. This reference build puts a Notecarrier CX, a Notecard, and a rechargeable 2 Ah LiPo battery in a polycarbonate IP67 housing (Hammond 1554C2GY or equivalent, see §4). The housing attaches directly to the container with non-penetrating fasteners, such as stainless zip ties, band clamps, cradle brackets, or industrial-grade adhesive pads, depending on the container type. Nothing is wired to the container, and the sites don't need any infrastructure. Expect to swap batteries every 12–24 months in a low-motion deployment, which makes this hardware a fit for bench validation and short field trials rather than a multi-year production rollout. The device configures itself on first power-on and runs on its own, and fleet operators manage every device and tune per-fleet settings from Notehub. Production deployments that need 3–5+ years between swaps replace the LiPo path with a custom carrier board and a Li-SOCl₂ primary cell (see §11).
Safety: not rated for classified or explosive atmospheres. This build uses standard commercial electronics and a LiPo battery. It is not ATEX, IECEx, Class I Division 2, or intrinsically safe certified, and it must not be deployed in classified or explosive atmospheres, including anywhere flammable gas or vapor may be present, unless a recognized testing body has certified the complete assembly (enclosure, battery, antenna, and all electronics) for that environment.
2. System Architecture
Event Format in Notehub
The firmware emits two Note types. Notehub appends location to each one automatically and routes them separately:
Motion event (tote_event.qo), which appears within ~30 seconds of the
motion threshold being crossed:
{
"file": "tote_event.qo",
"body": {
"event": "departed",
"moving": true,
"battery_mv": 3851.0,
"cycle": 14
},
"where_lat": 35.9728,
"where_lon": -86.7653,
"where_location": "Nashville TN USA",
"where_country": "US"
}Daily heartbeat (tote_heartbeat.qo), which fires once per
heartbeat_hours (default 24 h):
{
"file": "tote_heartbeat.qo",
"body": {
"battery_mv": 3920.0,
"moving": false,
"cycle_count": 42,
"reason": 1
},
"where_lat": 35.9728,
"where_lon": -86.7653,
"where_location": "Nashville TN USA"
}The reason field is 0 (boot), 1 (heartbeat), or 2 (low battery).
Notehub appends the where_* fields after cell-tower triangulation, so the
firmware doesn't need any code to produce location data.
Device-side responsibilities. The Notecarrier CX's STM32 host is designed to sleep through most of every day. Its only jobs are managing the duty cycle and making quick decisions when something does happen. On each wake (from either a motion-state change or the daily heartbeat timer), the host works out why it woke, formats the right Note, hands it to the Notecard over I²C, and goes back to sleep within seconds. For the 23+ hours a typical container sits still each day, the host MCU stays in STM32 STOP2, drawing a few microamps, until the Notecard's ATTN pin wakes it. The Notecard's own low-power IMU does all the accelerometer monitoring, so the host is only involved for the few seconds it takes to queue a Note.
Notecard responsibilities. The Notecard does the patient work. Its built-in accelerometer watches for motion-state changes around the clock and fires the ATTN pin the instant the configured motion threshold is crossed. On every Notehub session it scans the surrounding cell towers and nearby WiFi APs for cell-tower and WiFi triangulation. Notehub turns that scan into a latitude/longitude and appends it to every event automatically, without any per-event firmware code. Notes queue locally through outages and go out in order when connectivity comes back. Environment variables pushed from Notehub arrive on each inbound sync, so a pool manager can retune motion sensitivity or heartbeat cadence without a firmware update or a truck roll.
Notehub responsibilities. The
Notecard
manages its own cellular session on supported carrier networks worldwide
through its embedded global SIM and delivers data to
Notehub over the Internet. From there,
Notehub ingests and stores every event,
resolves the cell-tower data into a human-readable location, and applies your
project-level routes. The two streams are kept apart at the source on purpose
(tote_event.qo for motion events and tote_heartbeat.qo for daily
heartbeats), so each can go to a different destination with a different
urgency, and the routes don't need filter logic.
Fleets and Smart
Fleets are the natural
way to organize a mixed pool. A pool manager can set different motion
thresholds for totes, kegs, and cylinders without ever forking the firmware.
Routing to the cloud (high level only). Notehub can route to HTTP, MQTT,
AWS, Azure, GCP, Snowflake, and several other destinations, and route setup
depends on your project. This project doesn't ship a specific downstream
endpoint, so see the Notehub routing
docs for details. A
typical fleet integration sends tote_event.qo to a warehouse management
system (WMS) or custom fleet portal in real time, and tote_heartbeat.qo to
long-term storage for pool health analytics.
3. Technical Summary
- Clone this repo and open
/firmware/tote_pool_tracker/tote_pool_tracker.ino. - Define
PRODUCT_UIDas your Notehub project's ProductUID. Until you do, the#ifndef PRODUCT_UIDguard near the top of the sketch stops the build with an error (see §6). - Install the Arduino Core for STM32, Blues Wireless Notecard, and STM32duino Low Power libraries (see §7.1).
- Jumper the Notecarrier CX's ATTN pin to D5 (see §5).
- Build for the Cygnet board with USB support set to None, and upload (see §7.1).
- Power the assembly (USB or battery). The first time it connects over cellular, the device associates itself with your Notehub project, and it shows up in the Devices tab within 1–2 minutes.
- Tap or shake the assembly firmly for 3–4 seconds, and a
tote_event.qowith"event":"departed"should arrive in Notehub within ~30 seconds. Set it down, and after 30 seconds of stillness an"event":"arrived"follows.
The sections below cover assembly, firmware, and Notehub configuration in detail.
Here's a sample Note from this device:
{
"file": "tote_event.qo",
"body": {
"event": "departed",
"moving": true,
"battery_mv": 3851.0,
"cycle": 14
},
"where_lat": 35.9728,
"where_lon": -86.7653,
"where_location": "Nashville TN USA",
"where_country": "US"
}4. Hardware Requirements
Bench/POC BOM. These components build the bench prototype documented here. The Notecarrier CX and its LiPo charge path are the right platform for bench validation and limited field trials. A production deployment aiming for 3–5+ years between swaps needs a custom carrier board with a direct +VBAT input and a Li-SOCl₂ primary cell instead of the LiPo. §11 covers that path.
| Part | Qty | Rationale |
|---|---|---|
| Notecarrier CX | 1 | Integrated carrier with an onboard STM32L433 host MCU, so you don't need a separate host board. The host sleeps in STOP2 between wakes until the Notecard's ATTN pin wakes it. |
| Notecard Cell+WiFi (MBGLW) | 1 | Cellular connectivity plus a built-in accelerometer for motion detection. The Cell+WiFi variant turns on both cell-tower and WiFi AP triangulation (mode:"wifi,cell"), which improves location accuracy in AP-dense environments and falls back to cell-only automatically where there are no APs. Per the NOTE-MBGLW datasheet antenna requirements, the MBGLW includes a PCB antenna for its WiFi function, so no external WiFi hardware is needed in plastic or polycarbonate enclosures. The on-board PCB antenna scans nearby WiFi APs through non-metallic walls as is. The MBGLW also has a dedicated WiFi U.FL connector for swapping in an external 2.4 GHz antenna. Other Blues reference builds that put the same Notecard inside a metal enclosure (e.g., a commercial HVAC RTU sidecar) do connect an external lead to this connector. They do that because the metal enclosure blocks the on-board PCB antenna's 2.4 GHz signal, and the Notecard hardware itself is the same. In this plastic-tote deployment, the on-board PCB antenna is the right choice and all you need. In a metal enclosure or a metal-bodied container, the metal shields the 2.4 GHz signal no matter which antenna is connected inside. The on-board PCB antenna and any antenna sitting entirely inside the metal box are blocked equally, and WiFi AP triangulation will suffer unless the antenna's radiating element is mounted outside the metal shell or behind a verified non-metallic RF window. See the optional WiFi antenna row below. |
| Blues Mojo (commissioning/validation) | 1 | Coulomb counter spliced inline on the battery rail to measure real energy use during bring-up. It doesn't ship with the field tracker. It stays on the bench as commissioning and regression-testing equipment. |
| SparkFun Lithium Ion Battery, 2 Ah (PRT-13855) | 1 | 3.7 V / 2000 mAh LiPo with a JST PH connector that matches the Notecarrier CX battery port. It's the POC power source, with a realistic service life of 12–24 months. §11 covers the Li-SOCl₂ production path. |
| Hammond Manufacturing 1554C2GY (field deployment enclosure, IP67/NEMA 4X) | 1 | 120 × 65 × 40 mm UV-stabilized polycarbonate enclosure, NEMA 4X / IP67–IP68, with a silicone gasket and flat lid. The internal cavity (~113 × 58 × 34 mm) fits the Notecarrier CX (76 × 38 mm board) lengthwise, with the LiPo pack lying flat on top of the board. It's rated for outdoor service at truck yards, rail interchanges, port terminals, and keg/cylinder washdown environments. A clear-lid variant, 1554C2GYCL, is available if you want to check the LEDs through the lid. You can get it from Mouser, DigiKey, and RS Components. |
| Hammond Manufacturing 1591XXBSFLBK (bench bring-up only, IP54, not suitable for outdoor or field deployment) | 1 | 113 × 63 × 28 mm ABS enclosure, IP54, with a flanged gasketed lid and integral PCB standoffs. It's only adequate for bench bring-up and sheltered indoor environments. Do not use for outdoor field deployment. Truck yards, rail interchanges, port terminals, and container washdown workflows need at least an IP67 enclosure, so use the Hammond 1554C2GY above. Available from Hammond, Mouser, and RS Components. |
| SparkFun SMA to U.FL Cable, 150 mm (WRL-18568) (optional, external antenna path) | 1 | Carries the cellular signal from the Notecard's u.FL socket to an SMA female connector seated in the enclosure wall. You only need it for enclosed or stacked-container deployments where the bundled patch antenna can't see enough sky. |
| SparkFun LTE Hinged External Antenna, 698 MHz–2.7 GHz, SMA Male (CEL-16432) (optional) | 1 | Stub antenna (SMA male, 2.3 dBi) that covers all LTE Cat-1 bis bands globally. Thread the pigtail's SMA connector through a ¼″ panel hole in the enclosure wall, and this antenna screws straight onto the pigtail's SMA female end. Alternate: Taoglas TG.30.8113 (3 dBi, 698–2700 MHz, hinged SMA male) for ~0.7 dB more gain where coverage is challenging. |
| Blues Flexible Cellular/WiFi Antenna (SKU 233-00006) (optional, only useful if the radiating element can be placed outside any metal shielding) | 1 | 85 mm × 14.5 mm flexible FPC antenna (Molex), 698 MHz–4.0 GHz, with a 180 mm u.FL coaxial tail. Connect it to the Notecard's WiFi U.FL connector. In metal-enclosure or metal-bodied installations, this antenna only helps if the radiating element is routed outside the metal, either through a cable gland or behind a dedicated non-metallic RF window in the enclosure wall. Placed entirely inside a metal enclosure, it's still shielded by the metal and does no better than the on-board PCB antenna. Not required for plastic or polycarbonate enclosures, where the MBGLW's on-board PCB WiFi antenna scans nearby APs through the non-metallic walls as is. |
| Essentra Components CG-M16-1-BK Cable Gland, M16, IP68 (optional, cable-through variant only) | 1 | Only needed if you swap the stub antenna above for a remote-mount antenna with an integrated SMA cable (e.g., a magnetic-mount antenna on the outside of the shipping container). It seals the cable exit through the enclosure wall and accepts 3–10 mm cable diameters. |
Blues hardware ships with an active SIM that includes 500 MB of data and 10 years of service, with no activation fees and no monthly commitment.
5. Wiring and Assembly
There isn't much wiring in this design. The Notecard's built-in accelerometer handles all the motion detection on its own, and in a plastic or polycarbonate enclosure the MBGLW's on-board PCB WiFi antenna takes care of AP scanning without any external hardware. That leaves six short steps between an empty enclosure and a tracker ready to clip onto a container. You seat the Notecard, connect the antennas, fit the ATTN-to-D5 jumper, connect the battery, splice in the Mojo for bench validation if you want it, and close the enclosure.
1. Notecard. Slide the Notecard Cell+WiFi (MBGLW) into the Notecarrier CX's M.2 slot, label-side up, and press until it seats. The Notecarrier CX's STM32 host talks to the Notecard over the I²C bus routed through the M.2 interface, so that part needs no wiring.
2. Antennas. The MBGLW has two antenna connections.
Cellular: Connect the bundled cellular patch antenna to the Notecard's cellular u.FL connector. In sealed polycarbonate enclosures or stacked-container deployments where the patch antenna can't see much sky, run a u.FL-to-SMA pigtail to an SMA connector seated in a ¼″ hole in the enclosure wall, and screw an external stub antenna onto it (see the optional BOM items). The bundled patch antenna is fine for bench testing and most open-top plastic housings.
WiFi: Per the NOTE-MBGLW datasheet antenna requirements, the MBGLW includes a PCB antenna for its WiFi function, so no connection or external hardware is needed in plastic or polycarbonate enclosures. The on-board PCB antenna scans nearby APs through the non-metallic walls at each Notehub session, and Notehub resolves that data to a location automatically. The MBGLW also has a WiFi U.FL connector, where you can substitute an external 2.4 GHz antenna when the enclosure material calls for it. Reference builds that put the same Notecard inside a fully metal enclosure do connect an external lead to this WiFi U.FL connector. They need it because the metal enclosure blocks the on-board PCB antenna's 2.4 GHz signal, and every MBGLW unit has the connector either way. In this plastic-tote deployment the on-board PCB antenna is the right choice, and no WiFi U.FL connection is required. If the device will be housed in a metal enclosure or mounted inside a metal-bodied container, note that metal shields the 2.4 GHz signal. Both the on-board PCB antenna and any antenna left entirely inside the metal are equally blocked, so connecting a flex antenna to the WiFi U.FL connector won't help unless you route it outside the metal. To keep WiFi AP scanning in a shielded installation, the antenna's radiating element has to sit outside the metal shell. Route a 2.4 GHz u.FL antenna (e.g., Blues Flexible Cellular/WiFi Antenna, SKU 233-00006) through a cable gland or behind a non-metallic RF window in the enclosure wall, then connect the u.FL tail to the Notecard's WiFi U.FL connector. Where you can live without WiFi scanning, the device falls back to cell-only triangulation automatically.
3. ATTN-to-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). The Notecard pulls ATTN low
while the host sleeps and raises it when a motion-state change or the heartbeat
timer should wake the host. The rising edge on D5 then wakes the STM32 from
STOP2. Leave the Notecarrier CX EN pin unconnected. On the CX it enables the
shared 3.3 V VIO rail (host, Qwiic, 3V3_OUT, and Notecard I/O), so driving
it from ATTN would brown out the whole board instead of putting the host to
sleep. Putting a Host to Sleep Between Sensor
Readings
walks through the full pattern.
4. Battery. Connect the SparkFun 2 Ah LiPo to the Notecarrier CX's 2-pin JST
PH battery connector (labeled LIPO). The keyed connector enforces polarity, so
don't force a reversed pack. The Notecarrier CX includes a LiPo charge circuit.
The battery charges whenever USB power is present, and the tracker runs from
the battery when USB is removed.
5. Mojo (bench bring-up only). For power validation during development,
splice the Mojo inline between the LiPo JST connector and the Notecarrier CX
LIPO input. Mojo's BAT+ / BAT- connect to the battery, and Mojo's LOAD+
/ LOAD- connect to the Notecarrier CX. The Mojo Qwiic connector attaches to
the Notecarrier CX's Qwiic port, and it reports cumulative mAh to the Notecard
over I²C. This measures the full assembly, host plus Notecard, on battery power.
Note: this firmware doesn't read Mojo data over I²C or publish any Mojo
telemetry in Notes. Mojo serves only as a bench commissioning and
regression-testing instrument, and it isn't deployed with the field tracker.
6. Enclosure. Mount the board and battery in the polycarbonate enclosure (Hammond 1554C2GY for field deployment, 1591XXBSFLBK for bench only), with any external cellular antenna cable routed through a cable gland. Attach the housing to the container with non-penetrating fasteners only: stainless zip ties or hose-clamp style straps on plastic totes, band clamps or collar brackets on kegs and cylinders, and industrial-grade VHB adhesive pads on flat polymer surfaces.
Safety: pressurized vessels must not be drilled or tapped. Kegs, gas cylinders, and any other pressurized container must never be drilled, tapped, welded, or structurally modified in any way to mount a tracker. Use only external band clamps, collar brackets, or strap-style mounts that grip the vessel's exterior surface without compromising its structural integrity.
Safety: not rated for classified or explosive atmospheres. This assembly is not ATEX, IECEx, Class I Division 2, or intrinsically safe certified. Unless the enclosure, battery, antenna, and complete assembly are fully certified, do not deploy it in any classified or explosive atmosphere, including the flammable-gas environments that come with certain gas-cylinder applications.
Important: the ATTN jumper is the whole sleep mechanism.
cxSleepUntilAttn() puts the host into STOP2 and counts on the ATTN→D5
rising edge to wake it. Without the jumper, the firmware sees that ATTN never
went low, stays awake, and polls motion every 15 minutes instead. The tracker
still works, but battery life takes a big hit. If you adapt this firmware to a
different carrier board, wire ATTN to any interrupt-capable host GPIO and
update CX_ATTN_PIN in cx_sleep.h. The Attention Pin
Guide has
background on the pin.
6. Notehub Setup
1. Create a project. Sign up at notehub.io and create
a project. Copy the
ProductUID, which looks
like com.your-company.your-name:tote-tracker.
2. Set the ProductUID in firmware. Open
tote_pool_tracker.ino
and add #define PRODUCT_UID "com.your-company.your-name:tote-tracker" (with
your value) above the #ifndef PRODUCT_UID guard near the top. You can also
pass it as a build flag:
-DPRODUCT_UID=\"com.your-company.your-name:tote-tracker\". Without one of
those, the guard's #error stops the build, so an unconfigured binary can't be
flashed by accident.
3. Claim the device. Power up the assembly. The first time it connects over cellular, the Notecard associates itself with your Notehub project, so there's no manual claim step. The device shows up in the Devices tab within a minute or two.
4. Organize into Fleets. Fleets
group devices that share configuration. For a mixed container pool, one fleet
per container type (totes, kegs, cylinders) or per customer region works well,
and each fleet can carry its own motion_threshold tuned to how those
containers get handled. Smart
Fleets can auto-assign
new devices to the right fleet based on a device name or serial number prefix,
which saves a lot of manual setup once the pool gets big.
5. Set environment variables. In Notehub, select the fleet you created above and open Environment in the left sidebar. Every variable is optional, and the table shows the firmware defaults. The device fetches them on its next inbound sync, and changes take effect without reflashing.
| Variable | Default | Purpose |
|---|---|---|
heartbeat_hours | 24 | Hours between heartbeat Notes while the container is stationary. Range: 1–168. Longer intervals stretch battery life, and shorter ones keep "last-seen" timestamps fresh. Heartbeat Notes go out with sync:true, so each one opens its own cellular session right away instead of waiting on the outbound periodic timer. Changing this value reapplies hub.set outbound/inbound in the same wake cycle, which keeps the Notecard's inbound polling cadence (environment variable pulls) in step with the heartbeat schedule. |
low_battery_mv | 3300 | Battery rail voltage (mV) below which the heartbeat Note's reason code becomes 2 (low battery). Use it as your cue to schedule a battery swap before the tracker goes dark. |
motion_threshold | 5 | Accelerometer events per time bucket needed before the Notecard calls the container "moving." Raise it if vibrating dock floors cause false departures, and lower it for gentle handling like roller conveyors. Range: 1–20. |
motion_bucket_sec | 30 | Length of each accelerometer counting bucket (seconds). Use it with motion_threshold to trade sensitivity against response latency. Range: 5–300. |
6. Configure routes. Add one
route for
tote_event.qo (immediate motion events to a WMS, fleet portal, or alert
webhook) and a second for tote_heartbeat.qo (daily pool health for a
long-term analytics store). Because the Notefiles are separate at the source,
each route can target its own destination and urgency without any filtering
logic.
What to expect in Notehub
The Notecard starts a session early on first boot, so events usually show up within a few minutes. The exact timing depends on network registration and the initial sync, and a weak-signal site or busy carrier can stretch that to 5–10 minutes. Once the device is registered, the Events tab should show:
_session.qo: automatic Notecard housekeeping on each cellular session. Seeing it confirms the radio is reaching Notehub.tote_heartbeat.qo: one perheartbeat_hours(default 24 h), sent withsync:trueso each one reaches Notehub as soon as a session comes up after the on-device timer fires. Thereasonfield is0on first power-on (boot),1on later timer wakes (heartbeat), and2when the battery is belowlow_battery_mv. Notehub appendswhere_lat,where_lon, andwhere_locationonce it has resolved the cell-tower data.tote_event.qo: emitted withsync:truewhen the container crosses the configured motion threshold (it starts or stops moving). Theeventfield is"departed"(stopped → moving) or"arrived"(moving → stopped).
7. Firmware Design
The firmware spans four files in the firmware/ directory. Keep them together,
because the main sketch references the helpers:
| File | Role |
|---|---|
tote_pool_tracker.ino | Main sketch: setup(), loop(), and global definitions |
tote_pool_tracker_helpers.h | Shared types, constants, and extern declarations |
tote_pool_tracker_helpers.cpp | All helper-function implementations |
cx_sleep.h | Host sleep: card.attn request, STOP2 entry, ATTN wake |
When you open or build the sketch directory, the Arduino toolchain compiles the
headers and .cpp along with the .ino automatically.
7.1 Installing and flashing
Dependencies:
- Arduino core for STM32:
stm32duino/Arduino_Core_STM32. Add the index URLhttps://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.jsonunder File → Preferences → Additional Boards Manager URLs. The Notecarrier CX's host uses the Cygnet board definition (see the build settings below). Blues Wireless Notecardlibrary:note-arduino. Install it from the Arduino Library Manager (search for "Blues Wireless Notecard") or witharduino-cli lib install "Blues Wireless Notecard". The note-arduino releases page shows which version is current when you install.STM32duino Low Powerlibrary (and its dependencySTM32duino RTC):stm32duino/STM32LowPower. It provides the STOP2 entry and ATTN-pin wake thatcx_sleep.huses.
Build settings: The Notecarrier CX's host is the same STM32L433 as the Blues
Cygnet, so select Blues boards → Cygnet (pnum=CYGNET) and set USB
support (if available) to None (usb=none). If the USB CDC stack is
enabled and no USB host is attached (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 (when DEBUG is defined in
tote_pool_tracker_helpers.h) 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
firmware/tote_pool_tracker/tote_pool_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:
# Compile and upload — point at the sketch *directory* so arduino-cli
# picks up the helper .cpp and headers automatically. 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/tote_pool_tracker
arduino-cli upload -b STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none -p /dev/cu.usbmodem* firmware/tote_pool_trackerfirmware/tote_pool_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.
Replace /dev/cu.usbmodem* with your actual port, which is COMx on Windows
or /dev/ttyACM* on Linux.
After upload, serial output is disabled by default. Release builds leave
out Serial calls on purpose to avoid the ~1 mA UART wake-time penalty on a
battery-powered device. To turn logging on, uncomment #define DEBUG in
firmware/tote_pool_tracker/tote_pool_tracker_helpers.h and reflash, then open
the serial monitor at 115200 baud to watch each wake cycle. The host prints
a few lines on first boot and on each wake after that, then goes quiet while it
sleeps. A few seconds of activity per cycle is normal.
7.2 Modules
| Responsibility | Function |
|---|---|
Notecard one-time configuration (hub.set, card.voltage, card.triangulate, card.motion.mode) | notecardConfigure() |
| Heartbeat Notefile template registration | defineTemplates() |
| Environment variable fetch and clamped override | fetchEnvOverrides() |
| Motion configuration reapplication when env vars change | inline in loop() after fetchEnvOverrides() |
hub.set outbound/inbound reapplication when heartbeat_hours changes | inline in loop() after fetchEnvOverrides() |
| Motion state read from Notecard accelerometer | readMotionMoving() |
| Battery voltage read from Notecard rail monitor | readBatteryMv() |
Immediate motion event emission (sync:true) | sendMotionEvent() |
Immediate daily heartbeat emission (sync:true, template-backed) | sendHeartbeat() |
| Sleep timing, ATTN arming, and STOP2 entry | enterSleep() |
7.3 Motion and location strategy
Motion detection runs entirely on the Notecard's built-in accelerometer, which
you configure with
card.motion.mode.
The motion parameter sets the threshold, meaning how many accelerometer events
have to happen within a configurable time bucket before the Notecard flips the
motion status from "stopped" to "moving" (or back). When the status changes,
the Notecard fires the ATTN pin to wake the host. Reading the current status
takes a simple card.motion request, and the Notecard answers with
"mode":"moving" or "mode":"stopped".
Location comes from
card.triangulate
set to mode:"wifi,cell", which turns on both cell-tower and WiFi AP scanning.
On each Notehub session the Notecard scans the surrounding cell towers and
nearby WiFi APs. Notehub resolves the combined data to a latitude/longitude and
appends the position (where_lat, where_lon, where_location,
where_country) to every event automatically, without GPS hardware or any
per-event firmware code. In AP-dense places like warehouses and distribution
centers, the WiFi data tightens accuracy from the kilometer scale to tens of
meters. Where the Notecard can't detect any APs, it falls back to cell-only
triangulation automatically. The WiFi scan adds a little time to session
startup (usually a few seconds, depending on AP density and radio conditions).
Next to the LTE Cat-1 bis cellular session that's negligible, so in AP-rich
environments the extra accuracy comes almost free. This use case only needs to
know which site or city a container is at, and cell-plus-WiFi triangulation
gives that precision at far lower power than GPS. WiFi AP scanning uses the
MBGLW's on-board PCB WiFi antenna and works through plastic and polycarbonate
enclosure walls without any external hardware. In metal-enclosure or
metal-shielded installations, the metal blocks the 2.4 GHz signal, and WiFi AP
triangulation will suffer unless the antenna's radiating element sits outside
the metal shell (see §5).
7.4 Event payload design
tote_event.qo (motion events, untemplated, sync:true):
{
"file": "tote_event.qo",
"body": {
"event": "arrived",
"moving": false,
"battery_mv": 3851.0,
"cycle": 14
},
"sync": true
}Motion events are untemplated. There are only a handful per container per day
at most, and a flexible JSON schema is handy while the design is still
changing. sync:true tells the Notecard to skip the outbound cadence timer
and start a cellular session as soon as the Note is queued. The event usually
reaches Notehub 15–60 seconds after the motion threshold is crossed, depending
on network conditions and the configured bucket duration. Notehub appends the
triangulated where_* fields before passing the event to any configured
routes.
tote_heartbeat.qo (daily, sync:true,
template-backed):
{
"file": "tote_heartbeat.qo",
"sync": true,
"body": {
"battery_mv": 3920.0,
"moving": false,
"cycle_count": 42,
"reason": 1
}
}sync:true makes the Notecard open a cellular session as soon as the Note is
queued. That guarantees each daily heartbeat reaches Notehub as soon as a
session comes up after the on-device timer fires. It doesn't wait for the next
periodic outbound sync, and it doesn't depend on a motion-triggered session
happening to come first. Heartbeats use a Note template registered at cold
boot. All four fields are fixed-width (battery_mv is a 4-byte float, moving
a 1-byte bool, cycle_count a 4-byte uint, and reason a 1-byte int), so
every record is stored on the Notecard as a compact, fixed-length binary entry
instead of a variable-length JSON blob. At one heartbeat a day over a
three-year deployment, the bandwidth you save over raw JSON adds up on a
constrained SIM data plan. The reason field is a numeric code: 0 = boot
(first power-on), 1 = heartbeat (normal timer wake), and 2 =
heartbeat_low_battery (battery below low_battery_mv). Using a number keeps
the record fully fixed-width.
7.5 Low-power strategy
setup() runs once at power-up to configure the Notecard, and each pass through
loop() is one wake cycle. After queuing the right Note, enterSleep() calls
cxSleepUntilAttn() (in cx_sleep.h). That sends a
card.attn request with
mode:"sleep,arm,motionchange" and a seconds value equal to the time left
until the absolute next_heartbeat_epoch deadline, so a motion wake re-arms
only the time still owed instead of starting a fresh full interval. If the
epoch isn't available yet (before the first cellular sync), the function falls
back to the full configured interval. When failed_send_count is non-zero,
sleep is also capped at RETRY_WAKE_SEC (15 minutes) so a pending Note gets
retried promptly. The Notecard pulls the ATTN pin low, and the host enters
STM32 STOP2, where it draws a few microamps and keeps its RAM. When the
Notecard raises ATTN, the rising edge on D5 wakes the host and loop() runs
again. Since the host resumes in place, the ToteState struct just stays in
RAM, and nothing gets serialized to the Notecard.
Adding "arm,motionchange" to the sleep mode means the Notecard wakes the host
on whichever comes first, a motion-state transition (the accelerometer
threshold being crossed) or the heartbeat timer running out. motionchange only
fires ATTN during the sleep window if arm is there too. Without it, the mode
string accepts the keyword but never arms the wake source (per the asset
tracking
guide).
Sleep current stays very low because the host is in STOP2, but total battery
energy depends mostly on the number of cellular sessions. Each daily heartbeat
and each "departed"/"arrived" motion event opens its own cellular session
through sync:true. So a container that moves several times a day burns a lot
more energy than one that sits still for a week.
Both Note types are queued with sync:true, so the Notecard opens a cellular
session as soon as each Note is ready and skips the outbound cadence timer
entirely. That guarantees each daily heartbeat reaches Notehub as soon as a
session comes up after the on-device timer fires. It isn't held for the next
periodic outbound sync, and motion-triggered sessions in between don't affect
it. The firmware still sets
hub.set
outbound/inbound to match heartbeat_hours and reapplies it at runtime
whenever that value changes. That keeps the Notecard's inbound polling cadence,
which is what pulls environment variable updates from Notehub, in step with the
heartbeat schedule.
7.6 Retry and error handling
- If
card.motionfails three times in a row (no response, an error, or an unrecognizedmode),readMotionMoving()returns the previously savedwas_movingstate. That way a transient I²C failure can't be mistaken for a real motion-state change and set off a spurious event. readBatteryMv()returns0.0on a NULL or error response. Abattery_mv: 0sentinel in a Note stands out from any normal in-range reading and flags that the voltage wasn't available, instead of quietly reporting a misleading non-zero value.fetchEnvOverrides()writes resolved environment values to both the runtimeg_*globals and theg_state.desired_*fields. If it fails (a NULL response or a Notecard error), it leaves both alone, so a brief connectivity outage can't quietly revert fleet tuning to the compile-time defaults.- After
fetchEnvOverrides()returns,loop()compares the desiredg_motion_threshold/g_motion_bucket_secvalues withg_state.last_applied_motion_threshold/g_state.last_applied_motion_bucket_sec. If they differ, the firmware reissuescard.motion.modeand only updates thelast_applied_*fields once that succeeds, so a failed reissue gets retried automatically on the next wake. heartbeat_hoursworks the same way throughg_state.last_applied_heartbeat_hours. A desired value that differs from the last applied one triggers ahub.setreissue with the updatedoutbound/inboundcadence in the same wake cycle. The firmware also re-anchors the absolute heartbeat deadline to the new interval right away (when it has a valid epoch), so the device starts sleeping toward the new schedule without running out the old one first.- On a retry wake where the pending Note finally goes through, Branch 2
compares the current motion state with
g_state.pending_moving(the motion state captured when the pending record was first created) and emits a follow-up event if they differ. For example, say a"departed"Note fails and the container stops before the retry succeeds.pending_movingistrue(the container was moving when the event was queued),now_movingisfalse, and the firmware emits an"arrived"event on that same retry wake. The baseline ispending_movingand notg_state.was_moving, becausewas_movinggets updated at the end of every wake, failed retry wakes included. Across several retries it drifts toward the current motion state and would hide the difference.pending_movingis written exactly once per pending record and never changes during retries. Design limitation: the firmware holds at most one pending Note at a time. If the container changes state more than once during a long retry period, only the final motion state gets compared with the original baseline. This single-pending-Note model can't recover intermediate transitions that reverse and then reverse again.
7.7 Key code snippet 1: combined motion + timer sleep
After queuing the wake-cycle Note, the firmware arms a sleep with two wake
conditions. It computes the sleep duration from the time left until the
absolute heartbeat deadline, so motion wakes don't reset the daily schedule,
and caps it at RETRY_WAKE_SEC when a Note needs retrying. Before the first
cellular sync, when there's no epoch yet, it sleeps the full interval.
cxSleepUntilAttn() sends the card.attn request (and checks the result,
since the host stays alive to see the response), waits for ATTN to go low,
and enters STOP2. Passing "arm,motionchange" adds the second wake source, so
ATTN also rises if the motion status changes before the timer runs out.
void enterSleep(uint32_t now_epoch) {
// Derive sleep duration from the remaining time to the absolute deadline.
uint32_t sleep_sec = g_heartbeat_hours * 3600UL;
if (now_epoch > 0 && g_state.next_heartbeat_epoch > now_epoch) {
sleep_sec = g_state.next_heartbeat_epoch - now_epoch;
}
// Cap at the retry interval when a note.add failed this wake so the
// dropped note is retried promptly rather than waiting a full heartbeat
// interval.
if (g_state.failed_send_count > 0 && RETRY_WAKE_SEC < sleep_sec) {
sleep_sec = RETRY_WAKE_SEC;
}
#ifdef DEBUG
Stream *log = &debugSerial;
#else
Stream *log = NULL;
#endif
if (!cxSleepUntilAttn(notecard, sleep_sec, "arm,motionchange", log)) {
// The Notecard didn't take the sleep request, or ATTN never went low
// (check the ATTN -> D5 jumper). Stay awake for at most one retry
// interval so motion is still polled, then run the cycle again.
// ...
uint32_t wait_sec = sleep_sec < RETRY_WAKE_SEC ? sleep_sec : RETRY_WAKE_SEC;
delay(wait_sec * 1000UL);
}
}7.8 Key code snippet 2: cell-tower and WiFi AP triangulation configuration
The firmware sends this once at cold boot in notecardConfigure(), and
loop() resends it on each wake until the Notecard confirms it. On every
Notehub session after that, the Notecard scans cell towers and nearby WiFi APs
automatically, and Notehub resolves the combined data and appends the position
to each event.
if (!g_state.triangulate_confirmed) {
J *req = notecard.newRequest("card.triangulate");
JAddStringToObject(req, "mode", "wifi,cell");
JAddBoolToObject(req, "on", true);
JAddBoolToObject(req, "set", true);
J *rsp = notecard.requestAndResponse(req);
if (rsp) {
if (!notecard.responseError(rsp)) g_state.triangulate_confirmed = true;
notecard.deleteResponse(rsp);
}
}7.9 Key code snippet 3: motion event with immediate sync
sync:true skips the outbound cadence timer and starts a cellular session as
soon as the Note is queued. The event usually reaches Notehub 15–60 seconds
after the motion threshold is crossed. sendMotionEvent() first copies the
event ("departed" or "arrived"), motion state, battery reading, and cycle
count into the g_state.pending_* record. doSendMotionNote() then builds the
Note from that record, making up to three attempts, and clears it only once the
Notecard accepts the Note. If every attempt fails, the record is still there
for the next wake to replay:
J *req = notecard.newRequest("note.add");
JAddStringToObject(req, "file", FILE_EVENTS);
JAddBoolToObject(req, "sync", true); // immediate delivery
J *body = JAddObjectToObject(req, "body");
JAddStringToObject(body, "event", g_state.pending_event);
JAddBoolToObject(body, "moving", g_state.pending_moving);
JAddNumberToObject(body, "battery_mv", g_state.pending_battery_mv);
JAddNumberToObject(body, "cycle", (int)g_state.pending_cycle);
J *rsp = notecard.requestAndResponse(req);
if (!rsp) continue;
bool ok = !notecard.responseError(rsp);
notecard.deleteResponse(rsp);
if (ok) {
g_state.pending_note_type = PENDING_NONE;
return true;
}7.10 Key code snippet 4: heartbeat Note template
All four fields are fixed-width, so every heartbeat record encodes as a
compact, fixed-length binary entry. TFLOAT32, TBOOL, and TUINT32 are
type-hint macros from note-c, and 11 is the Note template system's 1-byte
integer placeholder (the same one used throughout the reference accelerators).
The REASON_* codes are 0 (boot), 1 (heartbeat), and 2 (low battery).
template_confirmed is set only when the Notecard accepts the template, and
loop() re-registers it on each wake until then.
J *req = notecard.newRequest("note.template");
JAddStringToObject(req, "file", FILE_HEARTBEAT);
JAddNumberToObject(req, "port", 10);
J *body = JAddObjectToObject(req, "body");
JAddNumberToObject(body, "battery_mv", TFLOAT32); // 4-byte float
JAddBoolToObject(body, "moving", TBOOL); // 1-byte boolean
JAddNumberToObject(body, "cycle_count", TUINT32); // 4-byte unsigned int
JAddNumberToObject(body, "reason", 11); // 1-byte int: see REASON_*
J *rsp = notecard.requestAndResponse(req);
if (rsp) {
if (!notecard.responseError(rsp)) g_state.template_confirmed = true;
notecard.deleteResponse(rsp);
}8. Data Flow
Collected. On each wake cycle: the current motion state ("moving" or
"stopped" from the Notecard's internal accelerometer), the battery voltage
from the Notecard's rail monitor, and the wake-cycle counter. There are no
external sensors to read, and the host is only awake for a few seconds per
cycle.
Transmitted.
tote_event.qo: queued withsync:trueon every motion-state transition, so the Notecard starts a cellular session as soon as the Note is ready instead of waiting on the outbound cadence timer. A normally active container sends a"departed"event when it starts moving and an"arrived"event when it stops, which gives you a matched pair for each transit leg. The Notecard delivers each Note to Notehub, which appends the triangulated location before passing it to any configured routes.tote_heartbeat.qo: emitted once perheartbeat_hours(default 24 h) whether or not there's been motion, queued withsync:true, and delivered right away in its own cellular session. It guarantees a daily "last seen alive" confirmation in Notehub, even for containers that sit idle for days or weeks, and no matter what motion-triggered sessions happen in between.
Routed. Both Notefiles pass through Notehub, which resolves and appends
where_lat, where_lon, where_location, where_country, and
where_timezone to each event as it arrives. From there,
routes can send the
events to any supported destination. That might be an HTTP/HTTPS webhook into a
WMS (warehouse management system) or TMS (transportation management system),
MQTT to a real-time fleet portal, or Snowflake/cloud storage for historical
pool analytics. Route setup depends on your project, and this reference design
doesn't ship a downstream endpoint.
In-band alerts. The reason code 2 (low battery) in tote_heartbeat.qo
is the main in-band signal for scheduling battery maintenance. Other alerting,
like spotting containers outside their expected site boundaries, belongs
downstream instead of in firmware (see §11).
9. Validation and Testing
Expected steady-state. A healthy tracker on an idle container sends one
tote_heartbeat.qo a day and no tote_event.qo events. Both Note types use
sync:true, so each opens its own cellular session and reaches Notehub as soon
as that session comes up after the on-device event. A tracker on an active
container sends anywhere from 0 to a dozen tote_event.qo events a day (one
"departed" and one "arrived" per transit leg), plus one
tote_heartbeat.qo. If neither file shows up in the Notehub Events tab after 5
minutes, the likeliest causes are a missing or wrong PRODUCT_UID, a
disconnected antenna, or firmware built with USB support enabled (see
§7.1).
Triggering a motion event on the bench. With the assembly powered (and the
ST-LINK serial port open at 115200 baud, if DEBUG is enabled), tap or shake
the Notecarrier CX firmly a few times. The Notecard's internal accelerometer
samples slowly in motion-detect mode, so it takes a few seconds of steady
motion to rack up enough events to cross the bucket threshold. A firm 3–4
second burst of taps is usually enough to clear the default threshold of 5. A
tote_event.qo with "event":"departed" should appear in Notehub within ~30
seconds. Set the device down, wait out the bucket duration (30 seconds of
inactivity by default), and "event":"arrived" follows.
Using Mojo to validate power behavior. Notecard's published idle current in low-power mode is about 8 µA at 3.7 V between Notehub sessions. The Notecard low-power design guide has the authoritative measured figures across SKUs and operating conditions. A transmit burst for a single queued Note usually lasts from a few seconds to tens of seconds at 50–250 mA average on LTE Cat-1 bis, and most of that is radio warm-up and network registration. The STM32L433 host in STOP2 adds roughly 1–2 µA.
Here's the expected 24-hour energy at one heartbeat a day, with no motion events and good signal (bench prototype, LiPo supply):
| Phase | Duration | Approx. current | Approx. energy |
|---|---|---|---|
| Deep sleep (Notecard idle, host in STOP2) | ~23.98 h | ~10 µA | ~0.24 mAh |
| Heartbeat wake and Note queue (~5 seconds host + I²C) | ~5 seconds | ~15–20 mA | ~0.03 mAh |
| Heartbeat sync:true cellular session (~20 seconds at ~150 mA avg LTE Cat-1 bis) | ~20 seconds | ~150 mA | ~0.83 mAh |
| Total active drain | ~1.1 mAh/day |
On paper, the 2000 mAh LiPo would cover that active drain for ~5 years. That ignores LiPo self-discharge, though, and at this duty cycle self-discharge dominates. A typical 2000 mAh LiPo self-discharges at 1–2% per month (~480 mAh/year), which eats roughly half the pack's capacity over 2 years even while the tracker sits still. Once you account for self-discharge and cell aging, a realistic LiPo service life is 12–24 months in a low-motion, good-signal deployment. Weak-signal retries, motion events, and cold temperatures each shorten that further. That's why this build is documented as bench/POC and not production. Multi-year deployments need the Li-SOCl₂ primary-cell path described in §11.
Getting 3 to 5+ years between swaps takes a Li-SOCl₂ primary cell (under 1% annual self-discharge and much higher volumetric capacity). That in turn needs a different carrier with no charging circuitry (see §11). Whatever the chemistry, the energy figures in the table above still help you size the active-drain budget.
For a useful Mojo bench check, leave the assembly running for 24 h and confirm the Mojo tally lands in the 1–5 mAh range. If it's off by more than ~5×, the cause is usually one of these:
- Host never sleeping: a flat, continuous 10–80 mA baseline. The usual
causes are a missing
ATTN→D5jumper (the firmware then waits awake and retries every 15 minutes) or firmware built with USB support enabled (the host leaves STOP2 within a millisecond). Check the jumper and theusb=nonebuild option (see §7.1). - Excessive cellular retries: sync bursts run longer than expected or fire
more than once a day. Check signal strength with
card.wirelessorhub.statusin the blues.dev In-Browser Terminal. - Motion false triggers:
tote_event.qoNotes pile up from dock-floor vibration or transport. Raisemotion_thresholdwith the Fleet environment variable and watch the event rate drop, with no reflash needed.
10. Troubleshooting
Device doesn't appear in Notehub Devices tab after 3–5 minutes.
- Make sure the
PRODUCT_UIDin your sketch matches exactly (copy and paste it from Notehub instead of retyping it). A mismatch is the most common cause. - Check the serial monitor at 115200 baud (uncomment
#define DEBUGintote_pool_tracker_helpers.hand reflash to see Notecard I²C trace output). - Confirm USB power is connected or the LiPo battery is seated properly in the JST connector (the keyed connector enforces polarity, so don't force it).
- Check that the Notecard is fully seated in the M.2 slot.
- Check signal strength. The Notecard may be registered but slow to sync, so give it up to 10 minutes in a weak-signal area.
Device appears but never sends an event.
- Shake or tap the assembly firmly for 3–4 seconds. The accelerometer samples slowly in motion-detect mode, so it needs sustained motion to accumulate events. Gentle handling or one quick tap won't cross the motion threshold.
- Confirm the motion threshold suits your environment. If it's set high (e.g.,
motion_threshold: 15), normal dock handling may not register. Lower the value in the Fleet → Environment panel and wait for the next inbound sync (~24 hours by default, or sooner, on the next cellular session, if you set off a motion event). - Make sure
tote_heartbeat.qoshows up at least once a day in the Events tab. If heartbeats never arrive, motion events won't either, since both come down to the same connectivity or configuration problem.
Event arrives once per minute instead of at motion state changes.
- The motion threshold is too sensitive. Raise
motion_thresholdfrom 5 to 10–15 in Fleet → Environment to cut false triggers from dock vibration, and increasemotion_bucket_secto 60 or higher so motion has to last over a longer window.
Battery drains much faster than expected (>5 mAh/day consumed).
- Confirm the host is really entering STOP2, either with the Mojo coulomb
counter or by measuring the battery rail directly. If the host draws a
continuous 10–80 mA, check the
ATTN→D5jumper and confirm the firmware was built with USB support set to None (see §7.1). If you're adapting this design to a different carrier, wireATTNto an interrupt-capable host GPIO and setCX_ATTN_PINincx_sleep.h(see the Attention Pin Guide). - Check signal strength, because weak-signal retries add cellular sessions and
burn more energy. Run
card.wirelessin the blues.dev In-Browser Terminal to see RSSI and bars. - If motion events are piling up more than once a day, raise
motion_thresholdand increasemotion_bucket_secto cut down on false triggers.
Event payload is missing a field (e.g., cycle is absent).
- Check the firmware version, since early versions may have had different payloads. Make sure you've uploaded the latest sketch and that the installed Blues Wireless Notecard library is up to date.
11. Limitations and Next Steps
This reference design is scoped to bench validation and limited field trials. It's meant to be the fastest way from "we should track these containers" to a live cellular uplink with site-level location. A few scope choices keep that path short on purpose. Each one is listed below with the production hardening that turns a 12–24 month LiPo prototype into a fleet that stays deployed for years.
Simplified for the POC
Each choice below keeps the prototype quick to build, and each comes with the work it needs before the trackers go out on a real container pool.
LiPo battery vs. Li-SOCl₂ is the primary scope constraint. This POC build
runs a rechargeable 2 Ah LiPo on the Notecarrier CX's charge-enabled power path.
As §9 explains, LiPo self-discharge caps practical
service life at 12–24 months no matter how low the active drain is. That's the
main reason this build is documented as a bench prototype and not a production
deployment. For real multi-year deployments, with 3 to 5+ years between swaps,
the right chemistry is Li-SOCl₂ (e.g., a D-size Tadiran TL-5930 at 3.6 V /
14.5 Ah, under 1% annual self-discharge). The catch is that the Notecarrier
CX's onboard charge IC is designed for LiPo chemistry. Connecting a Li-SOCl₂
primary cell directly to the JST LIPO port would run the charge circuit
against a non-rechargeable cell, which is unsafe. A production deployment on
Li-SOCl₂ needs a purpose-built carrier board with no charging circuitry and a
direct +VBAT input matched to the primary cell chemistry. That power path has
to be designed from scratch instead of adapted from a charge-enabled design.
The Notecarrier CX documented here is the right platform for prototyping and
limited field trials.
Not rated for classified or explosive atmospheres. This build uses standard commercial electronics and a LiPo battery, and none of them carry ATEX, IECEx, Class I Division 2, or intrinsically safe certification. Deployments in classified or explosive atmospheres (including certain gas-cylinder and industrial-gas environments where flammable gases or vapors may be present) need a purpose-certified assembly, where the enclosure, battery, antenna, and all the electronics carry the right hazardous-location rating. That certification work is far beyond the scope of this POC.
Enclosure fit and gland rating need checking when you adapt the design. The main BOM specifies the Hammond 1554C2GY (NEMA 4X / IP67, polycarbonate) for field deployment and lists the Hammond 1591XXBSFLBK (IP54, ABS) only for bench bring-up. If you switch to a different enclosure, check the interior cavity against the Notecarrier CX footprint (76 × 38 mm board) and the flat LiPo pack before you commit. Inner dimensions tighter than ~95 × 50 mm mean stacking the board and battery instead of laying them flat. Any cable gland where a cellular antenna pigtail leaves the enclosure has to be rated to the same IP level as the enclosure body. The Essentra M16 IP68 gland in the BOM covers that for both the 1554C2GY and any IP68-rated replacement.
Triangulation accuracy depends on the surroundings. This build defaults to
cell-tower and WiFi AP triangulation (mode:"wifi,cell"). In AP-dense places
like warehouses, distribution centers, and port terminals, the combined mode can
get accuracy down to 15–200 m, which is plenty to tell individual customer sites
apart. In rural or outdoor areas with few APs, the device falls back to
cell-only, which gives roughly 300 m to 5 km accuracy depending on tower
density. That's still good enough for site-level identification in most cases.
If you need sub-50 m precision (a specific dock door rather than a facility),
the upgrade path is a GPS fix through
card.location.mode
periodic. It costs you a GPS antenna, up to a few minutes of cold-start
latency, and extra power for every fix.
No server-side geofencing. The POC reports locations but doesn't evaluate
them. If you want an alert when a container leaves an expected zone or turns up
at an unexpected site, build it in the downstream fleet application or as a
Notehub JSONata route
transform. The
transform would compare where_lat/where_lon against fleet-level environment
variables that define site polygons, and fire a webhook when a container shows
up outside every known site. Either way, the site boundary data can change
without a firmware update.
No transit-leg pairing. The firmware reports "departed" and "arrived"
events on their own. Stitching them into origin-destination legs (Container #4712
left Supplier A at 08:15 and arrived at Customer B at 14:42) takes logic in the
fleet application, which has the context to make the match.
Single wake reason. The firmware never asks the Notecard which event
raised ATTN. It works the reason out from state instead. A motion-state
change means a motion wake, a pending Note means a retry wake, and anything
else is checked against the heartbeat deadline using card.time. Until the
Notecard has valid time, a wake with nothing else to explain it is treated as a
heartbeat. Asking the Notecard what fired would make that explicit, at the cost
of another I²C round trip on every wake.
No tamper or shock detection. The accelerometer could pick up sustained unusual orientations or sharp impact signatures. Both are workable firmware extensions, but this POC doesn't implement them.
Production Next Steps
Getting from this prototype to a fleet that runs for years starts with redesigning the power path, then adding the cloud-side logic a real tracking operation depends on.
A custom carrier board with a direct +VBAT input (and no charging circuitry) can run a D-cell Li-SOCl₂ primary, leaving the Notecarrier CX form factor for prototyping only.
Voltage-variable sync through
hub.set voutbound/vinbound
lets the Notecard cut back on cellular sessions by itself as the battery drains,
once you configure the voutbound/vinbound voltage thresholds. The shipped
firmware only sends hub.set with fixed outbound/inbound. To turn on
voltage-variable sync, you'd need either (a) a small firmware change that adds
voutbound/vinbound fields to the hub.set call, plus a check that the
hub.set reissue that fires when heartbeat_hours changes doesn't overwrite
them, or (b) a one-time commissioning step in the blues.dev In-Browser Terminal
that sets the fields directly on each device.
Server-side geofencing in Notehub JSONata would compare
where_lat/where_lon against fleet-level environment variables that define
site polygons, and fire a route to a webhook when a container shows up outside
every known site.
Notecard Outboard DFU delivers over-the-air host firmware updates to the whole fleet. That matters a lot for a tracker that may stay on a container for 5+ years without a hands-on service visit.
Per-container asset metadata (container ID, type, tare weight, inspection due date) can live in Notehub device-level environment variables, and a JSONata route transform can append it to events. The fleet portal then shows full container profiles without the tracker storing or sending any of that data itself.
12. Summary
The pool manager used to find out about container shrinkage at quarter-end,
when the replacement budget finally caught up with reality. Now every container
reports back on its own schedule. A tote_event.qo Note arrives within ~30
seconds of a container starting or stopping, and a tote_heartbeat.qo comes in
once a day even when it sits perfectly still. Each one carries battery state
plus a location that Notehub resolves from cell-tower and WiFi AP triangulation
and appends automatically. Assets that spend most of their time sitting still
need a tracker that clips on, wakes only when something interesting happens,
and costs next to nothing to run between wakes. The Notecard's built-in
accelerometer does the patient work of watching for motion at microamp power
levels, and its ATTN pin keeps the host MCU in STOP2, drawing a few microamps,
until there's something to report. Because the cellular service is prepaid, the
same hardware and firmware deploy the same way at an urban cold-storage
facility and a rural distribution depot, with no IT coordination at either
site. The data that comes back tells you where each container last checked in,
whether it moved today, and whether its battery is healthy. That's enough to
run weekly reconciliations without manual cycle counts, flag containers that
haven't moved in 30 days for recovery calls, and catch customers holding
containers past their agreed dwell time before it snowballs into a shortage
emergency.
On This Page
- 1. Project Overview
- 2. System Architecture
- 3. Technical Summary
- 4. Hardware Requirements
- 5. Wiring and Assembly
- 6. Notehub Setup
- 7. Firmware Design
- 7.1 Installing and flashing
- 7.2 Modules
- 7.3 Motion and location strategy
- 7.4 Event payload design
- 7.5 Low-power strategy
- 7.6 Retry and error handling
- 7.7 Key code snippet 1: combined motion + timer sleep
- 7.8 Key code snippet 2: cell-tower and WiFi AP triangulation configuration
- 7.9 Key code snippet 3: motion event with immediate sync
- 7.10 Key code snippet 4: heartbeat Note template
- 8. Data Flow
- 9. Validation and Testing
- 10. Troubleshooting
- 11. Limitations and Next Steps
- 12. Summary