Welcome to uranashel

Chào mừng đến uranashel

I am ntan in the commit logs. I write the mobile clients at uranashel, plus most of the sensor and digital signal processing math that sits underneath them. We are a three-person studio based in Vietnam. I handle SwiftUI, Jetpack Compose, and the physics; atuan runs backend services, CI pipelines, and release trains; ktuyen owns QA, the garage test matrix, and the Vietnamese copy that keeps our App Store screenshots from reading like machine translation. No venture capital. No ad SDKs in the sensor apps. No pitch deck pretending we are building "the future of mobility." We ship tools we already use on real hardware before they reach the store.

Under the com.uranashel bundle family we publish five apps on the App Store; Stashio also ships on Google Play and as a Chrome extension. Wheria finds your parked car after GPS dies in a basement garage, fusing IMU step counting, barometer floor hints, and magnetometer heading into a walked path you can retrace instead of a lying blue dot. Estua synthesizes non-repeating ambient sleep audio at 48 kHz, entirely on the phone. Sonarish is a noise meter and spectrum analyzer with machine baseline comparison, built for anyone who hears their AC unit change pitch after three years. Phyzix exposes more than 100 live physics instruments and simulations, in English and Vietnamese. Stashio captures links, PDFs, and voice notes from the share sheet, then finds them again by keyword or by meaning. Each product grew from a specific annoyance, not a market trend slide.

The Tuesday that started everything

The origin story people ask about is not glamorous. On an ordinary Tuesday I parked on level B3 under Landmark 81 in Ho Chi Minh City, the tallest building in Vietnam, with a basement parking structure deep enough to swallow radio signals whole. I photographed the pillar number and took the elevator up. When I came back 40 minutes later, the map showed my car floating in the Saigon River, roughly 400 m east of reality, holding a steady 0 km/h midstream. I walked two full laps through identical concrete rows before I found it.

The failure mechanism is boring and well documented. L-band GPS signals attenuate hard in reinforced concrete, and the little energy that leaks underground arrives via multipath reflections that bias range estimates by tens of meters. Every mapping app knows this. Every mapping app drew the confident pin anyway, as if satellite geometry were a matter of opinion; I wrote up the full mechanism in why GPS lies. That evening I opened Xcode and a blank Kotlin project side by side. "Indoor navigation" would have looked impressive on a conference badge, but the badge had nothing to do with it. The phone in my pocket already carried a 3-axis accelerometer, a gyroscope, a barometer, and a magnetometer, which together form a complete inertial measurement unit. The sensors were there. Nobody was reading them honestly. The whole build is documented in building Wheria for indoors.

Four more annoyances, four more apps

Estua exists because my wife could hear the seam in an 8-minute rain loop at 2 in the morning. That is normal hearing, unfortunately. The human auditory system is absurdly good at detecting repetition, and a recorded loop fails the sleep test no matter how long you stretch it. So Estua synthesizes its audio at 48 kHz on the phone rather than playing recordings, and every night comes out statistically fresh. How the generators avoid repeating themselves has its own post.

Sonarish came from a residential AC unit that developed a 127 Hz hum, a harmonic of mains frequency and motor pole count, and from the discovery that no consumer sound meter could track that hum over weeks. We built the tracking ourselves: A-weighted levels checked against the NIOSH 8-hour exposure limit, a live spectrum, and a machine baseline you record while the machine is healthy so that later drift shows up as a number you can argue with. The same acoustic core turned out to measure breathing without contact; from an inaudible tone it resolves chest displacement down to 118 µm.

Phyzix came from watching students treat phones as TikTok machines while every one of those phones held a sensor laboratory nobody had opened. It exposes more than 100 of those instruments directly, bilingual, raw numbers visible. Stashio came from my own Notes app, which had accumulated 2,400 unsorted links across four years of garage testing and paper reading. The fix was a capture path through the share sheet plus retrieval by meaning. After three months of daily use, roughly 94% of my retrieval attempts succeed in under 10 s.

Where the physics sets the limits

The barometer is the cleanest example of physics deciding what a feature may claim. Hydrostatic equilibrium says ΔP = ρ·g·Δh. Put in air density of about 1.2 kg/m³ and g of 9.81 m/s², and 1 m of height change moves pressure by roughly 12 Pa. A typical parking floor is 3.2 m tall, so one floor is worth about 38 Pa. Meanwhile a phone barometer sitting still shows 0.3–1 Pa RMS of noise; our own bench note, 3 phones logged overnight on a shelf, landed at 0.4, 0.6, and 0.9 Pa RMS. Divide signal by noise and the honest claim writes itself: Wheria can tell B2 from B3 with a stated uncertainty band, and it must never pretend to centimeters. The full derivation, including what weather fronts do to it, is in the barometer math post.

