Wheria's most-requested feature sounds small on the store page: the app notices you parked without you opening it. Behind that sentence sits a pipeline that has to sense something during every hour your phone rides in a pocket. 16 hours a day, roughly. A parking app that flattens a battery gets uninstalled by Thursday, and the OS battery-usage screen is the exact mechanism people use to decide whom to blame. So before we committed to the indoor pipeline, we measured what continuous sensing actually costs on hardware we own.
These are the bench notes from that exercise. Method in one line: 4 phones (iPhone 13, iPhone 15, Pixel 6, Galaxy A54), airplane mode, screen off, one sensor configuration per night, battery percentage logged every 5 min, 3 nights per configuration plus one 24 h weekend run each, about 5 weeks of nights in total.
A shelf of phones, screens off
The rig is embarrassingly simple. Four phones lie face down on a wooden shelf. Airplane mode kills every radio. A small logger app samples the reported battery percentage every 5 min and appends a line to a CSV. Each night runs one configuration: baseline first, then one sensor added at a fixed rate. In the morning the phones charge back to 80% and the next configuration starts.
Two honesty notes on the instrument. Battery percentage is a fuel-gauge estimate with a granularity of 1%. Over an 8.5 h night, one step of 1% works out to 0.12%/h, which on a 4,400 mAh pack corresponds to roughly 5 mA. Anything cheaper than 5 mA is invisible in a single night, so the sub-mA figures below come from averaging 3 nights and from the 24 h runs. Second, fuel gauges go nonlinear near full charge, so every run starts at 80% and we trim the first 30 min. Baseline drain with everything off came out at 0.9–2.1% per night across the fleet, Pixel 6 the thirstiest. Every figure that follows is a delta above that phone's own baseline.
What each sensor drains
These are system-level deltas: chip plus driver plus the CPU wakeups needed to deliver samples to an ordinary app process. Datasheet figures for the bare chips run 10–100× lower, and that gap is most of what this post is about.
- Accelerometer, 50 Hz: 1.4–2.6 mA depending on phone. At 100 Hz: 1.9–3.1 mA. Single-digit mA-class, and almost none of it is the chip — the transducer itself draws about 20 µA. The rest is delivery.
- Gyroscope, added on top: +2–4 mA. A MEMS gyro keeps a proof mass vibrating continuously, so the physics bills you before the software does.
- Magnetometer, 25 Hz: 0.5–0.9 mA. Cheap.
- Barometer, 1 Hz: below the 5 mA single-night floor and below the averaged floor too. Datasheet-class µA. Effectively free, which is why Wheria can afford continuous floor math at all.
- GPS, continuous fix: 27–38 mA. GNSS is receive-only, so airplane mode leaves it alive; those phones spent their nights on a window sill for sky view.
- Audio, 48 kHz capture plus our FFT chain: 9–14 mA. Moderate. Fine for Estua on a bedside charger, wrong for anything that runs unplugged all day.
From milliamps to percent per hour
The conversion is one division. A battery of capacity C mAh under a constant drain of I mA loses 100·I/C percent per hour. Minimal fix preserving all downstream arithmetic: replace with "For a nominal 4,400 mAh pack that is I/44". (Fuller alternative: change to 5,000 mAh / I/50 and cascade: ~6 mA single-night floor, 0.02%/h per mA, 0.48%/day, GPS 14%/day, audio 5.8%/day, and 'division by 50' in the closing paragraph.): each continuous milliamp costs 0.023%/h, or 0.55% per day. Strictly the budget should be kept in energy, mWh against pack voltage, because sensor rails and the battery sit at different potentials; at phone scale the mAh shortcut lands inside the fuel gauge's own noise, so we use it.
Now the list above turns into consequences. A 50 Hz accelerometer at 2 mA is 1.1% per day, real but invisible next to a screen. Continuous GPS at 30 mA is 0.68%/h, 16% per day, and that is the app the battery screen names by Wednesday. The audio pipeline at 12 mA sits between them at 6.5% per day. The whole discipline of background sensing is arranging never to pay the expensive rows continuously.
Duty cycling: sense only when something happens
Every cost above is a rate, so total drain is rate times time switched on. The duty factor is where the budget is won. A phone spends most of its day stationary on a desk, and parking happens twice on a commute day. Wheria therefore runs as a ladder of states, each rung more expensive and shorter-lived than the one below:
IDLE: // coprocessor subscriptions only, ~0 extra
on significant-motion wakeup -> CLASSIFY
CLASSIFY: // accel 50 Hz, 20 s burst
activity == automotive -> DRIVING
else -> IDLE
DRIVING: // barometer 1 Hz + activity transitions
automotive->walking within 90 s -> PARK_CHECK
PARK_CHECK: // full pipeline, capped at 3 min
step detector + heading + floor delta
plausible park: save spot -> IDLE
On a two-commute day the full pipeline runs 8–15 min in total. Call it 0.2 h at 6 mA: 1.2 mAh, under 0.03% of the pack. The CLASSIFY bursts add about as much again. Nearly the entire background day is spent in IDLE, and IDLE has to cost almost exactly nothing. Which brings us to the coprocessor.
Living inside the motion coprocessor
Phones already run a small always-on core — Apple's motion coprocessor, the sensor hubs in Qualcomm and Exynos parts — that counts steps and classifies activity for the OS around the clock, on power the OS has already decided to spend. Subscribing to its outputs costs the subscriber approximately nothing, because the marginal work is a callback. Auto parking detection therefore has to be built out of exactly those primitives: significant-motion wakeups, activity-transition events (automotive to walking is the one we care about) and significant-location change. Our own math runs only inside the short windows those events open.
The alternative is streaming 50 Hz accelerometer data to our own process all day and running a smarter detector than the OS provides. We tried that prototype for a week. It cost 2.3 mA around the clock on the Pixel 6, about 1.3% per day before doing anything useful, and it kept the application processor out of deep sleep, which the OS punishes with throttled delivery and eventually a spot on the battery screen. Coprocessor events are coarser and arrive 30–90 s late. For parking that is acceptable, because the signal we need next is the walk away from the car, and the step detector gets its data during those minutes anyway.
Batching and sensor-hub delivery
Inside the windows where we do stream, delivery style matters as much as sample rate. Sensor chips write into a hardware FIFO in the hub, and the OS can hand samples over one at a time or in batches. At 100 Hz delivered per-sample, the application processor takes 100 wakeups per second and never reaches its deep idle states. Batched at 4 s intervals, 400 samples per delivery, the same stream measured 0.4 mA against 2.2 mA per-sample on the Pixel 6. Same data, same math, 5× cheaper, at the price of 4 s of latency.
So the background states run fully batched, and the live navigation screen runs unbatched, where a 4 s lag would show up in the heading arrow. That mode does not need the frugality anyway: the display itself draws on the order of 80–120 mA, two orders of magnitude above the sensors it is showing. Once the screen is on, sensor power is a rounding error.
The budget we ship
Wheria's background target is 1.5% per day. The fleet measures 0.7–1.3% depending on how much driving a day contains. Getting there meant cutting things that worked. Continuous magnetometer logging for garage fingerprinting cost 0.7 mA around the clock and was cut in favor of sampling only inside PARK_CHECK. Periodic GPS polling to catch highway driving was replaced entirely by activity transitions, which removed the single largest line item. The 100 Hz always-on stream became 50 Hz bursts after the detector proved insensitive to the difference.
None of this arithmetic is deep. It is division by 44 applied ruthlessly, plus a refusal to pay any continuous cost the coprocessor already pays on our behalf. The result is a feature on the Wheria page that sounds like magic and is mostly bookkeeping: the app knows where you parked, and the battery screen never learns our name.
Tính năng được xin nhiều nhất của Wheria nghe rất nhỏ trên store: app tự biết bạn vừa đỗ xe, không cần mở lên. Đằng sau câu đó là một pipeline phải cảm nhận gì đó suốt 16 tiếng điện thoại nằm trong túi mỗi ngày. App đỗ xe mà làm tụt pin thì đến thứ Năm là bị gỡ. Màn hình thống kê pin của OS chính là nơi user quyết định đổ lỗi cho ai. Nên trước khi cam kết làm pipeline indoor, team đo xem sensing liên tục thật sự tốn bao nhiêu trên máy thật.
Đây là ghi chú bench của đợt đó. Phương pháp gói trong một dòng: 4 máy (iPhone 13, iPhone 15, Pixel 6, Galaxy A54), airplane mode, tắt màn hình, mỗi đêm một config sensor, log phần trăm pin mỗi 5 phút, 3 đêm mỗi config cộng một lần chạy 24 h cuối tuần, cộng lại chừng 5 tuần toàn đo đêm.
Một kệ điện thoại, màn hình tắt
Cái rig đơn giản đến ngượng. Bốn máy nằm úp trên kệ gỗ. Airplane mode cắt sạch radio. Một app logger nhỏ đọc phần trăm pin mỗi 5 phút, ghi thêm một dòng vào CSV. Mỗi đêm chạy một config: baseline trước, rồi thêm từng sensor ở tần số cố định. Sáng hôm sau sạc lại về 80% rồi chạy config kế.
Hai ghi chú thành thật về dụng cụ đo. Phần trăm pin là ước lượng từ fuel gauge, bước nhảy 1%. Qua một đêm 8,5 h, một bước 1% tương đương 0,12%/h, tức cỡ 5 mA trên pin 4.400 mAh. Cái gì rẻ hơn 5 mA thì một đêm không nhìn thấy, nên các con số dưới mA phía dưới đến từ trung bình 3 đêm và các lần chạy 24 h. Thứ hai, fuel gauge phi tuyến khi pin gần đầy, nên mọi lần chạy bắt đầu từ 80% và cắt bỏ 30 phút đầu. Baseline khi tắt hết mọi thứ: 0,9–2,1% mỗi đêm tùy máy, Pixel 6 khát nhất. Mọi con số phía sau đều là phần chênh so với baseline của chính máy đó.
Từng sensor ngốn bao nhiêu
Số ở đây là mức chênh ở tầm hệ thống: chip cộng driver cộng các lần wakeup CPU để đưa sample về process của app. Số trong datasheet của chip trần thấp hơn 10–100 lần. Khoảng cách đó gần như là toàn bộ chủ đề bài này.
- Gia tốc kế, 50 Hz: 1,4–2,6 mA tùy máy. Lên 100 Hz: 1,9–3,1 mA. Cỡ vài mA mà gần như không phải do chip — phần cảm biến chỉ ăn cỡ 20 µA. Còn lại là chi phí giao hàng.
- Con quay, bật thêm: +2–4 mA. Gyro MEMS phải giữ một khối rung liên tục, vật lý tự nó tính tiền trước cả phần mềm.
- Từ kế, 25 Hz: 0,5–0,9 mA. Rẻ.
- Áp kế, 1 Hz: dưới sàn đo 5 mA của một đêm và dưới cả sàn sau khi lấy trung bình. Cỡ µA theo datasheet. Coi như miễn phí — nhờ vậy Wheria mới dám chạy toán tầng liên tục.
- GPS, fix liên tục: 27–38 mA. GNSS chỉ thu không phát nên airplane mode không tắt nó; mấy máy chạy config này nằm bên bệ cửa sổ để thấy trời.
- Audio, thu 48 kHz cộng chuỗi FFT: 9–14 mA. Tầm trung. Ổn cho Estua cắm sạc đầu giường, sai cho thứ chạy cả ngày không cắm điện.
Đổi từ mA sang phần trăm mỗi giờ
Phép đổi chỉ là một phép chia. Pin dung lượng C mAh chịu dòng I mA thì mất 100·I/C phần trăm mỗi giờ. Với một pin danh định 4.400 mAh, tức I/44: mỗi mA chạy liên tục tốn 0,023%/h, hay 0,55% một ngày. Chặt chẽ thì phải lập định mức bằng năng lượng mWh so với điện áp pin, vì rail của sensor và pin nằm ở thế khác nhau. Ở quy mô điện thoại, cách tính tắt bằng mAh lệch ít hơn nhiễu của chính fuel gauge, nên cứ dùng.
Giờ danh sách trên biến thành hệ quả. Gia tốc kế 50 Hz ở 2 mA là 1,1% một ngày, có thật nhưng vô hình khi đứng cạnh màn hình. GPS liên tục 30 mA là 0,68%/h, 16% một ngày và đó là app bị màn hình pin réo tên vào thứ Tư. Pipeline audio 12 mA nằm giữa, 6,5% một ngày. Toàn bộ kỷ luật của sensing nền là sắp xếp sao cho không bao giờ trả mấy dòng đắt kia theo kiểu liên tục.
Duty cycle: chỉ đo khi có chuyện
Mọi chi phí ở trên là tốc độ, nên tổng hao bằng tốc độ nhân thời gian bật. Định mức thắng hay thua nằm ở hệ số duty. Điện thoại nằm yên trên bàn phần lớn ngày; chuyện đỗ xe xảy ra hai lần trong một ngày đi làm. Wheria vì thế chạy như một cái thang trạng thái, bậc trên đắt hơn và sống ngắn hơn bậc dưới:
IDLE: // chỉ subscribe coprocessor, tốn thêm ~0
wakeup significant-motion -> CLASSIFY
CLASSIFY: // accel 50 Hz, burst 20 s
activity == automotive -> DRIVING
không phải -> IDLE
DRIVING: // áp kế 1 Hz + sự kiện chuyển activity
automotive->walking trong 90 s -> PARK_CHECK
PARK_CHECK: // pipeline đầy đủ, tối đa 3 phút
đếm bước + heading + chênh tầng
ra chỗ đỗ hợp lý: lưu lại -> IDLE
Một ngày hai lượt đi về, pipeline đầy đủ chạy tổng 8–15 phút. Tính tròn 0,2 h ở 6 mA: 1,2 mAh, dưới 0,03% pin. Các burst CLASSIFY thêm chừng đó nữa. Gần trọn ngày nền nằm ở IDLE. Mà IDLE thì phải tốn gần đúng bằng 0. Chỗ này dẫn thẳng tới coprocessor.
Sống trong định mức của coprocessor
Điện thoại nào cũng có sẵn một lõi nhỏ chạy thường trực — motion coprocessor của Apple, sensor hub trong chip Qualcomm và Exynos — đếm bước và phân loại hoạt động cho chính OS suốt ngày đêm, bằng phần điện OS đã quyết định tiêu sẵn. Đăng ký nhận output của nó gần như không tốn thêm gì, vì phần việc biên chỉ là một callback. Tính năng tự phát hiện đỗ xe do đó phải được lắp từ đúng mấy viên gạch này: wakeup significant-motion, sự kiện chuyển hoạt động (automotive sang walking là cái cần) và significant-location change. Toán riêng của team chỉ chạy trong các cửa sổ ngắn mà mấy sự kiện đó mở ra.
Phương án kia là stream gia tốc kế 50 Hz về process của mình cả ngày, chạy detector thông minh hơn cái OS cho sẵn. Team thử prototype đúng một tuần. Tốn 2,3 mA suốt ngày đêm trên Pixel 6, cỡ 1,3% mỗi ngày trước khi làm được gì hữu ích, lại còn giữ AP không vào được deep sleep. OS phạt chuyện đó bằng cách bóp delivery rồi cuối cùng bêu tên trên màn hình pin. Sự kiện từ coprocessor thô hơn và đến muộn 30–90 s. Với đỗ xe thì chấp nhận được: tín hiệu cần tiếp theo là đoạn đi bộ rời xe mà bộ đếm bước lấy dữ liệu trong chính mấy phút đó.
Batch và sensor hub
Trong các cửa sổ có stream thật, cách giao sample quan trọng ngang tần số lấy mẫu. Chip sensor ghi vào FIFO phần cứng trong hub; OS có thể đưa từng sample một hoặc gom theo batch. Ở 100 Hz giao lẻ từng cái, AP nhận 100 wakeup mỗi giây và không bao giờ xuống nổi các mức idle sâu. Batch mỗi 4 s, tức 400 sample một lần giao, cùng luồng đó đo được 0,4 mA so với 2,2 mA khi giao lẻ, trên Pixel 6. Cùng dữ liệu, cùng phép toán, rẻ hơn 5 lần, đổi lại trễ 4 s.
Nên các trạng thái nền chạy batch toàn bộ, còn màn điều hướng trực tiếp thì không batch, vì trễ 4 s sẽ lộ ngay trên mũi tên chỉ hướng. Chế độ đó cũng chẳng cần tằn tiện: riêng màn hình đã ăn cỡ 80–120 mA, gấp hai bậc so với đám sensor nó đang hiển thị. Màn hình bật rồi thì điện của sensor chỉ là số lẻ làm tròn.
Định mức mà team ship
Mục tiêu nền của Wheria là 1,5% mỗi ngày. Đo trên cả dàn máy ra 0,7–1,3% tùy hôm đó lái nhiều hay ít. Để tới đó phải cắt cả những thứ chạy tốt. Log từ kế liên tục để fingerprint hầm xe tốn 0,7 mA suốt ngày đêm, bị cắt, chỉ còn lấy mẫu trong PARK_CHECK. Poll GPS định kỳ để bắt cảnh chạy cao tốc bị thay hẳn bằng sự kiện chuyển hoạt động, xóa luôn khoản chi lớn nhất. Stream 100 Hz thường trực hạ xuống burst 50 Hz sau khi detector chứng minh không phân biệt nổi hai mức.
Không phép tính nào ở đây sâu cả. Chỉ là phép chia cho 44 áp dụng không khoan nhượng, cộng thái độ nhất quyết không trả bất kỳ chi phí liên tục nào mà coprocessor đã trả thay. Kết quả là một tính năng trên trang Wheria nghe như phép màu mà thực ra phần lớn là sổ sách: app biết xe đỗ ở đâu, còn màn hình pin thì không bao giờ biết tên tụi mình.