# Barometers and parking floor math > Pressure changes as you descend — the hydrostatic idea behind guessing B2 vs B3, and why weather and elevators complicate it. 2026-03-08 · 19 min read · wheria, physics · by ntan (ntan) for uranashel Canonical HTML: https://uranashel.com/blog/barometer-parking-math.html --- The barometer in your phone is a MEMS pressure sensor: a silicon membrane that flexes as air presses on it, read out as absolute atmospheric pressure in hectopascals. Sitting still on a desk, a good one shows noise of about 0.3–1 Pa RMS. That number turns interesting next to a second one. Near sea level, atmospheric pressure falls by roughly **12 Pa per meter** of altitude gained, and dividing the two says the sensor can resolve a few centimeters of height change on paper. In a live garage, with air handling and weather in the loop, call it 1 m of usable vertical resolution. That is enough to separate basement B2 from B3 in a structure built at 3.2 m per floor. Replacing horizontal positioning is a different job entirely, and GPS underground has problems of its own, covered in [why GPS lies](https://uranashel.com/blog/why-gps-lies.html). So Wheria treats the barometer as a floor hint, not a navigation solution. Tap Park and the app records the current pressure `P0` as a session baseline, optionally flagged if you are still outdoors at street level, where weather drift has had no time to accumulate. From then on, samples arrive at 1–10 Hz depending on how the OS schedules the sensor, and each becomes `ΔP = P − P0`. Two filters stand between that raw difference and anything the interface is willing to call a floor. ## Where 12 Pa per meter comes from Take a horizontal slab of still air: thickness `Δh`, cross-section `A`, density `ρ`. It weighs `ρ·A·Δh·g`. The only thing holding it up is the pressure difference between its bottom face and its top face, so `(P_bottom − P_top)·A = ρ·g·A·Δh`. The area cancels. What remains is the hydrostatic relation `ΔP = ρ·g·Δh`, or rearranged into the direction we actually use it, `Δh = ΔP/(ρg)`. Plug in numbers. Air density near sea level runs from about 1.15 kg/m³ on a 32 °C Saigon afternoon to 1.225 kg/m³ in the 15 °C standard atmosphere, and `g = 9.81 m/s²`, so `ρg` lands between 11.3 and 12.0 Pa per meter. Call it 12. One garage floor at 3.2 m is then worth about 37 Pa of pressure change. The temperature spread introduces a 3–5% scale error, which across a five-floor basement stays well under half a floor and disappears into the uncertainty band. The full barometric formula is exponential, `P(h) = P0·e^(−h/H)`, with scale height `H = RT/(Mg) ≈ 8,400 m`. Garages are not 8,400 m deep. Over the 16 m of a five-level basement, the linear approximation deviates from the exponential by about 0.1% of the signal, so we linearize without guilt. ## Two filters and what they keep The stationary noise floor of 0.3–1 Pa RMS turns out to be the smallest problem. Walking pumps the pressure field around the phone. HVAC fans in enclosed garages add step changes whenever a ventilation zone switches over. The pipeline therefore low-passes `ΔP` at about 0.05 Hz, treating anything faster than a 20-second period as mechanical noise, since a stair descent of a full floor does not complete in under 20 s. The slow end is worse. Weather fronts move ambient pressure by 200–400 Pa per day, which averages 8–17 Pa per hour and runs several times faster while a front actually passes. Left uncorrected, a cold front arriving during a long dinner reads as a full floor of descent that never happened. A high-pass around 0.001 Hz (periods longer than roughly 17 minutes) strips that trend out. Between the two cutoffs sits the passband where real transitions live: a one- or two-floor stair descent takes 40–90 s and comes through untouched. Long sessions still need care, because a baseline recorded at noon means little at 21:00. On the walk back, the estimator re-anchors: pressure observed at the street entrance becomes a fresh reference, and the depth profile learned on the way out is replayed against it. The stale `P0` survives only as a consistency check. ## Where the naive formula breaks Elevators are the classic failure. Shaft pressurization and cabin ventilation produce pressure transients faster than anything a ramp walk can generate, and the sensor has no idea it is inside an elevator — from where it sits, the weather just improved by 0.7 hPa in 15 seconds. Wheria survives this only because the IMU reports elevator-like motion and the filter down-weights barometer trust for that interval. Subtler offenders exist too. Parking with the engine or the climate control running shifts cabin pressure slightly before you step out, so a baseline can be born a few pascals wrong. iPhone and Pixel barometers carry different absolute offsets, sometimes approaching a full hectopascal, which is why we store per-session baselines at park time instead of attempting aviation-style QNH altimeter correction against a weather service. Architecture matters as well: the pressurized elevator shafts of a tower like Landmark 81 behave nothing like the open spiral ramps of a suburban mall, and ktuyen segments the barometer regression suite by garage architecture for exactly this reason rather than assuming one calibration fits all. ## The estimator in 20 lines Stripped of platform scaffolding, the whole floor estimator fits on one screen: ``` // every barometer sample, 1-10 Hz fun onSample(p: Pa, t: Time) { val raw = p - p0 // Pa relative to Park weather = ema(raw, tauSec = 900) // slow trend ~ 0.001 Hz high-pass val band = lowPass(raw - weather, fcHz = 0.05) val trust = when (imu.activity) { ELEVATOR -> 0.1 // transients lie; wait it out WALKING -> 1.0 STATIONARY -> 0.6 else -> 0.3 } val slab = structure.slabHeight ?: 3.2 // m, learned per garage val paPerFloor = RHO_G * slab // 11.6 x 3.2 ~ 37 Pa val floors = round(band / paPerFloor) val sigma = noiseSigma(band) / paPerFloor publish(FloorHint(floors, plusMinusOne = true, trust, sigma)) } ``` Slab height defaults to 3.2 m per level and is user-configurable, and if you have parked in the same structure before, the value learned from prior visits overrides the default. Everything else is bookkeeping around one division. ## What we display and what we refuse to claim The interface shows something like "Likely B3" with an explicit ±1 floor uncertainty band. A floor label you typed manually is never auto-overwritten. Barometer inference is probabilistic; a wrong automatic label erodes trust faster than no label at all, and the label you entered while standing next to your own car outranks any filter output. The regression numbers justify the humility. In Landmark-style open slab garages at 3.2 m floor height, median classification error is 0 floors when the user takes the stairs. Elevator-only transitions mislabel roughly 30% of the time once IMU activity context is withheld — nothing then distinguishes walking down a ramp from standing in a moving box. A rider going from B1 to B3 produces a pressure profile indistinguishable from someone who walked two flights of stairs in 10 s; the giveaway is the accelerometer, which reports the characteristic vibration and near-zero horizontal displacement of elevator travel. That fusion logic lives in the same pipeline as step odometry, described in [step detection and IMU odometry](https://uranashel.com/blog/step-detection-imu.html), and compass heading. It is also why ktuyen logs "stairs only" and "elevator only" test cases separately in her regression spreadsheet instead of averaging them into one flattering number. ## Bench notes Method: three phones (iPhone 13, Pixel 7, Galaxy A54) face up on a shelf, screens off, logging pressure at 1 Hz for 48 hours, detrended hourly against a straight line. Detrended noise came out at 0.35, 0.6 and 0.9 Pa RMS respectively, comfortably inside the 0.3–1 Pa RMS band we design around. All three tracked the same synoptic weather curve. Absolute offsets between units, though, spanned 40–110 Pa — and that figure is the entire argument for per-session baselines, because 110 Pa is 3 floors of phantom altitude the moment you compare absolute readings across devices. Two shorter notes from the same week. A stairwell door slammed 2 m away spikes the trace by 25–30 Pa for under 1 s; the 0.05 Hz low-pass removes it completely. Closing a car door with the windows up produces a 60–90 Pa transient inside the cabin, which is one more reason the baseline flag prefers a reading taken after you have stepped out. ## Try the math yourself The [Lab page](https://uranashel.com/lab.html) has a garage scroll demo that visualizes the hydrostatic relationship in real time; drag through the floors and watch `ΔP` track depth. The barometer is one input among several — the full stack is described in [indoor navigation when GPS dies](https://uranashel.com/blog/building-wheria-indoor.html), and the floor hint enters the Extended Kalman Filter alongside step odometry and compass heading, as covered in [Kalman filters for parking](https://uranashel.com/blog/kalman-filter-parking.html). For the raw instrument with none of our filtering opinions on top, [Phyzix](https://uranashel.com/apps/phyzix.html) exposes barometer data in SI units in its environment instruments section. Watch it for 10 minutes and you will see everything this post described: the noise floor, the weather drift and, when somebody slams a door, the spike. --- uranashel · [Home](https://uranashel.com/) · [Apps](https://uranashel.com/apps.html) · [Lab](https://uranashel.com/lab.html) · [About](https://uranashel.com/about.html) · [Blog](https://uranashel.com/blog/) · [Developers](https://uranashel.com/developers/) · [API docs](https://uranashel.com/docs/) · [Privacy](https://uranashel.com/privacy.html) Machine-readable: [llms.txt](https://uranashel.com/llms.txt) · [sitemap.xml](https://uranashel.com/sitemap.xml) · [openapi.json](https://uranashel.com/openapi.json) · [API](https://uranashel.com/api/v1/)