Audio has an equally hard wall. At 48 kHz the render callback owes the hardware a buffer every few milliseconds, and that deadline turns into an absolute rule: no malloc, no locks on the audio thread, because a single allocation stall becomes an audible click at 3 am. Position math has a third wall. Double-integrating consumer accelerometer data sends your estimated position into another building within 30 s, which is why Wheria counts discrete steps and never integrates acceleration twice. Each wall killed at least one feature idea we were fond of. The details live in the audio thread post and the step detection post.

The magnetometer is the diva of the set. Rebar, EV chargers, and a car's own body add local fields on the order of the Earth field itself, so heading is only usable after hard-iron and soft-iron calibration, and the calibration goes stale when you walk past a transformer room. ktuyen's EV-bay test cases exist because of one specific charger in one specific mall basement. The saga is told in the compass calibration story, and how the Kalman filter weighs all these unreliable witnesses is in the Kalman post.

How the three of us divide the work

I write shared logic modules wherever the math must match exactly between platforms: Kalman filter updates, FFT window functions, step detector thresholds. Then I mirror the UI twice, once in SwiftUI and once in Compose. Two codebases with one math core sounds wasteful until you have debugged a filter that behaves differently on the two platforms because someone ported it twice; the reasoning is laid out in the two codebases post.

atuan keeps Stashio sync honest, manages the signing certificates, and tags iOS and Android releases with aligned version numbers so ktuyen never tests a mismatched pair. ktuyen maintains test plans segmented by garage architecture: open slab structures like Landmark, narrow spiral ramps in mall basements, flat outdoor lots, and the magnetometer nightmares around EV charging bays. She catches regressions of the shape "compass drift on iPhone 15 inside a steel garage but fine on Pixel 6" before users ever see them. We ship both platforms together by policy. Every feature lands twice, every bug lands twice, and parity is a release gate.

A deliberately quiet interface

Every uranashel product has a physics or signal-processing spine beneath the UI, and we keep the surface deliberately quiet about it: monochrome palettes, Nunito type, 12 px corner radius, nothing animated competing with a measurement. The test we design against is a parking garage at 11 pm, tired user, one glance. The Wheria home screen is a single card: the car, 124 m away, floor B2, pillar E9, a note that says near the elevator. The palette decision and the type decision each got a post, monochrome UI and Nunito on every pixel, because we argued about both for weeks.

What this blog covers

An App Store description cannot explain hydrostatic floor math. It cannot explain why malloc on the audio thread produces clicks at 3 am, or why double-integrated accelerometer data leaves the building within 30 s. This blog is the long-form companion to the store pages: how the apps actually work, what we tried and discarded, which ideas survived contact with real garages. Most posts come from the mobile and sensor side of the studio, because that is where I sit. When atuan has a release infrastructure story or ktuyen has a QA story worth telling, they will appear here too.

If you want to reproduce some of the measurements, the lab page lists our setups. If you have compass rants, garage logs, or questions about anything we build, write to uralab95@gmail.com. For the engineering philosophy everything above hangs on, keeping computation local to the device, start with why everything runs on your phone.

Trên commit log mình là ntan. Mình viết client mobile cho uranashel, kèm phần lớn toán cảm biến và xử lý tín hiệu số nằm bên dưới. Studio có ba người, đặt tại Việt Nam. Mình lo SwiftUI, Jetpack Compose và lớp vật lý. atuan chạy backend, pipeline CI và release train. ktuyen giữ QA, ma trận test hầm xe và bản tiếng Việt — nhờ cô ấy mà screenshot trên App Store không đọc như dịch máy. Không gọi vốn. Không SDK quảng cáo trong app cảm biến. Không pitch deck vẽ "tương lai di chuyển." Công cụ nào team cũng tự dùng trên máy thật trước khi đưa lên store.

Team phát hành năm app dưới họ bundle com.uranashel trên App Store; riêng Stashio có thêm bản Google Play và extension Chrome. Wheria tìm xe đỗ khi GPS chết trong hầm: ghép đếm bước IMU, gợi ý tầng từ áp kế và hướng từ từ kế thành một đường đi lần ngược được, thay cho chấm xanh nói dối. Estua tổng hợp âm thanh ngủ ambient không lặp ở 48 kHz, chạy hoàn toàn trên điện thoại. Sonarish là máy đo ồn kiêm phân tích phổ, có so sánh baseline máy móc, dành cho ai nghe điều hòa đổi pitch sau ba năm. Phyzix mở hơn 100 công cụ vật lý live và mô phỏng song ngữ Anh–Việt. Stashio hứng link, PDF và ghi âm từ share sheet rồi tìm lại theo từ khóa hoặc theo nghĩa. App nào cũng mọc từ một phiền toái cụ thể, không phải từ slide xu hướng thị trường.

