Construction Equipment Anti-Theft Tracker with Immobilizer
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/construction_equipment_anti_theft/ so it can learn how the device works and help you adapt it to your hardware.
This project is a loss prevention reference design for construction equipment fleets. Fit it to a skid steer, light tower, portable compressor, or generator and the machine becomes a much harder target. It reports its own location continuously, sends an alert when it breaches its geofence or moves after hours, and accepts an ignition immobilizer command that you stage remotely. It runs on its own cellular-plus-satellite link and a small battery that a solar panel keeps topped up, so you don't need job-site WiFi or any help from IT.
1. Project Overview
The problem. Construction equipment theft is a serious, expensive problem, and it's chronically underreported. A skid steer that costs $60,000 to replace can be driven onto a flatbed and hauled off in under ten minutes, and a light tower or portable generator can be gone before the morning crew shows up. Nobody smashes and grabs a skid steer. The equipment is large and easy to identify, and it sits completely exposed on a site that often goes unwatched overnight. Law enforcement recovers relatively little stolen heavy equipment. The unit often ends up at a rural stash yard, gets stripped, or gets loaded into a shipping container for export. Those are exactly the places where cellular telemetry that depends on urban coverage goes dark.
So the industry leans heavily on equipment marking, insurance claims, and police reports filed after the theft. What's mostly missing is a device that reports real-time location while a theft is in progress and can physically disable the equipment before it leaves the region, without depending on infrastructure that may not exist at the job site or at the final stash location.
This project builds that device. It checks GPS location against a configurable job-site geofence and watches for unexpected motion after hours. It also accepts a remote immobilizer command from the Blues Notehub cloud service, which stages a relay to cut the ignition circuit the next time the thief turns the key. The immobilizer is a polled, staged proof of concept rather than a continuously held cut, and §11 covers the limits that come with that. GPS, cellular, and satellite fallback all live in a single Blues Notecard for Skylo. It sits in a weatherproof enclosure and runs indefinitely on a LiPo battery that a small solar panel keeps topped off.
Why Notecard. Construction sites have no WiFi. Even when a site happens to have a hotspot, a security device that needs site IT to get online defeats the purpose. Cellular works in most scenarios, but a stolen skid steer doesn't stay parked somewhere with good urban coverage. It ends up in a rural equipment yard, a metal barn, or a shipping container at a port, where cellular alone fails and satellite decides whether you recover the asset or write it off. Notecard for Skylo (NOTE-NBGLWX) handles all three radio paths in a single M.2 module, with no firmware branching: LTE-M/NB-IoT cellular, Skylo satellite, and WiFi where it's available. For this use case, global roaming and satellite failover are the core of the design rather than add-ons.
New to Blues?
Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.
Learn what we do in this 3-minute video that walks you through how we simplify wireless connectivity.
Deployment scenario. A NEMA 4X weatherproof enclosure hides inside the equipment housing or mounts to a structural member where nobody will see it. It carries its own power, a LiPo battery that a small rooftop solar panel keeps topped off, so the tracker keeps running even after a thief disconnects the equipment's main battery. Two wires tap the ignition circuit. One reads the ignition-on state through a voltage divider, and the other runs through the relay that can cut the starter circuit on command. The antenna leads run to externally mounted elements with a clear view of the sky.
2. System Architecture
Device-side responsibilities. The STM32 host in the Notecarrier
CX spends almost all of
its time asleep, and the whole power budget depends on that. When the
configurable wake timer fires, the firmware reads the ignition state from a
voltage divider on the 12 V ignition line, polls the Notecard's built-in
accelerometer with
card.motion, and
pulls the latest GPS fix with
card.location.
Then it runs three checks in order: whether the position is outside the
job-site geofence (evaluated with a Haversine distance), whether it's the
after-hours window, and whether a fresh immobilize command is waiting in the
inbound queue. Heartbeat and alert Notes go to the Notecard over I²C through the
note-arduino request helpers. Between wakes the host sleeps in STM32 STOP2 (a
few µA) until the Notecard's
ATTN pin wakes
it. Because the host resumes in place with its RAM intact, the runtime state
(geofence center, immobilizer stage, and cadence parameters) just stays in the
AppState struct. Only the geofence also gets saved to the Notecard
(fence.db), so it survives a full power loss.
Notecard responsibilities. Notecard for Skylo does most of the connectivity
work behind the scenes. It buffers queued Notes
in on-device flash and opens a cellular or satellite session on the
hub.set outbound
cadence. Any sync:true alert Note goes up right away, and that's the
difference between an alert reaching the operator in seconds and the equipment
crossing a county line. The same module also has the GPS receiver, the
accelerometer, the real-time clock, and the battery-voltage ADC, so you don't
need any external sensors. Environment
variables
come down from Notehub on each inbound sync. A fleet manager can retune the
geofence radius or the after-hours window from a browser, and the firmware
reissues hub.set and card.location.mode on its own so all three Notecard
cadences stay matched to the new wake interval.
Notehub responsibilities. Notehub ingests and stores
every event from every device, whether it came in over cellular or Skylo, and
applies the project's
routes. Alert Notes
(alert.qo) and tracker heartbeats (tracker.qo) land in separate Notefiles.
One device can then serve both the security platform that needs alerts the
moment they happen and the fleet-management store that needs position history,
with no filter logic in between. To stage an immobilize or release command,
an operator posts a Note to the device's immobilize.qi inbound queue from the
Notehub UI or through the Notehub REST API.
Smart Fleets push
site-level geofence and after-hours defaults to every machine on a job site at
once.
Routing to the cloud (high level). Notehub can send data to HTTP, MQTT, AWS IoT, Azure, GCP, Snowflake, and other destinations. Route setup depends on your project, and this one doesn't ship with a specific downstream endpoint. The Notehub routing docs cover the options.
3. Technical Summary
What you'll have when done:
- A powered device, claimed in Notehub and showing its real GPS location
- Real-time geofence alerts when the device moves outside a job-site boundary
- A way to stage an immobilizer command from the Notehub UI and confirm it fires on the next ignition key-on
- Confidence in how the firmware and sensors behave before you wire anything into live equipment
Fastest path to first event (bench test, no ignition circuit yet):
-
Assemble the unit. Insert Notecard for Skylo into the Notecarrier CX's M.2 slot and connect the LiPo battery to the Notecarrier CX's JST battery header. Route the antenna cables with some slack (MAIN u.FL → Skylo-certified antenna, GPS u.FL → passive GNSS patch), because you'll finalize their placement once the enclosure location is set. Mount everything in a NEMA 4X weatherproof box and power the Notecarrier CX over USB.
-
Get your ProductUID. Sign up at notehub.io, create a new project, and copy the ProductUID from the project settings.
-
Flash the firmware.
- Install the Arduino IDE or
arduino-cli. - In the Arduino Boards Manager, install the STM32 core. The build targets the Cygnet board definition with USB support set to None (see §7).
- In the Arduino Library Manager, install "Blues Wireless Notecard" and "STM32duino Low Power" (plus its dependency "STM32duino RTC").
- Open
firmware/construction_equipment_anti_theft/construction_equipment_anti_theft.ino. - Paste your ProductUID into the
PRODUCT_UIDstring at the top of the sketch. - Fit the ATTN-to-D5 jumper on the Notecarrier CX header (§5), then flash the Notecarrier CX.
Via arduino-cli: The board options below match
firmware/construction_equipment_anti_theft/sketch.yaml, whicharduino-clipicks up on its own when you run it from the sketch directory.usb=noneturns off the USB CDC stack, which has to be off for the host to sleep:arduino-cli lib install "Blues Wireless Notecard" "STM32duino Low Power" "STM32duino RTC" arduino-cli compile --fqbn "STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none" \ firmware/construction_equipment_anti_theft arduino-cli upload --port /dev/ttyACM0 --fqbn "STMicroelectronics:stm32:Blues:pnum=CYGNET,usb=none" \ firmware/construction_equipment_anti_theft - Install the Arduino IDE or
-
Claim the device. Power up the unit and leave it powered. On first boot the Notecard connects over cellular and auto-provisions to your project. In Notehub, go to your project → Devices and confirm the Notecarrier CX shows up in the device list within 30 seconds.
-
Watch the heartbeat. Click the device in Notehub and open the Events tab. The firmware queues a
tracker.qoheartbeat Note on its first wake, with the currentlat,lon, and battery voltage. Heartbeats go out in a batch on the outbound schedule (every 4 hours by default,outbound_min), so the first one can take up to 4 hours to show up. If nothing shows up, check the USB power and the antenna placement, since Skylo lock needs a clear view of the sky. -
Test the geofence. In Notehub's Fleet view, set these environment variables:
fence_enabled=1fence_lat= your current latitude (shown in the device event detail)fence_lon= your current longitudefence_radius_m=20(20-meter test radius)
Give the inbound sync 4–5 minutes after hours (up to 60 minutes while parked during the day) to deliver these variables to the device. Then physically move the device ~25 meters away and watch the Events log for a
geofence_breachalert, which should appear within 1–2 wake cycles. During business hours (6 AM–6 PM UTC by default) the device only wakes every 60 minutes, so test in the after-hours window (6 PM–6 AM) to get the faster 2-minute wake cycles. -
Test immobilizer staging. In the Notehub device view, use the command bar to post a Note to
immobilize.qi:{"cmd":"immobilize"}Watch the Events log for
immobilize_armed, which confirms the device received and staged the command. Next, simulate a key-on edge. Connect the host's A2 GPIO to GND (ignition OFF state), leave it there for one full wake cycle, then briefly short A2 to 3.3V (ignition ON). On the next wake after the OFF→ON transition, the relay driver pin A1 should pulse HIGH andignition_on_immobilizedshould show up in the Events log. That confirms the immobilizer path works before you do any live wiring.
Here's a sample Note this device emits:
{
"file": "tracker.qo",
"body": {
"lat": 37.3387,
"lon": -121.8853,
"loc_valid": 1,
"ignition": 0,
"moving": 0,
"immobilized": 0,
"bat_v": 3.92,
"fence_ok": 1,
"fix_age_s": 47.0
}
}4. Hardware Requirements
12 V systems only. Everything in this reference design, from the voltage-divider ratios to the relay coil rating and the wiring guidance, is engineered for 12 V electrical systems. Most compact construction equipment (skid steers, portable generators, light towers) ships with a 12 V system, but larger machines (heavy excavators, some European platforms) use 24 V. On a 24 V ignition rail, the 33 kΩ / 10 kΩ ignition-sense divider shown here would put about 5.6 V on the host's A2 GPIO, which is over the 3.3 V limit, and the 12 V relay coil would overheat. Verify your equipment's system voltage before proceeding. Adapting the design to 24 V takes different divider values (e.g. 68 kΩ high-side + 10 kΩ low-side → 24 V × 10/78 ≈ 3.1 V, within the GPIO limit) and a 24 V–rated relay coil.
| Part | Qty | Rationale |
|---|---|---|
| Notecarrier CX (datasheet) | 1 | Compact carrier with an embedded STM32L433 host MCU, so you don't need a separate Swan or Feather. The host sleeps in STOP2 between wakes, and the Notecard's ATTN pin (jumpered to D5) wakes it through card.attn. |
| Notecard for Skylo (NOTE-NBGLWX) (datasheet) | 1 | LTE-M/NB-IoT/GPRS cellular, Skylo satellite, and WiFi, all in a single M.2 module, with automatic cellular-to-satellite failover and no firmware changes. It ships with its Skylo-certified MAIN cellular/satellite antenna (see the antenna row below) and includes 500 MB of cellular data and 10 KB of bundled Skylo data. |
| Blues Mojo (datasheet) | 1 | Bench-only coulomb counter for checking energy use during commissioning. It sits inline on the +VBAT rail and measures current draw across the sleep, active, and transmit phases. It isn't deployed in the field (see §11). |
| 3.7 V LiPo battery, 2000 mAh, JST-PH 2.0 mm (e.g. Adafruit 2011, or equivalent 2000–4000 mAh cell) | 1 | Primary energy storage. Size the capacity for the solar you expect to get and the dark-sky reserve you want, using the phase-by-phase current-draw breakdown in §9. |
| 6 V, 10 W solar panel, ~110×70 mm (e.g. Voltaic Systems P110) | 1 | Trickle-charges the LiPo during daylight hours. Construction equipment usually lives outdoors, and the 10 W panel leaves plenty of headroom even at sites with limited direct sun or long overcast stretches. |
| Solar LiPo charger module, 3.7 V LiPo output (e.g. SparkFun Sunny Buddy PRT-12885, or equivalent MPPT/CV-mode charger) | 1 | Sits between the solar panel and the LiPo and protects the battery from overcharge. Its output connects to the LiPo JST input on the Notecarrier CX, so check that the charger's output connector matches the Notecarrier CX's battery header. |
| BSS138 logic-level N-channel MOSFET, SOT-23 (e.g. Nexperia BSS138 or equivalent with R_DS(on) ≤ 3.5 Ω at V_GS = 2.5 V and I_D ≥ 300 mA) | 1 | Low-side relay coil driver. For a 3.3 V logic-level gate drive on a security-critical path, choose the part by R_DS(on) at V_GS = 2.5 V, not by threshold voltage. The Nexperia BSS138 datasheet specifies R_DS(on) max 3.5 Ω at V_GS = 2.5 V, I_D = 100 mA. At the relay coil current (~150–175 mA for a typical ISO 280 coil), worst-case V_DS ≤ 175 mA × 3.5 Ω ≈ 0.61 V and P_D ≈ 0.11 W, which is within the SOT-23 thermal rating. The relay coil sees ≥ 11.4 V of the 12 V supply, well above its pull-in threshold. Drain goes to relay coil −, and source goes to GND. The 10 kΩ gate-to-source pulldown keeps the MOSFET off when the GPIO is high-impedance during reset. |
| Automotive SPDT relay, 12 V coil, 30 A contacts (ISO 280 mini relay, e.g. Bosch 0 332 019 150 or equivalent Tyco/TE V23134-J52-X255) | 1 | Wired in series with the equipment's starter solenoid enable or ignition/run-start control circuit. That's the low-current signal wire that authorizes cranking, not the high-current cable that feeds the starter motor itself. Energizing the coil opens the normally-closed contact, so the engine can't crank. Any automotive supplier or electronics distributor carries them. |
| Inline automotive fuse holder with 1 A or 2 A mini (ATC/ATO blade) fuse | 1 | Goes on the wire that feeds the relay coil + terminal from the 12 V battery or ignition tap, as close to the power source as practicable. A typical ISO 280 coil draws ~150–175 mA, so a 1 A fuse gives ~6× margin over the continuous coil current while still protecting the added harness from a short-circuit fault. Don't rely on the equipment's upstream fusing. Any newly added conductor tied to battery or ignition power should carry its own fuse near the source. |
| 1N4007 diode | 1 | Flyback protection across the relay coil. It protects the BSS138 MOSFET drain from the inductive spike when the coil de-energizes, so don't leave it out. |
| 10 kΩ resistor, ¼ W | 1 | Gate-to-source pulldown on the BSS138. It keeps the MOSFET off if the host GPIO is high-impedance during reset or power-on. |
| 33 kΩ + 10 kΩ resistors, ¼ W, 1% tolerance | 2 | Resistive voltage divider for ignition sense: 33 kΩ high-side (to 12 V rail) + 10 kΩ low-side (to GND) → 12 V × 10 / (33 + 10) ≈ 2.79 V at A2, safely within the host's 3.3 V GPIO absolute maximum. The 1% tolerance keeps the threshold consistent. POC-only front end. This bare divider provides no protection against automotive load-dump or starter transients, so see §5 and §11 for production protection guidance. |
| NEMA 4X weatherproof enclosure, ~150×100×75 mm (e.g. Polycase WC-31 or Hammond 1554N2GYCL) | 1 | Rated for outdoor hose-down environments. Mount it inside the equipment housing where nobody can see it from outside. |
| Skylo-certified cellular/satellite antenna (use the antenna included in the NOTE-NBGLWX kit) | 1 | Connects to the MAIN u.FL on Notecard for Skylo. The NOTE-NBGLWX is certified on Skylo's network only with the included antenna. Substituting a different antenna voids the Skylo network certification and may get the device blocked from the network. See the antenna guide for placement requirements. |
| Passive GNSS antenna, GPS L1 1575 MHz, u.FL connector, outdoor-rated (e.g. Taoglas FXP611 (Cloud) flexible PCB GPS/GLONASS antenna with u.FL lead, or equivalent passive GPS patch) | 1 | Connects to the GPS u.FL on Notecard for Skylo. It has to be a passive antenna, because the GPS port doesn't supply bias voltage. Mount it outdoors next to the MAIN antenna with a clear view of the sky. |
All Blues hardware ships with an active SIM that includes 500 MB of cellular data and 10 years of service, with no activation fees and no monthly commitment.
Important satellite note. Skylo's constellation sits in geostationary orbit (GEO), so from the northern hemisphere the antenna needs an unobstructed view of the southern sky to lock onto the satellites. A metal equipment cab that blocks the southern horizon will prevent satellite connectivity. Mount the antenna on the roof of the enclosure and orient it accordingly. The Satellite Best Practices guide has more detail on antenna placement.
5. Wiring and Assembly
Safety. The ignition and starter circuits on heavy equipment carry large inrush currents and may involve safety-critical functions. All wiring must be done by qualified personnel following the equipment manufacturer's service manual and applicable electrical codes. The relay wiring described here is only a reference example, so confirm the correct interrupt point for your specific machine (skid steer, generator, light tower, etc.) before installation. Never interrupt circuits that control active running loads (fuel injection, hydraulics) while the engine is running.
The whole assembly has to disappear inside the equipment chassis, behind a
panel, under a seat, or in an equipment bay where a thief won't find it. That's
why the build is small and the antennas are the only parts that need to see the
sky. Notecard for Skylo seats into the Notecarrier CX's M.2 slot. Route the
MAIN u.FL lead to the Skylo-certified antenna that came with the kit and
the GPS u.FL lead to the separate passive GNSS antenna. Leave both leads
slack until you've settled on the final enclosure location, because antenna
placement and a clear view of the sky make a big difference to Skylo lock.
During bench validation, the Mojo coulomb counter splices inline on the +VBAT wire to measure current draw phase by phase (the Mojo datasheet shows the wiring). It comes out before the unit ships to the field, unless you add the Qwiic telemetry extension (see §11), in which case it stays in circuit.
12 V systems only. Confirm that the equipment's system voltage is 12 V before you connect the ignition-sense divider or relay coil. On a 24 V machine, the divider would exceed the host GPIO limit and the relay coil would overheat. The warning in §4 explains how to adapt the design to 24 V.
Pin-by-pin wiring:
Power chain (follow this order):
- Solar panel (+/−) → charger module solar/panel input terminals.
- LiPo battery JST → charger module battery output connector (the dedicated battery terminal on the charger, not its load output). The charger manages charging from the solar panel and keeps the cell from overcharging.
- Charger load/output (+) → (during bench validation: Mojo
BAT+, then MojoLOAD+ to next step; otherwise directly to next step) → Notecarrier CX battery JST connector (theBATT/+VBATheader). This is the supply rail for both the Notecarrier CX and its embedded STM32 host. Charger load/output (−) and Mojo GND go to common GND.
Before connecting, check that your charger module's output connector pitch and polarity match the Notecarrier CX's JST-PH 2.0 mm battery header. Some charger modules ship with reversed-polarity JST connectors.
Signal wiring:
-
A1 (relay driver GPIO) → BSS138 gate. BSS138 source to GND. A 10 kΩ gate-to-source pulldown resistor (from BSS138 gate to GND) keeps the MOSFET off when the GPIO is high-impedance during reset. BSS138 drain to relay coil pin 85 (coil
−). Put an inline 1–2 A automotive fuse on the wire between the 12 V supply tap and relay coil+(pin 86), as close to the 12 V source as practicable. The 1N4007 goes across the coil, cathode to pin 86 (+12 V) and anode to pin 85 (MOSFET drain/coil−), where it clamps the inductive spike when the coil de-energizes. At V_GS = 3.3 V the BSS138 operates inside its R_DS(on)-characterized region (max 3.5 Ω at V_GS = 2.5 V per the Nexperia datasheet), so every wake that asserts the driver pin reliably energizes the relay coil. -
A2 (ignition sense) → junction node of voltage divider. 33 kΩ high-side resistor from the equipment's 12 V ignition rail to the A2 junction node. 10 kΩ low-side resistor from the A2 junction node to GND. This divides 12 V down to about 2.79 V at the host GPIO (12 V × 10 / (33 + 10) ≈ 2.79 V), safely within the 3.3 V absolute maximum.
Ignition-sense front end (POC only). The bare resistive divider does nothing to stop the automotive transients that show up on ignition and battery lines in real equipment. Load-dump events on generator-set systems can produce transients of 40–200 V above nominal, and starter motor switching puts fast inductive spikes on the 12 V rail. A production design should add at least a TVS diode (e.g. 15 V unidirectional TVS from the 12 V rail to GND, placed before the divider high-side resistor) and an RC low-pass filter (e.g. 1 kΩ series + 100 nF to GND at the A2 node) to clamp transients and filter noise before they reach the host GPIO. For production deployments, the right long-term answer is an optically isolated or fully automotive-rated sensing front end.
-
Relay switched contacts → NC (normally-closed) contact (pins 87a and 30) wired in series with the starter solenoid enable or ignition/run-start control circuit, which is the low-current signal wire that authorizes cranking. With the coil de-energized, current flows normally, and energizing the coil opens the circuit. Do not wire the relay in the high-current starter motor power cable (the heavy-gauge cable running between the battery positive terminal and the starter motor terminal). That cable carries hundreds of amperes during cranking and isn't the right intercept point for this relay. Check the equipment service manual to find the correct low-current control wire.
-
SDA / SCL → Notecard for Skylo talks over the Notecarrier CX's onboard I²C bus internally, so there's no external wiring to do.
-
ATTN → D5 (jumper, host sleep) → Fit a short jumper between the ATTN and D5 pins on the Notecarrier CX. Both are on the same 16-pin header (
ATTNat position 3,D5at position 14). The Notecard pullsATTNlow while the host sleeps and raises it when the adaptive wake interval expires, and the rising edge onD5wakes the host from STOP2. Leave the Notecarrier CX EN pin unconnected. On the CX it enables the shared 3.3 VVIOrail (host, Qwiic,3V3_OUT, and Notecard I/O), so driving it fromATTNwould 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. -
GND → equipment chassis ground for the ignition sense divider and relay coil return. Keep this common with the Notecarrier CX GND.
Enclosure and antenna mounting. Hide the enclosure somewhere inside the equipment chassis where it isn't obvious, like behind a panel, under a seat, or inside an equipment bay. Route the antenna cable through a weatherproof grommet. For the best Skylo link budget, point the antenna patch at the sky with as much southern-hemisphere clearance as you can get from the installation latitude.
6. Notehub Setup
-
Create a project. Sign up at notehub.io and create a project. Copy the ProductUID and paste it into
firmware/construction_equipment_anti_theft/construction_equipment_anti_theft.inoasPRODUCT_UID. -
Claim the Notecard. Power up the unit. The Notecard auto-provisions to your project on its first cellular connection. Check that the device shows up in the Notehub device list.
-
Create a Fleet per job site. Fleets group devices for shared configuration. One fleet per active job site is the natural boundary, since all the equipment on a site shares the same geofence center and after-hours window through fleet-level environment variables. Per-device overrides cover any machine with unusual requirements. Smart Fleets can automate fleet membership based on location data.
-
Set environment variables. Every variable is optional, and the table shows the firmware defaults. The device pulls variables on each wake with a delta check, so you can retune thresholds without re-flashing the firmware. A cadence value outside its range is clamped to the nearest limit, and a zero or negative value is ignored. When a cadence variable changes, the firmware reissues both
hub.set(outbound and inbound session windows) andcard.location.mode(GNSS acquisition cadence) so all three Notecard cadences stay in sync with the updated host wake interval.Variable Default Range / Notes Purpose fence_enabled(not set) 1ortrueto enableRecommended commissioning flag. Set it to 1to applyfence_latandfence_lonto the geofence whatever their numeric values. Use it instead of the legacy non-zero-check fallback, because it supports fences centered exactly on latitude 0° or longitude 0°. Iffence_enabledis absent, the firmware falls back to checking thatfence_latandfence_lonare both non-zero.fence_lat(first GPS fix) Decimal degrees Latitude of the job-site geofence center. Set fence_enabled=1along with it (see above). If neitherfence_enablednor a non-zerofence_latis present, the firmware anchors the geofence at the first valid GPS fix after a cold boot.fence_lon(first GPS fix) Decimal degrees Longitude of the job-site geofence center. Set fence_enabled=1to apply it regardless of magnitude, because the legacy fallback silently ignores afence_lonof exactly0.0(the prime meridian).fence_radius_m200> 0 Geofence radius in meters. 200 m works well for a single job site. Go bigger for large sites or smaller for tighter perimeter security. after_hours_start181–23 UTC hour when after-hours monitoring starts. 0counts as "unset" (the firmware keeps the existing value). Adjust for the deployment region's UTC offset.after_hours_end60–23 UTC hour when after-hours monitoring ends. 0(midnight) is valid.heartbeat_stopped_min601–1440 (clamped) Minutes between host wake cycles and queued heartbeat Notes while the equipment is stationary. It also sets the daytime inbound cadence, since the Notecard polls Notehub once per this interval while parked (see inbound_min). Worst-case geofence-detection latency during business hours equals this value, so lower it for faster detection at the cost of more wake cycles.heartbeat_moving_min51–60 (clamped) Minutes between queued heartbeat Notes when the Notecard accelerometer reports motion. It also sets the GNSS acquisition cadence ( card.location.mode seconds), so the Notecard gets a fresh position fix at this interval while moving. Heartbeat records pile up in local flash and go out together on the next outbound sync session instead of one at a time at this cadence. Real-time pursuit location updates come fromalert.qoNotes (sync:true), not from the heartbeat queue.heartbeat_afterhours_min21–30 (clamped) Minutes between host wake cycles during the after-hours window. This sets alert-detection latency, because the host wakes this often to scan for motion and check the command queue. Heartbeats still queue at the stationary/moving rate no matter what this is set to. It does not control the Notecard inbound cadence (see inbound_min).inbound_min41–60 (clamped) The short inbound interval used after-hours and when moving. It's how often the Notecard polls Notehub for queued commands and environment-variable updates during active-monitoring windows. When the equipment is stopped during the day, the firmware switches to heartbeat_stopped_minas the inbound interval, so inbound sessions line up with host wakes and the Notecard doesn't open idle cellular connections while the equipment sits parked on the job site. The Notecard drives the inbound poll on its own, and it does not wake the host MCU.outbound_min2401–1440 (clamped) Minutes between Notecard outbound batch sessions, which is how often queued heartbeat Notes go to Notehub. It's deliberately longer than heartbeat_stopped_minso several heartbeats collect per outbound session. That cuts total cellular radio time without hurting detection latency. Alert Notes (sync:true) skip this window and go out immediately.alert_cooldown_min51–60 (clamped) Minimum minutes between repeated alerts of the same type. Each alert type ( geofence_breach,motion_after_hours,cmd_retrieve_failed) has its own cooldown timer, so one alert type can't suppress another. On the Skylo satellite link, consider raising this to 15–30 minutes to protect the 10 KB bundled satellite data budget during a long geofence-breach event. -
Issue an immobilize command. From the Notehub UI (or through the Notehub REST API), add a Note with body
{"cmd":"immobilize"}to the device'simmobilize.qiinbound queue. Command delivery has two sequential latency stages: (1) the Notecard pulls the inbound Note on its nextinboundsync session, and (2) the host processes the command the next time it wakes and runsrunCycle(). With the default cadence, the worst case from posting the command to seeing theimmobilize_armedacknowledgment in Notehub is ~6 minutes during the after-hours window (≤ 4 minutes for the Notecard inbound sync, then ≤ 2 minutes until the next host wake). It's ~9 minutes while moving (4 + 5 minutes) and just under 2 hours while stationary during business hours (up to 60 minutes for the Notecard daytime inbound sync + up to 60 minutes until the next host wake). The daytime inbound interval stretches to matchheartbeat_stopped_min(default 60 minutes) to avoid idle cellular sessions. If you need near-real-time command delivery around the clock, reduceheartbeat_stopped_minor add a motion-triggered wake (see §11 Production next steps). To cancel before the relay fires, send{"cmd":"release"}.Queue semantics: last command wins. On each wake the firmware drains the entire
immobilize.qiqueue, callingnote.getin a loop until it's empty and applying each command in order. The last command in the queue is the one that takes effect. If you postimmobilizeand follow it right away withrelease, thereleasewins on the same wake cycle. Keep at most one outstanding command in the queue at a time, and don't post a new one until Notehub shows either theimmobilize_armedalert (confirming a staged immobilize) or therelease_confirmedalert (confirming the immobilize was cleared). Each of those alerts goes out once, on the exact wake cycle that processes its command. This is a staged, edge-triggered POC immobilizer. The relay isn't asserted until the device wakes and sees an OFF→ON ignition edge while the command is staged (one prior wake saw OFF, and the firing wake sees ON). It deliberately won't fire while the engine is already running.Important: For a remote immobilizer, this is a security-sensitive control path. Anyone who can post to
immobilize.qican cut ignition on the equipment, so restrictimmobilize.qiwrite access to tightly scoped, audited Notehub API credentials and don't use a project-wide admin token. -
Configure routes. Add one route for
alert.qo(real-time delivery to a security or on-call platform) and a second fortracker.qo(position history to a fleet-management or asset-tracking store). Because the two Notefiles are already separate at the source, you can send them to different systems at different urgency, and the route itself doesn't need any filter logic.
7. Firmware Design
Every wake runs the same short cycle: sense, evaluate, queue any Notes, and
sleep. The application logic and the setup() / loop() entry points live in
the main sketch,
firmware/construction_equipment_anti_theft/construction_equipment_anti_theft.ino.
All Notecard interaction is wrapped up in
construction_equipment_anti_theft_helpers.h
and
construction_equipment_anti_theft_helpers.cpp,
and the host-sleep helper is
cx_sleep.h.
Dependencies:
- Arduino Core for STM32
(
stm32duino/Arduino_Core_STM32). The Notecarrier CX's host uses the Cygnet board definition, which you'll find in the Arduino Boards Manager (see the build settings below). Blues Wireless Notecard(note-arduinolibrary). Install it from the Arduino Library Manager or witharduino-cli lib install "Blues Wireless Notecard".STM32duino Low Power(and its dependencySTM32duino RTC), both from the Library Manager. It provides the STOP2 entry and ATTN-pin wake thatcx_sleep.huses.
Build settings: The Notecarrier CX's host is the same STM32L433 as the Blues
Cygnet, so select Blues boards → Cygnet (pnum=CYGNET) and set USB
support (if available) to None (usb=none). If the USB CDC stack is
enabled and no USB host is attached (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_SERIAL is set to 1 in
the sketch) goes to the LPUART on the Notecarrier CX debug jack instead, which
an ST-LINK V3 exposes as a virtual COM port (see Serial Logging With
STLINK).
Modules
| Responsibility | Where |
|---|---|
Notecard configuration: hub.set, card.transport (wifi-cell-ntn, which turns on satellite fallback), GPS mode, motion mode, templates (idempotent, runs every wake) | ensureConfigured() |
| Compact Note templates for satellite | defineTemplates() |
| Env-variable delta pull | fetchEnvOverrides() |
| Match hub inbound/outbound and GNSS cadences to the wake-state context | applyHubCadence() |
| Drain inbound command queue (last command wins) | checkAndHandleCommand() |
| Ignition state, motion state, GPS fix, battery voltage | getIgnitionState(), getIsMoving(), getLocation(), getBatteryVoltage() |
| Geofence evaluation (Haversine) | haversineDistanceM() |
| After-hours window evaluation (UTC) | isAfterHours() |
| Heartbeat and alert Note emission | sendHeartbeat(), sendAlert() |
| Relay assertion / release | assertRelay(), releaseRelay() |
Host sleep (STOP2 until the Notecard raises ATTN) | cx_sleep.h: cxSleepBegin(), cxSleepUntilAttn() |
| Application state (held in RAM across sleep cycles) | AppState |
| Geofence persistence across power loss | saveFenceToFlash(), loadFenceFromFlash() (fence.db) |
Sensor reading strategy
Ignition sense. With the ignition key ON, a voltage divider on the
equipment's 12 V ignition rail puts ~2.79 V on the host's A2 pin, inside the
3.3 V safe limit. The high-side resistor (33 kΩ, to the 12 V rail) and low-side
resistor (10 kΩ, to GND) set the divider ratio: 12 V × 10 / (33 + 10) ≈ 2.79 V.
On each wake the host reads A2 with analogRead() at 12-bit resolution and
averages 4 samples to filter out automotive line noise and battery-sag
transients during engine crank. Hysteresis thresholds govern state changes. The
averaged reading has to rise above ~2.50 V (3103 counts on a 12-bit / 3.3 V ADC)
to declare ignition ON, and fall below ~2.00 V (2482 counts) to declare ignition
OFF. Inside the ~0.5 V hysteresis band the firmware holds the previous state.
That prevents chatter at the logic boundary and spans the typical battery-sag
floor during cold cranks (~10.5 V → ~2.44 V at the divider).
Motion. The built-in accelerometer on Notecard for Skylo runs continuously
in low-power mode. card.motion.mode configures it for 25Hz, ±4G sensitivity
with a 60-second bucket. The firmware calls card.motion on each wake, and when
the bucket holds at least 3 events, the Notecard reports "mode":"moving". That
catches equipment being driven away (sustained multi-axis motion) as well as
equipment being loaded onto a trailer (tilt, vibration).
GPS. card.location.mode is set to periodic with a motion threshold of 4
events, so the Notecard only spins up the GNSS module once the accelerometer
confirms the unit is actually moving. That spares stationary wakes the
multi-second GNSS warm-up penalty. The firmware calls card.location to read
the latest fix, and because the Notecard caches the last valid location, the
call returns quickly even when GNSS isn't active.
Cached-fix latency. Geofence and alert decisions use the most recently
cached GNSS fix, which may not reflect where the device is right now. The GNSS
acquisition cadence bounds the fix age (card.location.mode seconds, default 5
minutes, tunable with heartbeat_moving_min). During the after-hours window
the host wakes every 2 minutes but GNSS only updates every 5 minutes, so two or
three wakes in a row may check the geofence against the same cached fix. The
firmware captures the time field from card.location (the epoch of the
cached fix) and computes fix_age_s = now − fix_time, but only when
card.time returns a valid non-zero epoch. When card.time isn't available,
for example on first boot before the Notecard has completed a Notehub session,
fix_age_s goes out as -1 (a sentinel meaning "age unknown") instead of
0. That lets downstream consumers tell a truly fresh fix apart from one whose
age just can't be determined. A value of 0 or greater is a real
elapsed-seconds count. Every outbound Note includes fix_age_s, both the
tracker.qo heartbeats and the alert.qo events.
Battery voltage. card.voltage returns the LiPo rail voltage directly from
the Notecard's internal ADC. Every Note includes it so you can trend battery
state of charge.
Event payload design
Both Notefiles use compact Note
templates
with a port number. Notecard for Skylo requires compact templates. They
store Notes as fixed-length binary records instead of free-form JSON, which
shrinks each Note's wire size by 3–5×.
The compact record sizes come straight from the template field types declared
in defineTemplates():
tracker.qo: 4 floats (lat,lon,bat_v,fix_age_s) × 4 bytes + 5 int16 fields (loc_valid,ignition,moving,immobilized,fence_ok) × 2 bytes = 26 bytes per heartbeat record.alert.qo: 4 floats (lat,lon,bat_v,fix_age_s) × 4 bytes + 3 int16 fields (loc_valid,ignition,immobilized) × 2 bytes + 1 string field (alert, 24-char sample) at 25 bytes (1-byte length prefix + 24 data bytes) = 47 bytes per alert record.
With the 10 KB of bundled Skylo data (10,240 bytes), payload-only capacity is roughly 390 heartbeat-sized Notes (10,240 ÷ 26 bytes) or 215 alert-sized Notes (10,240 ÷ 47 bytes) in total. Protocol and session framing overhead push the real delivered counts below those payload-only maxima, so validate against Notehub usage metrics and field measurements instead of relying on payload-only arithmetic. §11 has practical budget guidance.
Example heartbeat (tracker.qo):
{
"file": "tracker.qo",
"body": {
"lat": 37.3387,
"lon": -121.8853,
"loc_valid": 1,
"ignition": 0,
"moving": 0,
"immobilized": 0,
"bat_v": 3.92,
"fence_ok": 1,
"fix_age_s": 47.0
}
}fix_age_s is the age of the cached GNSS fix, in seconds, at the moment the
Note was queued. -1.0 means card.time wasn't available when the Note was
queued. In that case the true fix age can't be determined, so don't treat it as
a freshness indicator. A value of 0.0 or greater is a real elapsed-seconds
count. Anything well below heartbeat_moving_min × 60 (default 300 seconds)
indicates a fresh fix. A value near or past that threshold means the geofence
and alert decisions on this wake used a cached position from the previous GNSS
acquisition cycle.
Example alert (alert.qo), sent with sync:true:
{
"file": "alert.qo",
"body": {
"alert": "geofence_breach",
"lat": 37.3521,
"lon": -121.9204,
"loc_valid": 1,
"ignition": 1,
"immobilized": 0,
"bat_v": 3.87,
"fix_age_s": 112.0
},
"sync": true
}Alert types:
alert value | Meaning |
|---|---|
geofence_breach | Equipment GPS fix is outside the configured fence radius |
motion_after_hours | Accelerometer detects motion during the after-hours window while ignition is confirmed OFF |
ignition_on_immobilized | Immobilize was staged, and the relay asserted on an OFF→ON ignition edge (not on a level read of ignition-ON) |
immobilize_armed | Acknowledgment Note confirming the immobilize command was received and staged |
release_confirmed | Acknowledgment Note confirming a release command cleared the staged or active immobilize, with the relay de-energized |
cmd_retrieve_failed | Diagnostic: note.get on immobilize.qi ran out of retries or returned an error other than the normal empty-queue {note-noexist}, which means the command path is unhealthy. It's rate-limited by alert_cooldown_min (default 5 minutes) so a persistent I²C or Notecard fault doesn't generate repeated sync:true traffic. If this alert keeps firing, look at the I²C wiring and Notecard firmware. |
Low-power strategy
Both the STM32 host and the Notecard sleep between sensing cycles. At the end of
each wake, runCycle() calls cxSleepUntilAttn() (in cx_sleep.h). That
issues card.attn with
mode:sleep and the adaptive duration, waits for the Notecard to pull ATTN
low, and puts the host into STOP2, where it draws a few microamps and keeps its
RAM and its GPIO state intact. Between cellular sync windows the Notecard drops
into its own low-power idle
state (~8–18 µA @
5V). After sleep_sec seconds the ATTN pin goes HIGH, and the rising edge on
D5 wakes the host. Execution picks up on the next line with g_state exactly
as it was, and setup() only runs once per power-up. If the sleep request fails
or ATTN never goes low (a missing jumper), the firmware waits out the same
adaptive interval awake and tries again on the next cycle, so the sensing
cadence holds either way.
The sleep duration depends on context: 2 minutes after hours (fast theft detection), 5 minutes while moving (active tracking), and 1 hour at rest during business hours (background monitoring, which also sets the worst-case geofence-detection latency while parked). The after-hours wake interval is deliberately shorter than the heartbeat interval. The device wakes every 2 minutes to check for alerts and inbound commands, but heartbeat Notes queue at the stationary or moving rate at any time of day. That keeps overnight Note volume in line with daytime operation and still catches a theft within the scan window.
Sampling/queuing cadence and transmission cadence are always distinct. The
host queues heartbeat Notes into the Notecard's local flash, and they go to
Notehub on the next outbound sync session, which fires at the hub.set outbound
interval (set by outbound_min, default 240 minutes). At the 5-minute moving
sample rate, the Notecard collects a heartbeat every 5 minutes while in motion,
but those records still go out as a batch on the next outbound session unless
an alert happens to force a sync:true flush first. Only alert.qo Notes (with
sync:true) skip the outbound window and trigger an immediate uplink sync.
The Notecard also manages its own inbound sync sessions. These are cellular
connections the Notecard opens on its own schedule to pull queued commands and
updated environment variables from Notehub. The inbound cadence follows context.
When the equipment is parked during the day, the interval matches
heartbeat_stopped_min (default 60 minutes, set through hub.set inbound).
After hours or while moving, it shortens to inbound_min (default 4 minutes).
The Notecard drives these sessions, and they happen whether or not the host is
awake. The host's 2-minute after-hours wake cycle and the Notecard's inbound
cellular cadence are independent and tuned with separate parameters
(heartbeat_afterhours_min and inbound_min, respectively). That's how daytime
inbound sessions can match the host wake cadence instead of running dozens of
times an hour against a parked asset. Account for both cadences when you size
the energy budget (see §9).
Env-var cadence retuning scope. When an operator changes any cadence
variable (heartbeat_moving_min, heartbeat_stopped_min,
heartbeat_afterhours_min, inbound_min, or outbound_min) in Notehub, the
firmware catches the change with the env.get delta check and calls
applyHubCadence(). That reissues hub.set (outbound and inbound windows) and
card.location.mode (GNSS acquisition seconds, set to heartbeat_moving_s).
The firmware also calls applyHubCadence() right away whenever the motion or
after-hours context changes, so the Notecard's inbound window switches between
the short active-monitoring interval (inbound_min) and the long parked
interval (heartbeat_stopped_min) without waiting for a future env-var change.
The outbound window uses outbound_s (set by outbound_min, default 240
minutes) and is decoupled from the host wake interval. Env-var changes don't
affect card.motion.mode (accelerometer sensitivity and bucket size). Those are
set at power-up only, and changing them takes a firmware update.
The Notecard gates the GPS module on motion, so it only turns on after sustained accelerometer activity. The GNSS radio stays silent through the many hours a day the equipment sits parked.
Retry and error handling
- The firmware checks the
card.attnsleep request incxSleepUntilAttn(), since the host stays awake long enough to see the response. - All
requestAndResponsecalls check for aNULLresponse before touching the returned JSON. Calls tofetchEnvOverrides,checkAndHandleCommand, andgetLocationalso check for a non-null"err"field, and so do calls togetIsMoving,getBatteryVoltage, andgetEpochTime. A sensor call that fails returns a safe default (zero for GPS coordinates,falsefor motion/ignition) instead of pushing bad data into alerts or Notes. note.getonimmobilize.qireturns anerrtagged{note-noexist}when no Note is queued. The firmware treats that as the normal end of the queue and continues the wake cycle. Any other error raisescmd_retrieve_failed.env.getwith thetimedelta parameter still makes the I²C request on every wake. When no environment variables have changed since the last pull, though, the Notecard returns anerrresponse instead of the full env-var payload. The firmware detects that and skips the overhead of re-parsing and re-applying unchanged values.- When
env.getdoes return updated values, the firmware immediately reissueshub.setthroughapplyHubCadence()so the Notecard's outbound and inbound session windows pick up any changed heartbeat intervals. If that reissue is skipped (e.g., the Notecard isn't ready), the cadence stays at the last value that was applied successfully. - Alert de-duplication keeps a separate cooldown timestamp for each type
(
last_geofence_alert_s,last_motion_alert_s, andlast_cmd_failed_alert_s), so a geofence-breach cooldown can't suppress a concurrent after-hours motion alert, or the other way around.
Key code snippet 1: compact Note template definition
Notecard for Skylo needs the format:"compact" and port arguments. Without
them, Notes headed for the satellite path may be dropped. defineTemplates()
registers tracker.qo (TRACKER_NOTEFILE) on port 50 (TRACKER_PORT) as shown
below, and alert.qo the same way on port 51 (ALERT_PORT). Each request is
retried up to three times. The numeric type hints (14.1 = 4-byte float, 12
= 2-byte signed int) define the binary record layout: 4 floats × 4 bytes + 5
int16 fields × 2 bytes = 26 bytes per heartbeat. fence_ok is 1 in-fence,
0 on a breach, and -1 when unknown.
for (int attempt = 0; attempt < kMaxAttempts && !ok; attempt++) {
J *req = nc.newRequest("note.template");
JAddStringToObject(req, "file", TRACKER_NOTEFILE);
JAddStringToObject(req, "format", "compact");
JAddNumberToObject(req, "port", TRACKER_PORT);
J *body = JAddObjectToObject(req, "body");
JAddNumberToObject(body, "lat", 14.1);
JAddNumberToObject(body, "lon", 14.1);
// ...
JAddNumberToObject(body, "loc_valid", 12);
JAddNumberToObject(body, "ignition", 12);
JAddNumberToObject(body, "moving", 12);
JAddNumberToObject(body, "immobilized", 12);
JAddNumberToObject(body, "bat_v", 14.1);
JAddNumberToObject(body, "fence_ok", 12);
// fix_age_s: seconds since the GNSS fix was last acquired (>= 0), or
// -1 when card.time was unavailable and the age cannot be determined.
// Downstream consumers must treat -1 as "age unknown".
JAddNumberToObject(body, "fix_age_s", 14.1);
ok = sendRequestChecked(nc, req);
// ...
}Key code snippet 2: immediate-sync alert
sync:true wakes the radio and flushes this Note immediately, skipping the
normal outbound window. Whether a geofence breach alert reaches the operator in
seconds or in hours can decide whether the equipment gets recovered.
sendAlert() builds the Note below for every alert type (alert_type is
"geofence_breach", "motion_after_hours", and so on). It checks the response
for an err field and retries up to three times, so a transient I²C error
doesn't drop a security alert.
const int kMaxRetries = 3;
for (int attempt = 0; attempt < kMaxRetries; attempt++) {
J *req = nc.newRequest("note.add");
JAddStringToObject(req, "file", ALERT_NOTEFILE);
JAddBoolToObject(req, "sync", true); // wake radio, transmit now
J *body = JAddObjectToObject(req, "body");
JAddStringToObject(body, "alert", alert_type);
JAddNumberToObject(body, "lat", lat);
JAddNumberToObject(body, "lon", lon);
// ...
J *rsp = nc.requestAndResponse(req);
// ...
}Key code snippet 3: relay assertion on key-on edge
The immobilizer doesn't cut power while the engine is running. It stages the
relay and waits for the next OFF→ON ignition transition, which is an edge and
not a level check. The firmware keeps the previous wake's ignition state in
g_state.last_ignition_on so it can detect the edge across sleep cycles. If the
equipment was already running when the operator issued the immobilize command,
the firmware deliberately holds off until the thief turns the key OFF and back
ON. That keeps it from cutting the starter circuit while the engine is actively
running and avoids the safety hazards called out in §5.
bool ignition_edge = (!g_state.last_ignition_on && ignition_on);
if (g_state.immobilize_pending && ignition_edge && !g_state.immobilized) {
assertRelay();
g_state.immobilized = true;
g_state.immobilize_pending = false;
LOGLN("[APP] Relay asserted on key-on edge — ignition blocked.");
if (!sendAlert(notecard, g_state,
"ignition_on_immobilized", cur_lat, cur_lon, got_fix,
true, true, fix_age_s)) {
LOGLN("[APP] ERROR: immobilize alert failed after all retries.");
}
}Key code snippet 4: adaptive sleep until the Notecard raises ATTN
g_state stays in RAM through the sleep, so the next wake has full context no
matter what triggered it. The duration adapts to the current risk level. If the
sleep request fails, the firmware waits out the same interval awake so the
cadence holds.
uint32_t sleep_sec = afterhrs
? (uint32_t)g_state.heartbeat_afterhours_s
: moving ? (uint32_t)g_state.heartbeat_moving_s
: (uint32_t)g_state.heartbeat_stopped_s;
// ...
// Sleep in STOP2 until the Notecard raises ATTN sleep_sec seconds from now.
// cxSleepUntilAttn() sends card.attn "sleep" (checked, since the host stays
// alive to see the response), waits for ATTN to go low, and enters STOP2.
// Execution resumes on the next line; g_state stays in RAM.
// ...
if (!cxSleepUntilAttn(notecard, sleep_sec, NULL, log)) {
// The Notecard didn't take the sleep request, or ATTN never went low
// (check the ATTN -> D5 jumper). Wait out the same adaptive interval
// awake so the sensing cadence — and the after-hours scan rate — is
// preserved, then return to loop(), which calls runCycle() again with
// g_state intact (staged immobilize, alert cooldowns, heartbeat timing,
// and fence state are all still in RAM).
// ...
delay(sleep_sec * 1000UL);
}8. Data Flow
Collected every wake cycle. Ignition state (12V voltage divider), motion
status (Notecard accelerometer, read with card.motion), GPS fix (Notecard
GNSS, read with card.location), battery voltage (card.voltage), and UTC
epoch (card.time). The design uses no I²C sensors outside the Notecard,
because all of the sensing is built into the Notecard for Skylo module.
Transmitted:
tracker.qo: heartbeat Notes are queued at the moving/stopped sample rate (every 5 minutes while moving, every 60 minutes while stationary), then transmitted in batches on thehub.set outboundschedule (default 240 minutes, set byoutbound_min). Moving heartbeats collect in the Notecard's local flash at the 5-minute sample rate, but they don't reach Notehub one at a time on that cadence. They're flushed as a batch when the outbound window fires, or earlier if async:truealert happens to force a session. So real-time pursuit tracking relies on repeated alert syncs, not on the heartbeat delivery cadence. In normal stationary operation, a properly secured job site generates 24 heartbeat Notes queued per 24 hours (one per hour), 6 outbound sessions per 24 hours (one every 4 hours, each carrying ~4 heartbeats), zeroalert.qoNotes, andfence_ok:1in every heartbeat. Inbound sessions depend on context: roughly 12 sessions during the 12-hour business day while parked (every 60 minutes), and up to roughly 180 sessions during the 12-hour after-hours window (every 4 minutes). All of them count toward the daily radio-session total and energy budget.alert.qo: sent only when a rule trips, withsync:trueto bypass the outbound queue. Each alert type (geofence_breach,motion_after_hours) has its own 5-minute cooldown, so slow geofence oscillations or intermittent wind-buffeted vibration can't flood the alert channel, and one alert type can't suppress the other.
Routed. Both Notefiles reach Notehub over cellular (LTE-M / NB-IoT) or, when cellular coverage fails, over Skylo satellite NTN (non-terrestrial network). Routing from Notehub to the operator's downstream system of choice depends on the project (see the routing docs).
Alert triggers:
geofence_breach: the cached GPS fix is more thanfence_radius_mfrom the stored geofence center. The check uses the most recently cached fix fromcard.location, andfix_age_sin the alert body shows how stale that fix was when the alert fired (-1ifcard.timewas unavailable).motion_after_hours: the Notecard accelerometer reports"mode":"moving"during the after-hours window AND ignition is confirmed OFF. Motion with the ignition off is the telltale sign of equipment being towed or loaded instead of driven.ignition_on_immobilized: the host sees an OFF→ON ignition edge whileimmobilize_pendingis set and asserts the relay on that same wake cycle. This is a staged, edge-triggered immobilizer. The relay does not fire while the engine is already running, only on the next key cycle the host observes (the previous wake's ignition state must have been OFF).immobilize_armed: the operator's command was received and staged, and the relay will fire on the next OFF→ON ignition edge the host observes.cmd_retrieve_failed:note.getonimmobilize.qiran out of retries or returned an unexpected error on this wake cycle, so the command path is unhealthy. It's rate-limited byalert_cooldown_min(default 5 minutes) so a persistent fault can't flood the operator's inbox or the satellite budget.
Inbound commands. The operator sends {"cmd":"immobilize"} or
{"cmd":"release"} to immobilize.qi from the Notehub UI or REST API.
End-to-end command latency has two sequential parts. First, the Notecard has to
complete an inbound sync session to pull the queued Note from Notehub. That
interval depends on context: inbound_min (default 4 minutes) after hours or
while moving, or heartbeat_stopped_min (default 60 minutes) when parked
during the day. Second, the host has to wake and run runCycle(), where
checkAndHandleCommand() drains the entire immobilize.qi queue by calling
note.get in a loop until it's empty, applying each command in order (last
command wins). The host wake interval also depends on context: ≤ 2 minutes
after hours, ≤ 5 minutes while moving, and ≤ 60 minutes by default when
stationary during business hours.
Under default settings, the combined worst-case end-to-end latency is up to
~6 minutes after hours (≤ 4 minutes inbound sync + ≤ 2 minutes wake interval)
and up to ~9 minutes while moving (4 + 5 minutes). The worst window is a
stationary unit during business hours, at just under 2 hours from posting
the command to seeing the immobilize_armed acknowledgment in Notehub (up to
60 minutes for the daytime inbound sync + up to 60 minutes of wake interval).
If you need near-real-time command delivery around the clock, reduce
heartbeat_stopped_min, or see §11 Production next steps for the
interrupt-driven ignition sense upgrade. Keep in mind that inbound delivery
needs cellular or WiFi connectivity. immobilize.qi has no compact template,
and the Notecard only syncs templated Notefiles over satellite, so a command
staged while the unit is out of cellular coverage gets delivered once cellular
is restored.
9. Validation and Testing
Expected steady-state cadence. A correctly deployed tracker on a secure job
site queues tracker.qo heartbeat Notes at the stationary cadence (every 60
minutes by default, day and night), sends zero alert.qo Notes, and reports
fence_ok:1 in every heartbeat. Notes collect in Notecard flash and go out in
batches every 4 hours by default, so expect 24 heartbeats queued per 24-hour
period (6 outbound sessions, each carrying ~4 Notes). After hours the device
wakes every 2 minutes to scan for motion and check inbound commands, but it only
emits a heartbeat when the 60-minute interval is up. Any single alert.qo event
is worth investigating.
Geofence first-light test. With the unit powered and showing as connected
in Notehub, set fence_lat and fence_lon to the device's current coordinates
(you can see them in Notehub's device view), and set fence_radius_m to
something small (e.g. 20). Then wait one inbound sync cycle (default 4
minutes after hours, 60 minutes while parked during the day) for the env-var
update to reach the device.
Important: Motion does not wake the host MCU. The host only polls
card.motion when it wakes on its normal timer, and during business hours a
stationary unit wakes every 60 minutes (default heartbeat_stopped_min).
Physically moving the device won't get a response until the next scheduled
wake. To make the test practical, do one of these before you move the device:
- Run the test during the after-hours window (default 6 PM–6 AM UTC), when the host wakes every 2 minutes.
- Temporarily reduce
heartbeat_stopped_minto2in Notehub env vars and wait for the update to take effect. - With
DEBUG_SERIALenabled, connect an ST-LINK to the Notecarrier CX debug jack so you can watch for an activerunCycle()wake, and move the device right after one.
Once the device has woken with the updated geofence and you've moved it ~25 m
outside the radius, a geofence_breach alert should show up in the Notehub
event log after the next qualifying wake cycle that has a sufficiently fresh
GNSS fix. Geofence decisions use the most recently cached fix from
card.location (bounded by the card.location.mode periodic interval, default
5 minutes), so one or two wake cycles may pass before the Notecard gets a new
fix that puts the device outside the radius and trips the alert. The alert
fires in the same runCycle() that evaluates the breach, so nothing defers it.
Its timing depends on how fresh the GNSS fix is as well as on host-wake
latency.
Immobilizer test. From the Notehub UI, post {"cmd":"immobilize"} to
immobilize.qi. Two steps have to finish before the host acts on it: (1) the
Notecard retrieves the Note on its next inbound sync, and (2) the host wakes and
calls note.get in checkAndHandleCommand(). To test this in a reasonable
amount of time, either reduce heartbeat_stopped_min to 2 (matching the
after-hours cadence) or bench-test during the after-hours window, when the host
wakes every 2 minutes. Watch the Notehub event log for immobilize_armed. The
host queues that Note after it processes the command, so seeing it confirms
the host wake cycle finished. The relay fires on an OFF→ON ignition edge, not
a level check, which means at least one wake has to record ignition OFF before
the firing wake records ignition ON. To simulate a key-on edge, hold A2 at 0 V
(ignition divider output low) for one full wake cycle, then pull A2 to 3.3 V (or
briefly short the voltage divider output high) before the next wake. On the
wake that sees the OFF→ON transition, ignition_on_immobilized should appear in
alert.qo and the relay driver pin (A1) should go HIGH.
Power validation with Mojo. Treat the table below as a starting point for
sizing and diagnosis, not a substitute for a bench soak. There's one important
caveat on the published Notecard figures. Blues publishes Notecard current at
5 V on the VUSB input, and at the LiPo rail (~3.7–4.2 V) the same power
dissipation shows up as proportionally higher current. Blues doesn't publish
system-level totals (host + charger quiescent + relay driver), so measure them
on your specific build with the Mojo.
The published Notecard for Skylo figures come from the NOTE-NBGLWX low-power design guide and the NOTE-NBGLWX datasheet. The system-level totals add the STM32 host, the solar charger's quiescent draw, and the relay driver overhead to the Notecard baseline. Blues doesn't publish those, so you have to characterize them on your own build. Put the Mojo inline on the +VBAT rail and measure each phase instead of leaning on generic estimates.
| Phase | Notecard-only (published, @ 5 V) | Whole-device guidance (measure with Mojo) |
|---|---|---|
| Deep sleep (host in STOP2, Notecard idle) | ~8–18 µA (published) | Notecard contributes ~8–18 µA, the STM32L433 host in STOP2 adds ~1–2 µA, and the solar charger's quiescent draw adds a few µA more, so expect a floor around ~10–20 µA. Measure the total on your build. |
| Host active, I²C transactions only (no radio) | N/A | Host active current dominates. Measure it with the Mojo on your build. |
| Notecard cellular session (LTE-M) | ~250 mA while the modem is active (published). Millisecond spikes up to nearly 2 A only apply where GSM is in use. | The Notecard dominates, and the host and relay driver add a little on top. Measure with the Mojo for system totals. |
| Notecard satellite session (Skylo NTN) | Not separately published | Measure with the Mojo outdoors once you have confirmed satellite lock. Session current and duration depend on antenna placement and sky-view geometry, so getting accurate numbers takes field testing. |
Sizing the energy budget. Two independent cadences drive the radio, and the power budget has to account for both:
-
Inbound sync sessions follow context. While the equipment is parked during the day, the Notecard inbound interval matches
heartbeat_stopped_min(default 60 minutes), which works out to roughly 12 inbound sessions over the 12-hour business day. During the after-hours window the interval drops toinbound_min(default 4 minutes), for up to roughly 180 inbound sessions per 12-hour after-hours period. In practice the Notecard combines many of these with outbound sessions, so the real radio-on count is lower. Under default settings the combined total tops out around 200 sessions/day (12 + 180 inbound plus 6 outbound). That's far fewer than a constant 4-minute poll, and it drops further if the after-hours window is narrower. -
Outbound sync sessions carry queued heartbeat Notes to Notehub on the
hub.set outboundschedule (default 240 minutes, set byoutbound_min), roughly 6 outbound sessions per 24 hours. Each one usually carries the ~4 heartbeat Notes queued during its 4-hour window. Alert syncs (sync:true) add short extra bursts when a rule trips.
The host wake cycles (every 2 minutes after hours, every 5 minutes while moving, every 60 minutes at rest by default) are host-only active phases. Each one is a few seconds of I²C transactions, and the radio stays out of it unless an alert fires during that wake.
Measure the real phase currents on your build with a Mojo bench soak, then
estimate the daily mAh draw for your deployment's cadence and size the LiPo and
solar panel to match. Use the Mojo bench soak to
validate that estimate too. Splice it inline on the LiPo-to-Notecarrier-CX +VBAT
wire and measure a 24-hour soak. A continuous draw of >50 mA means the host
isn't entering STOP2, so check the ATTN→D5 jumper and confirm the firmware
was built with the usb=none board option. If the trace looks right but alerts
don't go out, check the antenna placement and the hub.set outbound
configuration.
To validate a satellite session, the antenna has to be outdoors with a clear view of the sky toward the equator, and the first satellite acquisition can take several minutes. The Satellite Best Practices guide covers troubleshooting for antenna placement and acquisition timing.
10. Troubleshooting
Device not appearing in Notehub after power-on.
- Confirm USB power is stable (green LED on the Notecarrier CX). If there's no LED, the USB connection may be faulty.
- Check that the ProductUID is pasted correctly into the firmware sketch (top of
construction_equipment_anti_theft.ino). An empty or mismatched ProductUID prevents auto-provisioning. - Make sure the antenna cables are fully seated in their u.FL connectors. A loose MAIN antenna means no cellular lock and no Notehub connection.
- If you're on Skylo satellite only (no cellular), the first satellite acquisition can take 2–5 minutes even with a clear view of the sky. Point the antenna at the southern sky and give it 5 minutes before you call it a failure.
- If DEBUG_SERIAL is enabled in the firmware, open the ST-LINK virtual COM port (Notecarrier CX debug jack, 115200 baud) to watch live I²C activity and Notecard responses.
Heartbeats appear but fix_age_s is always -1 or very large.
fix_age_s = -1meanscard.timewas unavailable when the Note was queued. That's normal on a cold boot, before the Notecard has synced with Notehub. After one successful outbound sync,card.timeshould populate andfix_age_swill show real elapsed seconds.- If
fix_age_sstays at-1after several outbound syncs, check that cellular or satellite sessions are completing (look for successful sync activity in Notehub Events). A device that can't reach the network never completes a session, so it never gets epoch time. - If
fix_age_sis consistently >300 seconds (approachingheartbeat_moving_min × 60), the Notecard's GNSS module isn't getting fresh fixes. Check the antenna placement and sky view. GPS acquisition usually takes 20–60 seconds in open sky, and metal roofs or urban canyons can stretch that out a lot.
Geofence alerts never fire even though device moves outside the configured radius.
- Confirm that
fence_enabledis set to1and thatfence_lat/fence_lonare filled in on Notehub (Fleet Settings → Environment). Withoutfence_enabled=1, the firmware falls back to checking that both coordinates are non-zero, which fails at the prime meridian (0°,0°). - Make sure the device has woken since you set the env vars. The inbound sync
interval is 4 minutes (default
inbound_minafter hours) or 60 minutes (defaultheartbeat_stopped_minduring business hours). Watch the Events log for a heartbeat after the sync should have completed. - If a heartbeat shows up but there's still no alert after you've moved outside
the radius, check the heartbeat's
fix_age_s. Over 300 seconds means the GNSS fix is stale and may not reflect your current position. Move the device and wait for the next wake so GNSS can re-acquire, which can take 5 minutes if the device was stationary and GNSS wasn't active. - During business hours the device only wakes every 60 minutes (default). For a
quick geofence test, reduce
heartbeat_stopped_minto2in Notehub and wait for it to take effect. While parked during the day, the inbound sync that delivers it can take up to 60 minutes, and the host only reads it on its next wake (up to 60 minutes more). Then move the device and wait for the next 2-minute wake cycle.
Immobilizer command appears in Notehub but relay never fires.
- Confirm that
immobilize_armedshows up in the Events log within the expected latency (worst case: 60 minutes inbound sync + 60 minutes host wake = 2 hours during business hours). Ifimmobilize_armednever appears, the device never received the command. - If
immobilize_armeddoes appear, the command was staged and the relay just didn't fire. The relay fires on an OFF→ON ignition edge (not a level check), so make sure you simulated the edge correctly. The A2 GPIO has to sit at GND (0V, ignition OFF) for at least one full wake cycle, then go to 3.3V (ignition ON) before the next wake. The relay only asserts on the wake that observes the transition. - Check the relay driver wiring (A1 GPIO → BSS138 gate). If the A1 wiring is loose or shorted to GND, the relay can't energize. Also confirm the 10 kΩ pulldown is in place (gate to GND) to keep the MOSFET off when the GPIO is high-impedance.
- If A1 goes HIGH but the relay doesn't click, check that the 1N4007 diode is oriented correctly (cathode to +12V, anode to MOSFET drain). A reversed diode conducts as soon as the MOSFET turns on and shorts out the coil, which can also blow the coil fuse or damage the MOSFET.
High power draw (Mojo shows >50 mA sustained).
- That means the host isn't entering STOP2. Check the
ATTN→D5jumper (§5). Without it,cxSleepUntilAttn()never seesATTNgo low and waits out each interval awake. Also confirm the firmware was built with USB support set to None (usb=none). With the USB CDC stack enabled and no USB host attached, the host leaves STOP2 within a millisecond. - Make sure DEBUG_SERIAL is commented out (not
#define DEBUG_SERIAL 1) in field builds, because the debug UART adds to the active-wake current. - Power the unit from the LiPo + solar charger instead of bench USB to see true sleep behavior on the Mojo.
- Check the Notecard firmware version.
card.versionshould show a recent Skylo-certified build, since outdated firmware may have high quiescent current. Update it over the air from Notehub (OTA DFU with Notehub).
Satellite acquisition fails or times out.
- Skylo satellites sit in geostationary orbit (GEO) over the equator, so the antenna needs a clear view of the southern sky. From the northern hemisphere the satellite is low on the southern horizon, and objects above 15° south-of-horizon elevation may block the link.
- The first satellite acquisition can take 2–5 minutes, and antenna orientation and a clear sky are critical. Satellite Best Practices has detailed placement guidance.
- When Skylo is the fallback and cellular is available, the device prefers cellular and never triggers satellite. To test satellite, disconnect cellular or take the device somewhere with no cellular coverage but a clear view of the sky (e.g., a remote rural location).
- Make sure the antenna on Notecard for Skylo (NOTE-NBGLWX) is the Skylo-certified antenna that came with the kit. A substitute may not lock onto the Skylo network. The datasheet lists the antenna part number.
11. Limitations and Next Steps
This POC shows the control path, the data path, and the satellite fallback working, but it deliberately stops short of being a production security device. A working immobilizer for a deployed fleet needs a latching relay that survives sleep gaps, a transient-protected ignition front end that can handle automotive load dump, and an interrupt-driven wake that catches a key-on edge within the next 100 ms instead of the next two minutes. The items below cover each of those, first as the reference design's scope limits and then as the engineering work that turns it into a production product.
Simplified for the POC
Each item below is a spot where the POC deliberately falls short of a production security device, along with the engineering work that would close the gap.
Staged, polled, edge-triggered immobilizer, not a continuously-held cut. The
immobilizer in this POC is a next-wake / next-key-on-edge design. The host
sleeps in STOP2 instead of powering off, so its GPIO state is retained and the
relay coil stays energized through the sleep window once immobilized = true.
The catch is that the standard relay then draws continuous coil current (tens
to ~150 mA at 12 V, depending on the relay) from the equipment battery for as
long as the immobilize is active. A wiring interruption or host reset still
releases the NC contact and restores the ignition circuit, too. The relay also
only fires on an OFF→ON ignition edge, so the previous wake must have observed
ignition OFF and the current wake must observe ignition ON. If the equipment is
already running when the immobilize command is staged, the firmware
deliberately holds off until the next key cycle, which avoids cutting the
starter circuit while the engine is actively running (per the safety guidance
in §5). And if the thief cycles the key all the way between two host wakes
(OFF→ON→OFF inside one sleep window), the firmware misses the firing edge
entirely until the next OFF→ON transition it observes. Treat this as a
proof-of-concept staging mechanism. Production deployments need a latching
(bistable) relay that holds its contact state with no continuous coil power
and survives host resets, power cuts, and wiring interruptions. See Production
Next Steps below.
Ignition-sense front end is POC-only: no transient protection. The bare 33 kΩ / 10 kΩ voltage divider does nothing against automotive electrical transients. Load-dump events on generator sets can produce transients 40–200 V above nominal 12 V, and starter-motor switching puts fast inductive spikes on the battery rail. Either one can exceed the host GPIO absolute maximum and damage the MCU. At a minimum, a production design must add a TVS diode (e.g. 15 V unidirectional, placed before the high-side divider resistor) and an RC low-pass filter (e.g. 1 kΩ + 100 nF at the A2 node), or use a properly isolated sensing front end. The relay side of the vehicle interface carries the same risk. The 1N4007 flyback diode protects the MOSFET drain from the relay coil's inductive spike, but it doesn't protect the 12 V feed from upstream transients, and the inline fuse in the BOM only addresses overcurrent.
No interrupt-driven ignition sense. The firmware polls the ignition state on
each wake, so a thief who turns the key and drives away within the 2-minute
window between wakes gets a head start before the ignition_on_immobilized
event fires. A hardware interrupt on the ignition-sense line would wake the host
the instant the key turns on. The Notecarrier CX's STM32 host can wake from
STOP2 on a GPIO edge (an EXTI interrupt on A2, registered next to the existing
ATTN wake with LowPower.attachInterruptWakeup()), but this POC doesn't
implement it. The Notecard's AUX1–AUX4 pins aren't broken out on the
Notecarrier CX, so the card.attn auxgpio approach isn't available on this
carrier.
Inbound commands require cellular. The firmware doesn't register a compact
template for immobilize.qi (or for environment variables), and the Notecard
only syncs templated Notefiles over satellite. So commands from Notehub to the
device need a cellular or WiFi connection to get delivered. If the equipment is
already in a dead zone, the operator has to wait for cellular to come back
before the command arrives.
Geofence decisions use cached location data, and daytime detection latency is
bounded by heartbeat_stopped_min. The firmware calls card.location to read
the Notecard's most recently cached GNSS fix instead of waiting for a fresh
acquisition. The GNSS acquisition cadence bounds the cached fix age
(card.location.mode seconds, default heartbeat_moving_s = 5 minutes), so one
or two after-hours wake cycles may check the geofence against the same stale fix
before GNSS re-acquires. A geofence breach alert won't appear until (a) the host
wakes and (b) the Notecard has a fresh enough fix that puts the device outside
the radius. During business hours, when the host wakes every 60 minutes
(default), the worst-case detection latency for a theft that starts from a
parked state is about 60 minutes plus up to 5 minutes of GNSS fix lag. Every
outbound Note includes fix_age_s (seconds ≥ 0, or -1 when card.time is
unavailable) so operators can judge how stale each geofence decision was.
Geofence is a circle. The firmware uses a Haversine-computed circle around a single center point. An irregular job-site boundary (an L-shaped lot, or equipment lots separated by roads) would need multiple overlapping circles or a polygon geofence, and this design implements neither.
Single geofence. The device stores one home location. Equipment that moves
between job sites for legitimate reasons (morning deliveries, weekend staging)
will trigger false geofence breach alerts unless the operator updates
fence_lat / fence_lon in Notehub before each move.
No tamper detection. The firmware can't detect cable cutting, enclosure intrusion, or GPS jamming. A motivated thief who finds and removes the tracker, jams the antenna, or removes the equipment battery before the solar reserve is depleted will defeat this POC design.
Satellite data budget. Notecard for Skylo comes with 10 KB of bundled Skylo
data, and that's a bundle, not a monthly allowance. Working from the actual
template field types, the compact records come to 26 bytes per tracker.qo
heartbeat (4 floats × 4 bytes + 5 int16 × 2 bytes) and 47 bytes per
alert.qo event (4 floats × 4 bytes + 3 int16 × 2 bytes + 24-char string field
at 25 bytes). On payload alone, the budget covers roughly 390 heartbeat-sized
Notes (10,240 ÷ 26 bytes) or 215 alert-sized Notes (10,240 ÷ 47 bytes) in
total. Satellite-session protocol and framing overhead bring the usable counts
down from there, so validate against Notehub usage metrics and field
measurements instead of relying on payload-only arithmetic. In normal
stationary operation, heartbeats queue every hour, which comes to 24 × 26 = 624
bytes a day. Batching them into ~6 outbound sessions a day doesn't shrink that,
so a unit that stays on satellite would use up the whole 10,240-byte bundle on
heartbeats alone in about 16 days. Alert storm risk: if the equipment is
under active pursuit and loses cellular coverage, repeated sync:true alerts
every alert_cooldown_min (default 5 minutes) can burn through the Skylo budget
fast. At 47 bytes × 288 alerts/day (5-min cadence) = 13.5 KB, a single day would
use more than the whole bundled satellite allowance. If you expect
satellite-only operation during a pursuit, raise alert_cooldown_min to 15–30
minutes with the Notehub fleet env var to protect the budget. In production,
consider limiting satellite transmission to alerts only and enforcing a
per-Note minimum satellite interval. Inbound checks count too. Each one costs
about 50 bytes over satellite (Satellite Best
Practices),
so the 4-minute after-hours inbound_min cadence could spend most of the bundle
in a single night on satellite, and commands can't arrive that way anyway. Set
the _sync_inbound_mins_ntn reserved environment
variable
to a long interval (e.g. 1440) to give satellite its own, much slower inbound
cadence.
Mojo is bench-only in this POC. The firmware doesn't read Mojo's LTC2959 coulomb counter over the Qwiic bus. Mojo only serves as a bench measurement instrument during validation.
Production Next Steps
Here's the engineering work that turns this POC into a production security device, in rough order from the most critical hardware change to fleet management.
A bistable (latching) relay replaces the standard relay. It's a dual-coil
type, where one coil pulse sets the contact and a second pulse resets it, and it
needs no continuous coil current to hold either state. Look for a dual-coil
bistable mechanism, 12 V set and reset coils, a contact rating suited to the
ignition/run-start control circuit, and an automotive-rated or equivalent
environmental rating. The host drives it with single-pulse assertRelay /
releaseRelay calls, and the relay contact state survives host sleep, power
interruption, and wiring cuts.
Interrupt-driven ignition sense closes the polling gap. Register the
ignition-sense pin (A2) as a second STOP2 wake source on the STM32 host with
LowPower.attachInterruptWakeup(PIN_IGNITION_SENSE, isr, RISING, DEEP_SLEEP_MODE),
next to the existing ATTN wake in cx_sleep.h. A key-on edge then wakes the
host instantly instead of waiting for the next timer to expire. (The Notecard's
AUX1–AUX4 pins aren't broken out on the Notecarrier CX, so card.attn
auxgpio mode isn't an option here.)
Satellite data budgeting protects the bundled allowance. Track the cumulative satellite Note count in state, keep non-alert heartbeats off the satellite path, and enforce a minimum 15-minute satellite interval, even after hours.
GPS jamming detection catches an active countermeasure. If card.location
keeps returning err while the Notecard accelerometer shows motion, log a
gps_jamming_suspected alert.
Multi-geofence support handles equipment that moves for legitimate reasons.
Store several geofence Notes in a .db Notefile, one site per Note with its
lat/lon/radius, and have the firmware query all of them and treat itself as
in-fence if it's inside any one.
A dedicated anti-tamper microswitch on the enclosure lid, wired to a spare
host GPIO (also registered as a STOP2 wake source), sends an enclosure_opened
Note if someone tries to physically remove the device.
Notecard Outboard DFU adds wireless firmware updates for the STM32 host. That's how you push updated geofence logic, new alert types, or threshold recipes to an entire fleet without a truck roll.
12. Summary
The fleet manager who used to find out about a stolen skid steer when the morning crew showed up now hears about it within minutes of the wheels moving, with an immobilizer command staged to fire on the thief's next key-on. A Notecarrier CX and Notecard for Skylo, a solar-trickled LiPo, and two wires into the ignition circuit turn the equipment into something you can get back. Hourly heartbeats cover the business day, two-minute scans after hours send an alert right away on motion, and a staged relay blocks ignition on the next key cycle the host observes. A production latching relay is the clear next step from there. When the equipment ends up where stolen equipment always ends up, in a rural stash yard, a shipping container, or a metal barn, Skylo satellite still gets the location and alert Notes out long after cellular has gone dark. That's the difference between a recovered skid steer and one that's already been exported.
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
- Modules
- Sensor reading strategy
- Event payload design
- Low-power strategy
- Retry and error handling
- Key code snippet 1: compact Note template definition
- Key code snippet 2: immediate-sync alert
- Key code snippet 3: relay assertion on key-on edge
- Key code snippet 4: adaptive sleep until the Notecard raises ATTN
- 8. Data Flow
- 9. Validation and Testing
- 10. Troubleshooting
- 11. Limitations and Next Steps
- 12. Summary