IoT Pet Health Tracker — Build
Twelve years of paying other people's subscriptions to learn my pets are home. This is the build log for the tracker I actually want — cheap, no-subscription, months of battery, location first and health later — designed the way I'd run any connected-product program, then soldered together nights and weekends.
The Pet IoT Field Guide is the observer's seat — twelve years of what shipped and what broke, kept in the order it happened. This notebook is the opposite chair: the builder's log.
I've spent a career building connected-hardware platforms and a decade buying connected-pet gear for Atom, Joule, Boson, and Quark. The two finally collided over a renewal notice. What follows is the whole train of thought — from "I'm tired of this" to a product spec, to the gear order, to the nights-and-weekends build that comes after. Location first, on BLE and LoRa. Cellular second. Health vitals third.
It's a real build with a full-time job attached, so it moves at the speed of evenings. Follow along — including the parts that don't work.
The Renewal Notice That Made Me Build My Own Pet Tracker
Three pets, three subscriptions, and the bulk of what I get for the money is a green dot that says 'home.' After a decade of paying other people to tell me my pets are where I left them, I'm building the tracker I actually want.
The renewal notice landed for the third time this year — different pet, same number. Add it up across Quark's collar and the two on the cats and I'm spending north of three hundred dollars a year, most of it to confirm that animals who rarely leave my house are, in fact, in my house.
I've been in connected products my whole career, and buying connected-pet gear since Atom got his first Whistle in 2013. Twelve years. It took a renewal email to make me actually do the math on what I'm paying for.
What I'm actually paying for
Strip it down and the answer is uncomfortable: presence. Ninety-five percent of what these trackers tell me is "your pet is home," which I could get from a two-dollar radio that never leaves the house. The cellular link — the expensive part, the part the subscription exists to fund — earns its keep maybe a handful of times a year, when a pet is actually somewhere it shouldn't be.
And the cellular link is also why I'm on the charging treadmill. Always-on LTE is the reason these collars die every few days. So I'm paying a monthly fee and a recurring chore, both in service of a capability my indoor cats use approximately never.
The math only gets worse the longer you run it. A subscription is a line that never stops climbing — three pets at ~$100/year each, compounding for as long as you own the gear. A one-time build is a single step up for parts and then flat. The two lines cross sooner than the renewal email wants you to notice.
Tractive just proved my point
Right on cue, Tractive shipped the CAT 6 Mini this month — their newest cat tracker, and the first to carry real Health Intelligence: resting heart rate and respiratory rate on a 1.13-ounce collar. It's genuinely impressive, and it tells me the whole category is finally moving toward health, which is where the real value has always been.
But look at what it actually is: another cellular collar, another subscription, per cat. Joule and Boson have worn Tractive trackers for years and they don't leave the house. Putting a cellular plan on a cat that's asleep on the couch is the most on-the-nose version of the problem I've been paying into for a decade.
The same gaps, twelve years running
I started all this with Atom — the first Whistle on his collar two months after he came home in 2013. He's gone now, and across his whole life the trackers got better at counting steps and never once got better at the things that actually bothered me: the cost scaled linearly with every pet I added, the batteries needed charging constantly, and the indoor case always paid full freight for range it never used.
Quark wears the dog version now; the cats wear theirs. Better silicon, glossier app, same three gaps.
So I'm going to build it
Here's the thing — I do this for a living. I just open-sourced a reference IoT stack: AWS IoT Core, device certs, ingest, a dashboard, the works. The cloud half of a pet tracker is already sitting in a repo with my name on it. I have the backend, I have the wireless decision rubric I make every team run, and I have twelve years of receipts on exactly where these products fall short.
So I'm building the one I actually want. The bet is simple and it's the inverse of what everyone ships:
- Cheap, with little or no subscription — because most of the value doesn't need a cellular plan.
- Months of battery, not days — because nobody wants to charge the thing.
- Location first, health later — get the dot reliable before it takes a heart rate.
- Matched to how pets actually live — home most of the time, occasionally on a trail, rarely truly lost.
Next post is the part where I resist the urge to start soldering and do the PM work first: the use cases, the rubric, and an honest read on whether this is a product or just a grudge. Then we buy parts.
Speccing a Pet Tracker That Doesn't Need a Subscription
Before I buy a single board, the PM work: three use cases, the wireless rubric, and the product line that falls out of it. The punchline is that the two loudest complaints about pet trackers — price and charging — have the same root cause, and you fix both by refusing to put cellular where it isn't needed.
Last time I talked myself into building a pet tracker. This time I do the part that keeps a grudge from becoming a garage full of dead prototypes: the product work, before the soldering iron.
Three use cases, in priority order
- Is my pet home / in the yard? The 95% case. Wants near-zero power, near-zero data, an instant "they left" alert, and activity tracking.
- My pet is off-grid with me — a trail, the woods. No cell coverage. I want to see where they are relative to me.
- My pet got out and is miles away. Rare, high-emotion — and the fear that actually sells trackers.
Most products are built entirely around #3 and bill you monthly for it. But #1 is where pets spend their lives.
The rubric, applied
I make every connected-product team I run answer five questions before picking a radio. Run them on a pet:
- Range? Home is meters. A trail is kilometers. "Lost in the next county" is anywhere.
- How often does it phone home, and how big is the message? A GPS fix is ~12 bytes, and at home you only need a heartbeat. Tiny.
- BOM budget? Consumer. Every dollar of radio locks the rest of the bill.
- Power budget? Small battery, and — per the last post — no charging cult.
- Security model? Consumer. A device cert to the cloud, TLS, no drama.
Answer those honestly and you do not arrive at "always-on cellular for everything." You arrive at a spectrum.
The connectivity spectrum
| Radio | Range | Power | Cost | Good for |
|---|---|---|---|---|
| BLE | meters | sips (an AirTag runs ~a year on a coin cell) | ~free | "Is it home?" presence |
| LoRa (915 MHz) | kilometers, line-of-sight | low | cheap, no carrier | trail + local recovery, if you bring the gateway |
| Cellular (LTE-M) | anywhere with a tower | hungry | hardware + monthly data | the genuinely-lost case |
The whole insight is one line: match the radio to the usage. A pet that's home 95% of the time should be running its cheapest, lowest-power radio 95% of the time — and only reach for the expensive one during the rare event that needs it.
The product line
That spectrum is the product line. Three SKUs, so the customer picks their own range / cost / battery trade instead of me guessing:
| SKU | Radios | Battery | For | Money |
|---|---|---|---|---|
| LoRa collar | BLE + LoRa + GPS | months | home/indoor pets; trail dogs (with the base station) | hardware + optional ~$20/yr cloud history |
| Cellular collar | BLE + LTE-M + GPS | days–~2 wks | roamers / escape artists / "find from the couch" | premium hardware + data sub (still under the incumbents) |
| Base station / handheld | BLE + LoRa | weeks (docked = always charged) | every LoRa-collar owner | bundled with the LoRa collar |
The tiered-power trick
The LoRa collar does one clever thing: it lives in BLE presence mode at home (months of battery, ~no data), and only flips to findable mode — GPS fixes + LoRa beacons — when it crosses the geofence. The expensive radio work only happens during the rare event, so the months-long battery survives. That single decision is what delivers both of the things people complain about: low cost and no charging.
The base station is the network
Normally it's a docked listener at home. But it's built as a handheld in a charging cradle — Garmin-Alpha style — so you grab it off the dock and it becomes a mobile LoRa gateway. Pet got out? You drive a grid around the neighborhood and re-acquire its beacon; the collar's GPS gets you to the block, and an RSSI proximity beep (faster and louder as you close in) gets you to the actual bush. On a trail, you just carry it and watch the pet relative to you, phone over BLE for the map.
And the strategic part: crowdsource the base stations. Every docked unit passively listens for any lost collar nearby and reports it. At neighborhood density that's a pet-specialized recovery mesh — the Apple Find My / Amazon Sidewalk model, seeded by always-on home base stations instead of phones. Tractive can't copy it; pure cellular has no network effect. The honest catch is cold-start: the mesh is only magic once it's dense, so early on recovery leans on you and your own handheld. Same chicken-and-egg every crowdsourced network faced.
Little features that punch above their BOM
A piezo buzzer costs pennies, so both the collar and the base get one:
- Collar: beep to find it (it fell off, or the pet's hiding), a recall/training cue, and a humane no-shock "you're leaving the yard" boundary warning.
- Base: an audible "he got out" alert the moment the pet crosses the geofence or drops off the radio — so you're not glued to the app — plus that proximity beep while you search.
The build phases
- Location — BLE + LoRa. The novel, can't-buy-it part: presence, the geofence flip, the mesh, the base station. The hardest phase, on purpose.
- Cellular. The "anywhere" premium SKU. A paved road (Nordic's nRF9160 + reference firmware), so it's a low-risk bolt-on.
- Health. And here's why it's last: pet vitals are mostly an IMU + algorithms problem, not a new sensor. A sensitive accelerometer picks up the body's micro-vibrations at rest — that's how the incumbents derive resting heart rate and respiratory rate, fur and all. So the accelerometer I put on the collar in phase 1 becomes the vitals sensor in phase 3, once tracking is rock-solid.
A phase-4 idea worth writing down now: the base station is stationary, right where the pet sleeps. It could later carry a mm-wave radar for fully contactless resting vitals — heart and respiratory rate with no collar contact at all. That's the magic version of "the base station is more than a charger."
The honest part
- AirTag already owns cheap + no-subscription + find-my-network at $29. So my moat is not raw location — it's the pet features Apple won't build (activity, sleep, vitals trends, multi-pet, family alerts, vet-shareable data) plus the pet-specialized mesh. The position is Whistle's features, AirTag's price model.
- A LoRa collar can't find a pet past the edge of the mesh and your car's reach. That's exactly what the cellular SKU is for. The line lets the buyer choose.
- At volume the hardware BOM is ~$30. The reason incumbents charge $120/yr is the cellular data plan — remove cellular and you remove the cost basis for the subscription, which is how ~$20/yr for cloud history is honest and still 6× cheaper.
Spec's done. Next post: I stop typing and hit buy.
The Gear, the Bill, and a Six-Month Nights-and-Weekends Plan
The spec is set, so today I hit buy — but the order got smarter than the plan. I was going to build the handheld base station from modules; turns out it already exists off the shelf, pre-flashed and FCC-certified, as a two-pack. So step one is a proof-of-concept I can strap to Quark the day it lands, then the collar build behind it.
The spec is done, which means today is the dangerous day: I hit buy. But the cart looks different from the plan — because while I was sketching the handheld base station, I found out somebody already builds it.
The head start
The cloud is done. The reference IoT stack I open-sourced — IoT Core, device-cert provisioning, ingest, DynamoDB, a dashboard — already accepts a real device with near-zero change. The collar just publishes pet telemetry (lat, lon, battery, activity) instead of tool telemetry. That's months of backend I don't have to write.
The shortcut I didn't expect: the Wio Tracker L1
I'd specced the base station as a handheld-in-a-cradle — screen, GPS, LoRa, battery, a button or two. Turns out that's a shipping product: the Wio Tracker L1, sold as a ready-to-use Meshtastic handheld, pre-flashed, FCC-certified, in a two-pack.

The spec reads like I wrote it for myself:
![]()
- nRF52840 + Wio-SX1262 (862–930 MHz) — the same chip family as the collar I'm going to build, so one firmware codebase covers both, and the reserved SWD pads mean the nRF9160 DK I already ordered can debug it.
- L76K multi-GNSS, 1.3" OLED, onboard buzzer, 3000 mAh + solar + USB-C. Everything the base station needs, including the beeper for the "he got out" alert and the proximity search.
- Grove + plated-through-hole I/O — I can hang an I²C sensor off it without soldering if I want to prototype the collar's heat-risk sensing on the bench.
- Pre-flashed with Meshtastic — which is the whole point of starting here.
![]()
![]()
Because it's pre-flashed and comes as a pair, it isn't just the base station — it's my entire proof-of-concept. One on Quark, one in my hand, and I can watch him move on a phone map over Bluetooth the day it arrives. No firmware, no soldering, no waiting. That's the right first dollar.
The order, reordered around the PoC
1 — Proof-of-concept + base station (ordered today, arrives tomorrow):
- Meshnology Wio Tracker L1, 2-pack (Amazon) — base unit + test node, pre-flashed Meshtastic.
2 — The collar build kit (RAK — this was the original plan; I'll revisit it once the PoC proves the range is real):
| Qty | Part | Price |
|---|---|---|
| 2 | WisBlock Meshtastic Starter Kit, US915 (SKU 116016 — base + RAK4631 core) | ~$64 (buy-2 −8%) |
| 2 | RAK12500 GNSS — u-blox ZOE-M8Q | $51.24 (buy-2 −5%) |
| 1 | RAK1904 3-axis accelerometer — ST LIS3DH | $7.97 |
| 2 | RAK1901 temp/humidity — Sensirion SHTC3 | $14.36 (buy-2 −20%) |
Plus a passive piezo buzzer (Amazon) and LiPos (Adafruit). The RAK4631 inside that starter kit is the same nRF52840 + SX1262 as the Wio L1 — so whatever firmware I prove on the PoC carries straight to a custom collar.
3 — Cellular (already ordered, for phase 2):
- Nordic Thingy:91 + nRF9160 DK (~$295, DigiKey). The DK doubles as my debugger for everything above.
The thread that ties the first two buys together is the silicon: the Wio L1 and the RAK4631 collar are both nRF52840 + SX1262, so the firmware I prove on the proof-of-concept is the firmware the custom collar runs. Cellular is the one part on a different chip — and it's deliberately last.
The phases, and what each one has to prove
- Location — BLE + LoRa. Presence at home, the geofence flip to findable mode, collar → base over LoRa, data into the existing cloud, beep + proximity search. The hard part, first.
- Cellular. Swap in the nRF9160 path for the "anywhere" collar. Lower risk — a well-documented road.
- Health. Resting heart rate and respiratory rate off the accelerometer the collar already carries. Only after the dot is boringly reliable.
The honest timeline
Six months, evenings and weekends, shipping delays included. Phase 1 only — cellular and health are next year's problem.
| Month | Goal |
|---|---|
| 1 | Wio L1 two-pack PoC — one on Quark, range tests around the neighborhood and on a trail, GPS → phone map |
| 2 | RAK4631 collar publishing GPS + battery into the existing IoT Core stack |
| 3 | BLE home-presence + geofence-exit flip to findable mode; the power budget that makes "months" real |
| 4 | Base station behaviors: home listener, grab-and-go gateway, RSSI proximity beep |
| 5 | Enclosure + collar mount + charging; survive a wet Lab |
| 6 | Field test on Quark; write up what broke |
Step one isn't a product. It's a proof-of-concept that earns the rest of the spend — before I commit to building a collar, I want to know the range and the GPS-to-phone loop actually hold up in my yard and on my trails.
Next: a scorecard, not a vibe
Which is exactly why the next post won't be "look, a dot moved." It'll be a test plan with a scorecard — range, time-to-first-fix, battery drain, in-house presence reliability — so the decision to build the real collar is made on numbers, not excitement. The boxes land tomorrow. The notebook turns into a real build log from here — the parts that work, and the parts that don't.
A Scorecard, Not a Vibe: How I'll Decide Whether to Build the Collar
The proof-of-concept hardware is here, so the temptation is to post a screenshot of a dot moving and call it a win. Instead, here's the scorecard — range, fix time, battery, presence reliability — with pass/fail targets, so the decision to build the custom collar gets made on numbers, not adrenaline.
The Wio Tracker L1 two-pack landed, which means I could post a screenshot of Quark as a dot on a map and call the proof-of-concept a success. That would be a lie of omission. A dot moving proves the radio turns on. It doesn't tell me whether this architecture survives my neighborhood, my trails, and a battery I don't want to charge every other day.
So before I spend another dollar on the custom RAK collar, the PoC has to earn it — against a scorecard with numbers I set before I started, so I can't move the goalposts when I get excited.
What landed
It doesn't show up as a finished gadget. Each unit is a flat-pack: a board, a 3D-printed enclosure in two halves, a 3000 mAh lithium pouch, a GPS patch antenna on a u.FL lead, a stubby LoRa whip, and a baggie of screws. No soldering and no firmware — but some assembly, which is honest about what this stage actually is.
![]()
Wired up before I closed the case, it's exactly the bill of materials from last post: the nRF52840 and the Wio-SX1262 LoRa radio on one board, the L76K GPS antenna on its pigtail, the LoRa antenna on an SMA bulkhead, the battery on a JST plug.
![]()
Put a ruler against the enclosure and the verdict writes itself: about 10 cm tall. That's a fine handheld — clip it to a belt, drop it in a truck cradle — and obviously not something a cat wears. Which is the split this whole series keeps circling back to: the base station you can buy off the shelf; the collar you still have to build small. This unit is the base. The collar is the custom job behind it.
![]()
![]()
The setup
Two nodes: one rides Quark (call it the collar), one stays with me (the handheld), phone paired over Bluetooth with the Meshtastic map open. That's the whole architecture in miniature — collar beacons its position over LoRa, handheld receives it, phone draws the map.
Getting it on the air — and what bit me
Two units, a foot apart, both powered on, both completely deaf to each other. Meshtastic ships with the LoRa region unset, and an unset radio won't transmit — legally it can't, because it doesn't yet know which band and duty cycle it's allowed to use. Set Region = US on each and they found each other instantly. First lesson, and a good one for any radio project: "powered on" is not "transmitting."
Then the collar looked dead anyway. It was in the node list, but its "last heard" read thirty minutes — no fresh beacons. Two defaults conspire there: the position broadcast interval ships at roughly 15 minutes, and smart positioning only beacons when the node has moved far enough. A collar sitting still on a bench is, by design, silent — wonderful for battery life, useless for a range walk, where I need a steady heartbeat to catch the exact second the link drops. So I forced it: fixed 1-minute interval, smart positioning off. Now it chirps every 60 seconds whether it's moved or not.
I rehearsed the signal before trusting it. Powered the collar off — "last heard" climbed and stuck: one minute, two, four. Powered it back on — it snapped to "now" within ten seconds. That climb-and-stick versus snap-back is the whole language of the range test: heartbeat alive, or heartbeat gone stale.
The surprise: my block was already a mesh
Here's the part I didn't see coming. The first time the app finished scanning, my node list had 41 nodes in it. I'd added two. The other 39 were my neighbors.
Meshtastic's default channel is a public commons — every node within radio earshot shows up. Thirty-nine of them around my block, most reachable only over two hops, which means my little handheld's signal was already being relayed across the neighborhood by strangers' nodes I'll never meet.
That cuts two ways. First, it's a problem for an honest range test: if a neighbor's node rebroadcasts my collar's beacon, my handheld keeps "hearing" the collar long after the direct link is dead — and I'd be measuring the neighborhood's mesh instead of my own radio. The fix was to move my two nodes off the public commons onto their own named channel. Because Meshtastic derives the radio's frequency slot from a hash of the channel name, renaming it also lifts the pair onto their own frequency, clear of the public traffic — then I switched the node view to a direct-only (zero-hop) filter, so the only thing I count is what my handheld hears firsthand.
But step back from the annoyance and that accidental discovery is the moat I sketched in the PRD. The reason a crowd-sourced tracker beats a pure-cellular one is the network effect: every unit sold becomes a relay that improves recovery for everyone — the same model behind Apple's Find My and Amazon Sidewalk. I'd assumed I'd have to bootstrap that network from zero, one sale at a time. Instead, 39 nodes of it already exist within a few hundred meters of my front door, before I've shipped a thing. The mesh isn't a someday feature on a roadmap. On my street, it's already on the air.
The scorecard
1. LoRa range — the make-or-break. If this fails, nothing else matters.
| Setting | Target |
|---|---|
| Open line-of-sight | ≥ 1.0 km |
| Suburban / through houses | ≥ 300 m |
| Dense foliage / trail | ≥ 150 m |
2. GPS.
| Test | Target |
|---|---|
| Time-to-first-fix (cold) | ≤ 60 s |
| Time-to-first-fix (warm) | ≤ 15 s |
| Position accuracy vs phone | ≤ 10 m |
3. Battery — the second make-or-break. The whole pitch is "you don't charge it constantly."
| Test | Target |
|---|---|
| Active beaconing → projected runtime | ≥ 3 days |
| Idle / presence mode → projected runtime | ≥ 2 weeks |
4. Presence + link.
| Test | Target |
|---|---|
| Home ↔ away detection (10 transitions) | ≥ 9/10 correct |
| BLE reconnect after walking out of range | ≤ 30 s, no manual fix |
5. The real test — Quark. Fit in a collar pouch, watch him cross the yard in real time, and an "escape" sim: someone walks him down the block while I re-acquire him on foot with the handheld's proximity readout.
The decision gate
This is the part that keeps it honest:
- Build the custom collar if all three range targets hold for how I'd actually use it, GPS passes, and projected active battery clears three days.
- Reconsider if range falls short in the neighborhood — that points to a better antenna, or leaning cellular-first. If battery fails, power tuning becomes priority one before any collar work.
Multi-hop mesh — the crowdsourced-recovery magic — I can't test yet; that needs a third node. It waits.
Next post is this same scorecard with the cells filled in, and an honest verdict. If the numbers say stop, I'll say stop — that's the difference between a build log and a highlight reel.
The Range Test: 1,250 Feet Direct — and Miles Through the Mesh
Range was the make-or-break criterion on the scorecard — if it failed, nothing else mattered. It didn't fail. A zero-hop direct link held to 1,250 feet in the suburbs, clearing the bar I set before I started. And the neighborhood mesh I stumbled onto last post was already relaying beacons miles further.
Last post I wrote the scorecard before I ran a single test, so I couldn't move the goalposts once I got excited. Criterion #1 was LoRa range, flagged the make-or-break: if this fails, nothing else matters.
It didn't fail. Here are the first numbers, against the targets I set.
The walk
One node on the move (call it the collar), one node home as the base station, beaconing forced to a 60-second heartbeat so I could catch the exact second the link died — the setup from last post. I started with the link solid and walked a straight suburban line until the heartbeat went stale.
![]()
The number: the direct link held to 1,250 feet before the heartbeat went stale and stuck — last-heard climbing to one minute, two, three, no fresh beacons. That's where I stopped.
![]()
1,250 ft is about 381 meters. Against the scorecard:
| Setting | Target | Result |
|---|---|---|
| Open line-of-sight | ≥ 1.0 km | — not yet walked — |
| Suburban / through houses | ≥ 300 m | 381 m — pass |
| Dense foliage / trail | ≥ 150 m | — not yet walked — |
The suburban bar — the one that matters most for a dog who gets out the front door — cleared, with margin.
Zero hops — why that number is honest
Here's the part I want to be precise about, because it's easy to accidentally cheat a range test. The app showed the base station at Hops Away: 0.
![]()
Zero hops means my handheld heard the collar firsthand — the radio itself reached 1,250 feet, with no neighbor's node quietly relaying the beacon and inflating the result. Last post I found 39 strangers' nodes already on the air around my block, so I deliberately moved my two nodes onto their own channel and filtered the view to direct-only. 1,250 feet is the floor: what one radio does, alone, with nobody helping.
And the mesh was already doing miles
![]()
Now lift the zero-hop filter off and the story changes scale. Those 39 nodes are a working mesh, and it is emphatically not local. Sort the list by distance and there's a station — Mesh_WYVERN, 33 miles (53 km) out, four hops away:
![]()
That's the network, not my radio — and my own collar rides it: on earlier runs across the public channel, with beacons free to relay hop to hop, the collar's position came back from about ten kilometers out. The reach my pet's beacon can borrow is measured in miles, not feet.
Put the whole span on one axis and the gap is the story — what my own radio did, against what the mesh carried for nothing:
My own radio's direct link tops out around 381 m. On the same axis, the neighborhood mesh reached North Point's tower 8.2 miles out and Mesh_WYVERN 33 miles away over four hops — about 140× my direct range, on nodes I didn't buy and didn't place. (The log even caught a node at 100 km; that one's almost certainly a misconfigured fixed position, so I'm not counting it.)
That's the moat I sketched in the PRD, and I'd assumed I'd have to bootstrap it one sale at a time. Instead it already exists, on my street, before I've shipped anything — the same network effect behind Find My and Sidewalk. Zero-hop clears the bar on its own; the mesh turns feet into miles for free. The direct link is what I can promise; the mesh is the upside I don't have to build.
And I haven't touched the antenna or the power
The 1,250 feet is conservative for a third reason: it's the stock rig. The base is running the little screw-on whip the Wio Tracker ships with — and the Meshtastic crowd is near-unanimous that it's the cheapest weak link to fix, with higher-gain antennas that clearly outrun it. It's a fiddly part to swap on this enclosure, but it's swappable. I also haven't pushed transmit power toward its ceiling.
Both are pure link budget, and link budget turns into range through one clean equation. Every decibel you add — antenna gain on either end, plus transmit power — multiplies range by:
range × 10 ^ ( ΔdB / (10 · n) )
where n is the path-loss exponent: about 3 in the suburbs (free space is 2; dense foliage pushes toward 4). Anchored at my measured 1,250 ft / 381 m:
| Link budget added | Multiplier (n = 3) | Direct range |
|---|---|---|
| +3 dB | 1.26× | ~1,580 ft |
| +6 dB | 1.58× | ~1,980 ft |
| +9 dB | 2.0× | ~2,500 ft |
| +12 dB | 2.5× | ~3,140 ft |
Where those decibels come from: trading the ~2 dBi stock whip for a ~5.8 dBi collinear is about +4 dB, and there's usually a few dB of transmit-power headroom on top — call it +8 to +10 dB on the base side, which the table turns into roughly double the direct link, a quarter-mile-plus. Two honest limits keep it from being free. US 915 MHz ISM has an EIRP cap, so you can't stack gain and power without bound. And the collar antenna can't grow — a cat won't wear a collinear — so the realistic gains live on the base side. The upside: the same decibels lift every mesh hop too, so a better base antenna stretches both the 1,250-ft direct floor and the ~10 km mesh ceiling.
I'll confirm it with a real walk once the new antenna's on. For now it's a model — but the model is just radio physics, and it's why range isn't the thing that worries me.
Messaging works too
The link isn't just position beacons. I sent a plain text from the collar mid-walk and it landed at the base station:
![]()
Small thing, big implication for an actual escape: I can ping the collar and get a fix back on demand, not just wait for the next scheduled beacon.
Presence — the heartbeat is the signal
The two walk photos above are also the presence test in miniature: at the start the heartbeat reads seconds ago (home / in range), at the limit it climbs and sticks (away / out of range). That climb-and-stick is exactly the home-vs-away signal the scorecard asks for — clean, with no false "still here" once the radio's truly gone.
The scorecard so far
| Criterion | Status |
|---|---|
| LoRa range — suburban | Pass (381 m, zero-hop) |
| LoRa range — open LOS / foliage | Not yet walked |
| Presence — home/away heartbeat | Looks clean; full 10-transition run to come |
| Messaging over the link | Works both directions |
| GPS — time-to-fix, accuracy | — to come — |
| Battery — active runtime | ~3.5 days at periodic beaconing — pass (continuous GPS + lit screen: ~10 h) |
| Battery — idle / presence runtime | — to come — |
| The Quark test (escape sim) | — to come — |
Where this leaves the build
The criterion that could have killed the project — range — cleared the suburban bar direct, and the neighborhood mesh already extends recovery to miles I didn't have to engineer. The build stays alive. And the other make-or-break, battery, is looking like a pass too: at periodic beaconing the collar projects to ~3.5 days on a charge — past the three-day bar — while a continuous-GPS, screen-lit stress run collapses it to ~10 hours, which is exactly why the production collar runs headless and duty-cycles the GPS. What's left is GPS fix quality, the idle/presence number, and a per-mode bench measurement to make the battery verdict airtight. Those are next.
The City Walk: Path Loss ~4 in Dense Urban — and the Cache That Wouldn't Die
Five walks in city density before the data was real, because the collar's firmware kept broadcasting a stale coordinate while the GPS chip was happily producing fresh fixes nobody could see. Factory reset was the only thing that cleared it — and then the clean walk gave the number I'd come for: path loss ~4 in dense urban row housing, about half the suburban range, exactly what the textbooks say.
Last range test was a clean suburban line — 1,250 feet, zero hops, scorecard cleared with margin. The city test should have been the easy follow-up. Same rig, denser environment, walk the block, get the number.
Five walks later I had no number.
Not because the link failed — it didn't — but because every position the collar broadcast was a stale value baked into firmware, while the GPS chip itself was producing fresh fixes nobody could see. Once that finally cleared, the actual walk took six minutes and gave me the headline I came for: path loss exponent of about 4 in dense urban row housing, roughly half the suburban range — exactly what dense-urban LoRa literature predicts.
The bug is the interesting half of the post.
The rig and the plan
Same two Wio Tracker L1s as the suburb test, US 915 MHz, LONG_FAST preset, 30 dBm TX, hop limit 3. One stays home as a base station with a manually-set fixed position. One walks with me as the collar, GPS on, broadcasting position every 30 seconds and a range-test seq packet every 15. A small Python listener on the base writes each received packet to a CSV with timestamp, sender lat/lng, receive SNR, and a great-circle distance computed against the base's known coord.
That last detail is important: by computing distance on the receive side from a coord the base knows is correct, the SNR-vs-distance curve isn't at the mercy of whatever the collar's firmware claims about itself. Or so I thought.
The first four walks — same stuck coordinate, every time
Every position broadcast from the collar carried the same lat/lng, to the bit: 40.2953856, -75.6246912. Every one. Across multiple walks, across reboots, across a full power-cycle. That coordinate is about 3 km from where the collar was actually standing — a stuck value from another part of the city the collar was never near.
When the same firmware emits the same precise float across hours of broadcasts and a power loss, the value isn't from a sensor. It's coming out of flash.
I had a working theory the moment I saw it: somewhere in the device's persistent storage, the Meshtastic firmware keeps a "last broadcast position" that the POSITION_APP plugin pulls from when emitting a packet, separate from whatever the GPS chip is currently reporting. If that cache gets corrupted with a stale value, the broadcast keeps shipping it even when the chip downstairs has a perfectly good live fix.
But the theory needed a level of access the tool in my pocket couldn't give me. The diagnostic moved up a ladder, one walk at a time.
Each tool got me one layer closer
Walk 1 — the Meshtastic Android app in my pocket. Confirmed the link was alive: collar in the node list, hop count, a position. Small screen, the position display sits behind a default-imprecise grid square, and I was walking — I noticed something was off about where the collar said it was, but I didn't slow down to look hard.
![]()
Walk 2 — same app, phone mirrored to the laptop side-by-side with a map. Bigger screen, the "wait, that's not where I am" feeling sharpened into a specific question. The Range Test module's config screen is also a clean tell of what the app can and can't do — there's a "Save .CSV in storage" toggle right there, greyed out, with "(ESP32 only)" next to it. The Wio Tracker L1 is nRF52840, so on-device CSV logging is off the table. That's the first time on this walk I knew I'd have to log the test from somewhere outside the app — still GUI, still abstracted from the firmware fields I actually needed to see.
![]()
Walk 3 — the Meshtastic Web Client over Bluetooth. Genuinely cool moment: a Chrome tab, a Web Bluetooth handshake, the device's config tree in a browser, no driver install or USB cable. Caveat worth knowing: one device at a time — Web Bluetooth pairs one peripheral per session, so I had to disconnect and reconnect to swap between collar and base. Useful enough to confirm the fixedPosition flag was already off on the collar and the broadcast still carried the stuck coord, which ruled out the obvious cause and made the bug feel deeper.
![]()
![]()
![]()
Walk 4 — the Meshtastic Python CLI (pip install meshtastic) over USB serial. This is the rung where the bug became visible. The CLI reads the device's raw protobuf, prints every field of the local node dump including cache values the GUI doesn't expose, and lets me query the base's view of the collar separately from the collar's view of itself. That's the comparison the smoking gun depends on; the GUI couldn't make it.
The progression isn't accidental. Each tool ships its own view of the device — the app shows you what an end user needs, the Web Client shows you what a tinkerer needs, the CLI shows you what a developer needs. When the bug lives in a field none of the user-shaped views expose, you have to keep climbing until you can see it. There's one more rung beyond the CLI in this story — but that one comes later, and it's about a different bug.
The smoking gun
The cleanest evidence: the collar's own meshtastic --info and the base's view of the collar disagreed.
| latitude | longitude | altitude | source | |
|---|---|---|---|---|
| Collar self-view (chip output) | 40.272606 | -75.6147148 | 24 m | LOC_INTERNAL (fresh GPS) |
| Base's view of collar (received broadcasts) | 40.2953856 | -75.6246912 | 24 m | LOC_INTERNAL (claimed fresh) |
Read that altitude column twice. The broadcasts were arriving with fresh altitude every time. They were marked LOC_INTERNAL, the firmware's flag for "this came from my GPS chip just now." Time field, fresh. Lat/lng — locked. Same value across two hours of walking and power events.
Whatever path the firmware uses to assemble an outgoing position packet, the altitude and time fields are getting pulled from the live GPS, and the lat/lng fields are getting pulled from a different place — somewhere persistent, somewhere that doesn't update when the chip does, and somewhere --remove-position and --reboot and full unpower don't reach.
What didn't clear it
The depressing list, in the order I tried them:
meshtastic --port COM5 --remove-position— clears the fixedPosition config flag and zerosfixedLat/fixedLng. Does not touch the broadcast cache. No effect on the stuck coord.--reboot— software reboot. Cache survives.- Full power-cycle (unplug USB, hold 30 s to drain capacitors, replug). Cache survives.
- Toggling
position.gps_mode,gps_update_interval,position_broadcast_smart_enabled. Cache survives. - Reading the cache via
--get positionto inspect it directly. The field isn't exposed in the public config surface — there's nothing to set.
I confirmed the GPS chip was healthy in the middle of all of this — it briefly reported a clean indoor fix at 40.2719211, -75.6144608 while I was watching, then proceeded to broadcast 40.2953856 thirty seconds later. The chip was telling the truth the whole time. The firmware just wouldn't repeat it.
What did clear it
meshtastic --port COM5 --factory-reset. Bluntest hammer in the box: it wipes the channel config, the owner name, the LoRa settings, the position settings, and the persistent broadcast cache. Cost is about ten minutes to rebuild — re-import the channel URL, set owner name, push LoRa region and preset and TX power and hop limit, push the GPS interval and the broadcast cadence. The configs live in YAML in this repo so the rebuild is a single --ch-set-url plus a chain of --set calls.
Except — and here's the fifth rung I promised earlier — in meshtastic Python CLI 2.7.8, --factory-reset itself is broken. The flag dispatches an AdminMessage whose factory_reset_config field expects an integer (1 for soft, 2 for hard), but the CLI assigns the Python True. The protobuf encoder catches it and aborts with Expected an int, got a boolean. Different bug, same shape of fix: drop one layer further down, from the CLI to the Python API the CLI is built on. Twelve lines: open a SerialInterface, build the admin message with factory_reset_config = 1, send via localNode._sendAdmin(). First try. The CLI flag was the wrong wrench for a fastener the API ships in the same toolbox; the device-side firmware is perfectly happy as soon as you hand it the integer it actually wanted.
After the reset, the broadcast cache was gone. The next --info from the base showed the collar reporting 40.2714555, -75.6146695 — a real coord, near my house, varying between consecutive readings the way GPS jitter is supposed to. End-to-end: fresh.
The walk that worked
About six minutes, down the block east and back, holding the collar at shoulder height. Eleven position broadcasts with fresh varying coords, plus the seq packets every fifteen seconds. Distance and SNR pulled from the live capture:
| Time | Distance | SNR |
|---|---|---|
| 19:57:27 | 377 m | +6.0 |
| 19:57:45 | 367 m | +5.5 |
| 19:58:05 | 268 m | +6.25 |
| 19:58:25 | 310 m | +6.0 |
| 19:58:45 | 303 m | +3.5 |
| 19:59:05 | 303 m | +5.5 |
| 20:00:05 | 363 m | +4.5 |
| 20:00:25 | 379 m | +5.75 |
| 20:01:05 | 379 m | +2.75 |
| 20:01:25 | 463 m | +3.75 |
| 20:01:46 | 486 m | −4.0 |
The seq packets after the last GPS broadcast kept arriving with SNR slipping further: −3.75, then −6.75, then −9.5 — about one decibel above where LONG_FAST loses lock. If I'd walked another thirty seconds I'd have caught the first dropped packet.
Path loss — the number
Plugging the closest and farthest fresh-GPS fixes into the standard log-distance model:
SNR(d₀) − SNR(d) = 10 · n · log₁₀(d / d₀)
With (d₀, SNR) = (268 m, +6.25 dB) and (d, SNR) = (486 m, −4.0 dB):
10.25 = 10 · n · log₁₀(486 / 268) = 10n · 0.2585
n ≈ 3.97 — call it 4.
That's not arbitrary. Path-loss-exponent tables for 900 MHz consistently put dense urban / row-house environments at 3.5 to 4.5, suburban at 3 to 3.5, free space at 2. My suburb walk in post 5 was calibrated against n = 3 and I extrapolated from a 1,250 ft direct number — that math holds for that environment. Dense urban density is roughly a decibel-per-meter harsher: brick walls, narrow row-house canyons, parked cars at antenna height, no clean line-of-sight past about a block.
Anchored at this walk's measured limit (SNR hitting LoRa's sensitivity floor at roughly 600 m given the tail slope), the direct-range ceiling in this density is somewhere around 600 to 900 m — half to two-thirds of the suburban result, exactly what the path-loss math forces. The mesh-relay extension I found before still applies — there are neighborhood Meshtastic nodes the collar can see from any given block, and a beacon that doesn't hit the base direct can still get there in two hops via somebody's roof.
Against the scorecard
The original scorecard split the LoRa range bar into three environments, and this walk fills in the dense-urban row:
| Criterion | Target | Result |
|---|---|---|
| LoRa range — open line-of-sight | ≥ 1.0 km | Not yet walked |
| LoRa range — suburban | ≥ 300 m | 381 m direct — pass (post 5) |
| LoRa range — dense urban / row housing | ≥ 300 m | ~500 m measured, ~600–900 m extrapolated — pass |
| LoRa range — dense foliage / trail | ≥ 150 m | Not yet walked |
Two of four environments cleared. The bar I cared about most for an escaped pet in a city — a block-plus of clean direct range through row houses — is met.
What I'd tell a team
Two things.
One. When a device's own self-view and the wire output disagree, the bug is almost always in the assembly path between sensor and packet, not the sensor itself. The altitude field being fresh while the lat/lng was stale was the find: it told me which fields lived where in firmware memory, and which path was broken. If both had been stale, I'd have spent another two hours suspecting the GPS chip.
Two. Persistent state survives more than people expect. --reboot is not a state reset. Power-cycle is not a state reset, for flash-backed values. Factory-reset is the only thing in the Meshtastic admin surface that wipes the whole flash-backed config, and in this case it was the only thing that mattered. The lesson for the production collar is to expose a deliberate "clear all persistent caches" operation in the device's own UI — not bury it behind a destructive reset that also wipes the channel and the owner. If users ever hit this in the field, they need a recovery path that doesn't unjoin them from their mesh.
What's next
Two open scorecard rows after tonight: GPS time-to-fix and accuracy (which I now actually trust the broadcasts to report, post-reset), and the Quark test — the escape simulation where I hand the collar to my Lab pup, open the back door, and time how fast the system says "the dog is gone" versus how long until it reads "the dog is back." That's the demo that matters.
The configs and the scripts from tonight are in the repo if you want to run the same walk: the collar YAML, the base YAML, the Python listener for live capture, and the twelve-line factory-reset workaround for the CLI bug. The CLI flag will get fixed in a release — the workaround is a today thing, not a forever thing.
Auditing the 95%: A Data-Driven Look at Amazon Sidewalk Before I Bet a SKU on It
Amazon claims Sidewalk reaches 95% of the US population. I'm about to bet a pet-tracker SKU on it, so I sat down with the math: bridge counts, urban-vs-rural distribution, geographic coverage, and the AWS IoT cost stack. Some of it holds up. Some of it is marketing dressed in a percent sign. Here's the audit, then the data path my collar's data would actually take through AWS — wired up before any hardware arrives so I'm not fumbling the integration on Day 1.
The cellular module for the Phase 2 build is sitting in a warehouse in China for three weeks while it makes its way to my workbench. Which gives me time to test: Amazon Sidewalk — does it actually reach the places I'd want a pet tracker to reach?
Amazon's marketing line is "approximately 95% of the US population is covered" — a number Amazon updates roughly annually as the bridge count grows. That's the number that justifies betting an entire product SKU on the Sidewalk tier, the way Tile did, the way Pebblebee did. So before I commit, I'm going to count.
What follows is the audit. Bridge math, coverage math, AWS-side cost math. Then a tangent on the AWS IoT integration path my collar's uplinks will actually take — because waiting for hardware is a good time to teeth-cut on the cloud side.
The 95% claim, dissected
The headline number traces to Amazon's Sidewalk launch press materials and the coverage.sidewalk.amazon public map. The claim is "approximately 95% of the US population is within Sidewalk coverage" — measured by counting Sidewalk-enabled Amazon devices (Echo, Ring) and computing which households fall within their effective sub-GHz / BLE radius.
Two words matter in that sentence, and they're the ones the marketing doesn't unpack. The phrase "approximately 95% of the US population" turns on that last word: population.
It's a population number, not a geographic number. And it's an eligibility number, not a reliability number. Both of those distinctions are the difference between "this will work for my customers" and "this will sometimes work for some of my customers."
Let's count the bridges
Public estimates put the US Echo install base at ~95–110 million devices as of mid-2026, with Ring devices adding another 12–15 million. Tile, since the Life360 acquisition completed and Sidewalk integration shipped, contributes another estimated 25–35 million bridge-eligible devices. Round numbers, but they bracket the order of magnitude.
| Source | Estimated US bridges (M) | Bridge type |
|---|---|---|
| Amazon Echo | 95–110 | sub-GHz LoRa + BLE + FSK |
| Ring (Doorbells + Floodlight Cams) | 12–15 | sub-GHz + BLE |
| Tile (post-Life360 / Sidewalk) | 25–35 (eligible — actual participation lower) | BLE only |
| Other certified third-party Sidewalk bridges | under 5 | mixed |
| Total potential bridges | 140–165 M |
That's a lot of bridges. Divided by ~129 million US households, that's roughly one bridge per household on average. Translated to area:
- Dense urban (NYC, SF, Chicago): thousands of bridges per square mile
- Suburban (Atlanta sprawl, San Jose, Houston suburbs): dozens per square mile
- Exurban / small-town: single digits per square mile
- Rural (most of MT, WY, NV, AK): approaching zero
The aggregate number is huge. The distribution is what kills the marketing claim for any specific use case.
Population covered is not land covered
The US is ~3.8 million square miles. The US is also ~333 million people. Those two numbers don't track each other.
Here's the actual Amazon Sidewalk coverage map for North America, screenshot today from coverage.sidewalk.amazon:
![]()
~80% of Americans live in urban areas, which occupy ~3% of the land. The other 20% — roughly 67 million people — are scattered across the remaining 97% of the country. So when Amazon says "95% of the population is covered":
- ~315 million people are inside coverage
- ~18 million people are outside coverage
- The 18 million sit in the 3.7 million square miles of low-density geography
That difference matters because dogs don't read census data. A dog that lives in San Francisco but escapes during a camping trip to the Sierra has just walked out of 95% coverage into 0% coverage, in one weekend. A retriever owned by a couple in suburban Pennsylvania near a state forest crosses the coverage edge every time it chases a deer.
A pet tracker that's "covered" in your driveway but "uncovered" at the dog park three blocks away ships exactly zero units to anyone who cares about finding their dog.
What coverage actually means
Even within the 95% population, "covered" is a more generous word than the marketing implies. There are four ways a bridge can be in your neighborhood but not bridging your packets:
Powered off. Echo Dots get unplugged. Spare bedrooms get cleaned out. Estimated unplug rate is somewhere between 10–20% of installed devices at any given time. The coverage map doesn't deduct for this.
Sidewalk opted-out. Amazon shipped Sidewalk with default-on in 2021, and gave users an explicit opt-out toggle. Estimated opt-out rate is in the 15–25% range of US Sidewalk-eligible households (no official number; press coverage and forum sampling). The coverage map shows potential, not actual.
WiFi-dead / backhaul-dead. Sidewalk uses your home WiFi to forward packets to Amazon's cloud. If the Echo has lost WiFi (router rebooted, ISP outage, the kid changed the password), the radio still works locally but the packets go nowhere. The collar gets an ACK. The owner gets nothing.
RF-shadowed. Bridges behind metal kitchen cabinets, basement Echos behind brick, Ring cameras under a metal awning — all sit in the device count but contribute little real coverage.
Multiply: even within the 95% population number, the effective real-world coverage is meaningfully lower. A reasonable internal estimate for "your packet actually reaches Amazon's cloud from this point on the map" is probably 60–75% of the headline number — which puts the real US household-level reliability at something like 57–71% of households when accounting for all four failure modes.
That's still huge. It's still better than any other crowdsourced network in the US. But it's not 95%. And the marketing doesn't tell you which 30–40% you're in until you ship.
So why am I still betting on it?
Because the alternative is worse, and the math at scale still works out.
Cellular comparison
Cellular LTE-M / NB-IoT (the comparison tier) gives you 99%+ population coverage and ~30% land-area coverage in the US — both better than Sidewalk on every axis. So why not just ship cellular?
| Tier | Coverage (pop.) | Cost/month (per device) | One-time hardware $ |
|---|---|---|---|
| Sidewalk | 57–95% (depending on what you count) | ~$2–7 (AWS) | $0 (rides Amazon infra) |
| LTE-M / NB-IoT | 99%+ | $5–10 (Hologram / 1NCE / Soracom) | $30 module + $1–10 SIM |
| LoRaWAN (Helium / TTN) | under 30% (US) | ~$1 (data credits) | $0 (rides community infra) |
Sidewalk's value isn't best-in-class coverage. It's "good enough" coverage at zero recurring cost to the manufacturer, in a country where the customer base skews urban/suburban (which is where Sidewalk is strongest). For a $79 collar with no monthly subscription as the product pitch, Sidewalk hits a price point cellular literally cannot, while covering the segment of the population that's most likely to buy a $79 collar in the first place.
What I'd ship
In the three-SKU plan, Sidewalk is the middle SKU — the "rides Amazon's infrastructure, no subscription, works for urban/suburban customers" middle tier between the LoRa-mesh community SKU (lowest cost, geographic gaps) and the Cellular SKU (highest cost, universal coverage). That's a defensible product matrix. Sidewalk doesn't have to be 95% reliable to be the right answer for the right customer. It just has to be honest about which customer.
AWS-IoT data path
When the cellular module finally lands and I start running real Sidewalk tests in three weeks, the cloud-side plumbing has to already be in place. Here's the path a single position broadcast takes from the collar to "the owner's app shows a pin on the map":
The breakdown of what I need to set up before any uplinks fly:
AWS account + region. Pick
us-east-1. AWS IoT Core for Sidewalk runs inus-east-1andus-west-2; everywhere else and you're routing across regions for no reason.Wireless Device Profile. Tells AWS what kind of device this is (Sidewalk-spec, US915 band, your manufacturer info). One profile, reusable across every device I ship.Destination. The routing rule that says "uplinks from this profile go to this IoT Topic." Single destination per product line.IAM Role. Lets IoT Wireless actually invoke whatever you've pointed the destination at. Annoying to debug if you skip it; the API silently swallows the failure.IoT Topic Rulewith a SQL filter. Routes the message to a Lambda function. SQL syntax looks likeSELECT *, topic() as topic FROM 'iot/wireless/sidewalk/+/uplink'. The+is the wildcard for device ID.Lambda. Reads the base64 payload, decodes the binary, extracts lat/lng/timestamp/SNR, writes to DynamoDB, fires an SNS notification if the dog is outside its geofence.
That's the minimum. Total AWS setup time, having done this once before: ~30 minutes. The first time you do it, ~3 hours, because every tutorial assumes you know the difference between an IoT rule (the SQL filter) and an IoT destination (the routing endpoint), and they're different things with similar names.
The cost math at pet-tracker scale
Cloud costs are what eventually decide whether your "no subscription" product story is actually sustainable, so let's count them too. AWS IoT Wireless billing is roughly:
- Per-device fee: $0.10/device/month (Sidewalk-registered)
- Per-message uplink: $0.0008 per message
- IoT Rules executions: $0.15 per million
- Lambda: ~$0.0000002 per ~10 ms invocation
- DynamoDB writes: ~$0.25 per million write capacity units
For a single pet collar at three reasonable ping rates:
| Ping interval | Messages / month | Cost / month / device |
|---|---|---|
| 1 / minute (active tracking) | ~43,200 | $34.66 — way too much |
| 1 / 5 minutes (battery-saving active) | ~8,640 | $6.96 — competitive with cellular |
| 1 / 15 minutes (idle / sleep) | ~2,880 | $2.40 — genuinely cheap |
| Hybrid: 1 / 15 min sleep + 1 / 1 min when accel-motion-triggered | ~6,000–10,000 | $5–8 — the right answer |
The hybrid mode is the answer. Sleep at 15-minute intervals when the accelerometer says "the dog isn't moving." Spike to 1-minute intervals when the 6-axis IMU detects motion (running, escaping, chasing). Average somewhere in the $5–8/device/month range. That's competitive with cellular but with no SIM to manage and no carrier relationship.
And for the customer experience: ~1 km of urban coverage at 1-minute resolution when the dog is moving, dropping to ~15-minute resolution when the dog is asleep on the couch. The accelerometer earns its place in the BoM all over again.
What I'm going to verify when hardware arrives
The audit-driven hypothesis list, things I'll test on the Sidewalk Starter Kit in the next few weeks:
- Does my house actually have a working Sidewalk bridge nearby? The coverage map says yes. The reality I'll measure by registering the dev kit and watching whether uplinks land.
- What's the actual roundtrip latency, packet-to-Lambda? Marketing implies "near real-time." I bet it's actually 5–30 seconds, and bursty.
- What's the daily packet loss rate? Single-device samples in dense urban can hit 5–10% over 24 hours. I'll find out for my specific location.
- Does coverage actually exist on the dog-walk route? Static map says yes; walking the route with a counting device will say definitively.
- What's the bridge-density gradient on a 5 km drive away from the house? Specifically, where does the link die?
Those are the five numbers that decide whether Sidewalk graduates from "interesting product tier" to "shippable product tier" in this build.
What's next
The cellular hardware lands in ~3 weeks. The AWS-side plumbing should be wired up before then. The Sidewalk Starter Kit lands in ~2 days and I can start running coverage tests immediately. Next post is the actual results, with numbers instead of estimates.
If you want to follow along: the bridge density in your own neighborhood is at coverage.sidewalk.amazon. Worth checking before you bet a SKU on the network.
The Battery Test: Runtime Is a Dial, Not a Number
Range could have killed the project; so could battery. The whole pitch is a tracker you don't charge constantly — so I let both nodes run for a day and read their own telemetry. The collar lasts three to four days beaconing, and ten hours with the GPS pinned on and the screen lit. Same board, same cell. Runtime turned out not to be a number at all.
The range test cleared its bar, which left the other criterion that could quietly kill this whole thing: battery. The pitch I wrote in the PRD was a tracker you don't charge constantly — and a tracker you charge every other day is a tracker that ends up dead in a drawer. So the scorecard set two hard numbers: ≥ 3 days of active beaconing, ≥ 2 weeks in idle/presence mode.
Reading the battery without a meter
I expected to babysit a multimeter for this. I didn't have to. Meshtastic logs Device Metrics on every node — battery %, voltage, channel utilization, air utilization — on an interval, and the app graphs them. So I just left both nodes running and read their own telemetry back. Two runs worth comparing: the range-walk day, where I had everything cranked, and a plain ~24-hour sit afterward.
The collar — the unit that actually has to last
![]()
After 22 hours 56 minutes of uptime the collar read 73% at 3.92 V, having started the day in the high-90s. That's roughly 1% per hour, which projects to 3.5–4 days on a charge — and at the 72-hour mark it still has about a quarter of the battery left. It clears the three-day bar with margin. Channel utilization sat at 7.1% and air at 3.7%, well under the levels where the mesh starts throttling, so it isn't burning the budget fighting for airtime either.
…and then the same collar lasted ten hours
Here's the honest counterweight. On the range walk, with the position interval forced to 60 seconds and the OLED lit the whole time, the same unit went from 97% to 66% in three hours — about 10% an hour, ~10 hours to empty. Same board. Same 3000 mAh cell. Eight times the drain, set entirely by how hard I was working it.
So the battery doesn't have a runtime. It has a runtime per mode:
Why the dial works
The radio sleeps almost all the time. What costs energy is the wake-ups — spinning up the GPS for a fix and keying the LoRa transmitter — plus, on this handheld, an OLED that has no business being on a collar. Average current is the sleep floor plus the area of those spikes. Stretch the interval, kill the screen, duty-cycle the GPS, and the average collapses toward the floor:
That's the whole reason the production collar is headless and will duty-cycle its GPS hard: it lives on the favorable end of that dial by design, not by luck.
A gut-check that the gauge isn't lying
Two independent readings, two voltages: the collar at 73% / 3.92 V, the base at 59% / 3.80 V. Both sit right where a lithium cell's discharge curve says they should — 3.9 V is high-70s, 3.8 V is high-50s. The firmware's percentage and the raw voltage agree, which means I can trust the number on the graph instead of second-guessing it.
The base barely moved
![]()
The base is a different animal — it mostly listens, and receiving is cheap next to transmitting. Its battery line is nearly flat across the day, which is idle-mode territory all on its own. And the base gets to cheat anyway: in real life it's plugged into the wall at home. The collar is the only node with a hard power budget.
What this still doesn't tell me
Two caveats keep me honest. First, these are the handheld Wio units, with screens — the production RAK collar drops the display draw entirely and can sleep the GPS harder, so it should beat everything here. Second, I've shown active clears three days, but I have not yet proven the two-week idle target; the base hints at it, but a hint isn't a measurement.
For that I need the spikes and the sleep floor in real milliamps, not a percentage that ticks once a minute. That's a job for a bench meter — a Nordic Power Profiler Kit II, which sources the board and measures current from nanoamps to amps. With it I can read the deep-sleep floor, integrate the energy in a single GPS-fix-and-transmit spike, and project each mode honestly instead of eyeballing a slope. It's on the way.
The verdict, against the scorecard
| Battery criterion | Target | Result |
|---|---|---|
| Active beaconing → runtime | ≥ 3 days | ~3.5–4 days — pass |
| Idle / presence → runtime | ≥ 2 weeks | Looks low-drain; bench measurement to confirm |
Active passes. Idle is pointing the right way but isn't nailed down. And the real lesson is the one in the title: the lever that makes or breaks runtime isn't a bigger battery — it's firmware, the duty cycle, the dial. Which is good news, because firmware is the cheapest thing on this whole project to change.
Next: the bench numbers, GPS fix quality, and the test I actually care about — putting it on Quark.