Thứ Ba khởi đầu mọi thứ

Chuyện khởi đầu chẳng lộng lẫy gì. Một thứ Ba bình thường, mình đỗ xe tầng B3 dưới Landmark 81, TP.HCM — tòa nhà cao nhất Việt Nam, hầm đỗ sâu tới mức nuốt trọn sóng vô tuyến. Chụp ảnh số cột, đi thang máy lên. 40 phút sau quay lại, bản đồ đặt xe mình giữa sông Sài Gòn, lệch về phía đông chừng 400 m, đứng yên 0 km/h giữa dòng. Mình đi bộ hai vòng qua những hàng cột bê tông giống hệt nhau mới thấy xe.

Cơ chế hỏng thì nhàm và ai cũng biết. Sóng GPS băng L suy hao mạnh trong bê tông cốt thép; chút năng lượng lọt xuống lại đến qua phản xạ multipath, làm ước lượng khoảng cách lệch hàng chục mét. App bản đồ nào cũng biết điều đó. Vẫn vẽ pin tự tin, như thể hình học vệ tinh là chuyện cảm tính. Mình viết kỹ trong bài GPS nói dối. Tối đó mình mở Xcode cạnh một project Kotlin trống. "Indoor navigation" in lên huy hiệu hội nghị thì oách, nhưng lý do thật nằm trong túi quần: gia tốc kế ba trục, con quay, áp kế, từ kế — một bộ đo quán tính đầy đủ. Cảm biến có sẵn. Chỉ chưa ai đọc chúng một cách thật thà. Toàn bộ quá trình dựng app nằm ở bài dựng Wheria trong hầm.

Bốn phiền toái nữa, bốn app nữa

Estua ra đời vì vợ mình nghe được đường nối trong vòng lặp mưa 8 phút, lúc 2 giờ sáng. Tai người bình thường vốn thế. Hệ thính giác nhạy với lặp lại đến mức phi lý; loop ghi âm dài mấy cũng trượt bài test giấc ngủ. Nên Estua không phát file mà tổng hợp âm ở 48 kHz ngay trên máy, đêm nào cũng ra bản mới về mặt thống kê. Cách generator tránh tự lặp có bài riêng.

Sonarish đến từ cái điều hòa nhà mình phát tiếng ù 127 Hz — hài của tần số lưới điện nhân số cực motor — và từ chỗ không máy đo ồn consumer nào theo dõi nổi tiếng ù đó qua nhiều tuần. Team tự làm phần theo dõi: mức A-weighting đối chiếu định mức phơi nhiễm 8 giờ của NIOSH, phổ live, cùng baseline máy móc ghi lúc máy còn khỏe để sau này drift hiện ra thành con số cãi nhau được. Lõi âm học đó hóa ra còn đo được nhịp thở không cần chạm: từ một tone ngoài ngưỡng nghe, nó phân giải dịch chuyển lồng ngực tới 118 µm.

Phyzix đến từ cảnh học sinh coi điện thoại là máy TikTok, trong khi máy nào cũng chứa một phòng thí nghiệm cảm biến chưa ai mở. App phơi hơn 100 công cụ đo trực tiếp, song ngữ, số thô nhìn thấy được. Stashio thì đến từ chính Notes của mình: 2.400 link chưa phân loại dồn qua bốn năm test hầm và đọc paper. Cách chữa là một đường capture qua share sheet cộng tìm lại theo nghĩa. Sau ba tháng dùng hằng ngày, khoảng 94% lượt tìm lại thành công dưới 10 giây.

Vật lý đặt giới hạn ở đâu

Áp kế là ví dụ sạch nhất cho việc vật lý quyết định feature được phép hứa gì. Cân bằng thủy tĩnh cho ΔP = ρ·g·Δh. Thay mật độ không khí cỡ 1,2 kg/m³ và g 9,81 m/s², mỗi mét độ cao đổi áp chừng 12 Pa. Tầng hầm điển hình cao 3,2 m, tức một tầng đáng giá khoảng 38 Pa. Trong khi đó áp kế điện thoại nằm im nhiễu 0,3–1 Pa RMS; bench note của team, 3 máy log qua đêm trên kệ, ra 0,4, 0,6 và 0,9 Pa RMS. Chia tín hiệu cho nhiễu là ra lời hứa thật thà: Wheria phân biệt được B2 với B3, kèm band bất định, không bao giờ được giả vờ chính xác tới centimet. Toàn bộ phép tính, kể cả chuyện front thời tiết phá gì, nằm trong bài toán áp kế hầm xe.

Audio có bức tường cứng ngang thế. Ở 48 kHz, callback render nợ phần cứng một buffer sau vài mili giây; deadline đó thành luật tuyệt đối: cấm malloc, cấm lock trên audio thread, vì một lần cấp phát khựng là thành tiếng click nghe rõ lúc 3 giờ sáng. Toán vị trí có bức tường thứ ba. Tích phân hai lần dữ liệu gia tốc kế consumer đẩy vị trí ước lượng sang tòa nhà khác trong vòng 30 giây; vì thế Wheria đếm bước rời rạc chứ không tích phân gia tốc. Bức tường nào cũng từng giết ít nhất một ý tưởng feature team thích. Chi tiết ở bài audio threadbài phát hiện bước chân.

Từ kế là đứa đỏng đảnh nhất bộ. Cốt thép, trạm sạc EV và chính thân xe đều thêm từ trường cục bộ cỡ ngang từ trường Trái Đất, nên hướng chỉ dùng được sau khi hiệu chuẩn hard-iron lẫn soft-iron, mà bản hiệu chuẩn lại ôi thiu khi bạn đi ngang phòng biến áp. Mấy test case quanh trạm sạc EV của ktuyen sinh ra từ đúng một trụ sạc trong đúng một hầm trung tâm thương mại. Chuyện dài kể ở bài hiệu chuẩn la bàn; còn cách bộ lọc Kalman cân mấy nhân chứng kém tin cậy này nằm trong bài Kalman.

Ba người chia việc thế nào

Mình viết module logic dùng chung ở mọi chỗ toán buộc phải khớp tuyệt đối giữa hai nền tảng: cập nhật bộ lọc Kalman, hàm cửa sổ FFT, ngưỡng phát hiện bước. Xong mirror UI hai lần, một bằng SwiftUI, một bằng Compose. Hai codebase chung một lõi toán nghe phí, cho tới khi bạn từng debug một bộ lọc chạy khác nhau trên hai nền tảng vì bị port hai lần; lý do đầy đủ ở bài hai codebase.

atuan giữ sync Stashio chạy tử tế, quản certificate ký và tag release iOS lẫn Android theo số version khớp nhau, để ktuyen không bao giờ phải test một cặp lệch. ktuyen giữ test plan chia theo kiến trúc hầm: sàn mở kiểu Landmark, ramp xoắn hẹp dưới trung tâm thương mại, bãi phẳng ngoài trời và cơn ác mộng từ kế quanh trạm sạc EV. Regression dạng "la bàn trôi trên iPhone 15 trong hầm khung thép nhưng Pixel 6 vẫn ổn" bị cô ấy bắt trước khi tới tay user. Hai nền tảng ship cùng lúc, thành chính sách. Feature nào cũng hạ cánh hai lần, bug nào cũng hai lần và parity là cổng chặn release.

Giao diện cố ý trầm

Sản phẩm uranashel nào cũng có xương sống vật lý hoặc xử lý tín hiệu dưới UI, còn bề mặt được giữ trầm một cách cố ý: bảng màu monochrome, font Nunito, bo góc 12 px, không hiệu ứng nào tranh chỗ với phép đo. Bài test thiết kế của team là hầm xe lúc 11 giờ đêm, người dùng mệt, một cái liếc. Màn hình chính Wheria chỉ một card: xe cách 124 m, tầng B2, cột E9, kèm ghi chú gần thang máy. Chuyện chọn màu và chọn chữ đều có bài riêng — bài monochromebài Nunito — vì team cãi nhau về cả hai suốt mấy tuần.

Blog này viết về gì

Mô tả trên App Store không giảng nổi toán thủy tĩnh cho tầng hầm. Không giải thích được vì sao malloc trên audio thread sinh click lúc 3 giờ sáng, hay vì sao dữ liệu gia tốc tích phân hai lần rời khỏi tòa nhà trong 30 giây. Blog này là bản dài hơi đi kèm trang store: app chạy thật ra sao, team đã thử gì rồi vứt gì, ý tưởng nào sống sót khi gặp hầm xe thật. Phần lớn bài từ phía mobile và cảm biến, vì mình ngồi ở đó. Khi atuan có chuyện hạ tầng release hay ktuyen có chuyện QA đáng kể, hai người sẽ xuất hiện ở đây.

Muốn tự tái tạo vài phép đo, trang lab liệt kê setup của team. Có rant về la bàn, log hầm xe hay câu hỏi về bất cứ thứ gì team làm, gửi về uralab95@gmail.com. Còn triết lý kỹ thuật đỡ toàn bộ những thứ bên trên — giữ tính toán ngay trên máy — bắt đầu từ bài vì sao mọi thứ chạy trên điện thoại.