Last month I did something a little crazy: I fully reverse-engineered the assisted-GPS data pipeline of my sports watch, then used publicly available satellite data to “manufacture” my own file in the same format, stood up my own server, and had my own app deliver ephemeris data to the watch in place of the official one.
After more than two weeks of tinkering, the watch still gets a fix in seconds without the official app. This post explains the whole process, and along the way gives a primer on what that “satellite positioning” we talk about every day actually is.
Disclaimer up front: this article is for learning and exchange only; please do not use it for any commercial or improper purpose. The format analysis described here is purely a personal research record.

1. What Satellites Are Actually Up There?
When we say “GPS” in everyday speech, it’s really just a habitual term. The Americans aren’t the only ones with satellite navigation systems up there; today there are six major global navigation satellite systems (GNSS) worldwide — which is also the “six constellations” vendors advertise:
| System | Country/Region | Abbreviation | Number of Satellites |
|---|---|---|---|
| GPS | United States | GPS | around 31 |
| BeiDou | China | BDS | 30+ (third-generation system) |
| GLONASS | Russia | GLONASS | 24+ |
| Galileo | European Union | Galileo | 28+ |
| QZSS | Japan | QZSS | 5 (regional augmentation) |
| NavIC | India | IRNSS | 7 (regional) |
The first four are global systems; the last two are regional augmentation systems (mainly covering Asia-Pacific). So-called “six-constellation positioning” means the receiver can pick up satellites from these systems simultaneously and compute a position together — with more “roads” overhead, it’s naturally easier to get a fix between high-rises or in the woods.
My watch receives five of these systems (GPS, BeiDou, GLONASS, Galileo, QZSS), and BeiDou is the one I use most day to day at home.
2. What Does “Dual-Frequency” Mean?
Each navigation satellite broadcasts signals on several different frequencies at once — for example GPS’s L1 (1575.42 MHz) and L5 (1176.45 MHz).
Traveling from twenty thousand kilometers up to the ground, the signal has to pass through the ionosphere. The ionosphere slows the signal’s propagation and introduces delay; this error can reach tens of meters and is a major source of positioning error. And ionospheric delay depends on frequency: different frequencies, different delays. If the receiver can pick up two frequencies at once, it can use that difference to compute and remove the ionospheric error directly, lifting accuracy from “meter-level” to “sub-meter-level.”
That’s the point of “dual-frequency.” Watches are limited by size and power consumption, so most are still single-frequency; among phones, dual-frequency has become basically standard in high-end models over the past few years.
3. What Is Positioning Actually Calculating?
A satellite signal carries its own orbital parameters and time. The receiver knows “where the satellite is right now” (ephemeris) and “how long the signal traveled” (pseudorange); with three or more satellites you can set up equations and solve for your own position on Earth — essentially ranging by intersection.
Two key concepts here:
Ephemeris: a “recent file” precisely describing each satellite’s current orbital parameters, valid for only about 2 hours. Collecting the ephemeris for one satellite usually takes 30 seconds (each satellite only broadcasts its own data at specific moments).
Almanac: a “rough group photo” of all satellites, accurate to a few kilometers, but valid for months and collected in just 12 minutes. Its purpose is to assist satellite acquisition — letting the receiver roughly know “which directions in the sky have satellites.”
From this comes positioning’s most famous metric, TTFF (Time To First Fix):
- Cold start: the receiver has nothing and must collect the almanac first, then the ephemeris — possibly 1-2 minutes or even longer.
- Warm start: with ephemeris stored (within 2 hours), a position appears within seconds.
- Assisted positioning (A-GPS): this is where every vendor pushes hard — downloading the “ephemeris” over the phone network and pushing it straight to the watch, so the watch “knows” where the satellites are at power-on, skipping the 30 seconds of over-the-air reception and compressing a cold start to under 10 seconds.
A watch has no SIM card, so it depends on phone relay: the watch tells its companion app over Bluetooth “I want ephemeris,” the app downloads and decodes it from the cloud, then sends it back to the watch in Bluetooth fragments.
4. The Hard Part Isn’t “Downloading,” It’s “Predicting”
For ephemeris valid 2 hours, the server can just fetch and send on demand. But the real problem is: the watch isn’t connected to the phone every day.
Mountaineering, trail running, cycling — you’re often out for three to five days at a stretch, and in between the phone dies, has no network, or you simply don’t want to carry it. This requires the assisted data to be able to “see the future” — where the satellites will be and what the clock offsets will become over those offline days all has to be computed in advance and packaged for the watch.
That’s why every major vendor’s assisted data isn’t raw ephemeris but predicted orbits: taking the forecast ephemeris for the coming days and fitting it into a time-position curve. My watch’s scheme slices the next 3 days into 36 windows (one every 2 hours), and within each window uses a set of mathematical coefficients — which I reverse-engineered as a 20th-order Chebyshev polynomial fit across the three ECEF coordinate directions, at 1-millimeter resolution — to describe each satellite’s trajectory. After receiving this, the watch can “fix in seconds” for the next 72 hours. That’s the secret of a watch still getting a fast fix after 3 days offline — the data was “drawn in advance” before you set out.
5. Why I Insisted on Doing It Myself
The origin of the story is a little absurd: this watch’s offline positioning grew ever more dependent on the official app syncing daily, and once it hadn’t synced for a few days, positioning became maddeningly slow (I measured a cold start taking up to 12 minutes).
What I wanted was simple: never depend on the official app again, and always have fresh ephemeris on the watch.
After analysis, the whole pipeline looks like this:
- The watch requests several kinds of data from the official app over Bluetooth: predicted ephemeris, real-time assistance data (AGNSS), and ionospheric parameters — each kind has a URL-like “tag”;
- The app downloads a seed file from the cloud (JSON, containing orbital data for each constellation);
- A closed-source native decoding library built into the app decodes the seed into a binary file the watch understands;
- It’s sent to the watch over Bluetooth in encrypted fragments.
I decided to move this entire pipeline onto my own infrastructure: make the seed myself, decode it myself, package it myself, and have my own app answer the watch. A bonus is that it enables A/B comparison experiments (same watch, same day, two data sources delivered alternately, objectively comparing TTFF).
6. How I Implemented It
1. Reverse-Engineering the Format
First I statically analyzed the official app’s Java layer to figure out tag dispatch and the cloud endpoints; then I pulled seed samples from the phone’s cache. The hardest part was the gpsNav bitstream inside the seed: no documentation, so I had to gnaw through the decoding library’s assembly by hand. Using the approach of “patching the decoding library to force-export intermediate values,” I obtained the internal data of the decoding process, then ran correlation analysis between those coefficients and orbits propagated from publicly broadcast ephemeris, and finally confirmed: those 60 numbers are the 20th-order Chebyshev fit coefficients of each satellite’s three-dimensional ECEF coordinates over a 12-hour interval.
The verification method was pretty hard-core too: decode “the seed captured from the official app” and “the seed I made myself” with the official decoding library, and only when the output files’ MD5 hashes matched byte for byte did I count the format as cracked.
2. Where the Data Comes From
Making the data requires two public resources (all from IGS, the International GNSS Service, free):
- Broadcast ephemeris in RINEX: all satellites’ current orbital parameters, aggregated in real time from stations worldwide — one file per day, updated on a rolling basis;
- Ultra-rapid predicted orbits in SP3: precise 48-hour satellite orbit forecasts published by several analysis centers, one point every 5 minutes, with a new version every hour.
The generator’s logic: for each satellite in each constellation, take orbital sample points over the next 12-hour interval (falling back to extrapolating broadcast ephemeris where the forecast doesn’t cover), perform a 20th-order Chebyshev least-squares fit, and convert to fixed-point as the bitstream in the seed. The sample points must use 8-point Lagrange interpolation — I initially cut corners with linear interpolation, and the fitted orbit was off by a full 6 kilometers.
3. The Full Pipeline
|
On the server side I built a dual-source design: the official source (automatically pulling the latest seed from the official app’s cache each day and repackaging it) and my own self-made source, switchable with one click, plus a fallback inspection script — the moment the self-made data fails to cover the current time window, it switches back to the official source automatically, guaranteeing the watch never runs out of food.
4. The Bluetooth Protocol
Between watch and app runs a proprietary Bluetooth protocol: the watch first sends a negotiation request for “what data I want,” the app replies with configuration, and then the watch pulls data file by file in fragments. There’s a big pitfall here I’ll get to later — watch models have different requirements for fragment encryption, and my watch must use TLV encryption mode; anything sent in plaintext is rejected outright, which was also the root cause of my initial “progress 0%.”
7. Pitfalls Encountered with Self-Made Data (Highlights)
The pitfalls from those two weeks could fill a page of A4; here are the most representative ones:
1. The JSON must be written compactly. The seed is JSON, but the decoding library reads it by byte offset. Python’s default json.dump inserts space separators; with one byte out of place the decoder silently produced 0 files, without an error. Half a day of debugging later I found it was the separator.
2. Time must be monotonic, and you can’t slack off on empty segments. Each satellite in the seed is split into 14 time segments. At first I only updated the segments I generated and left the template’s old timestamps in the ones I didn’t — the result was that the whole time series “went backwards,” the decoder judged the data invalid, and all output was zeroed. Five consecutive test rounds all came back with 0/36 valid windows. The lesson: better to write an all-zero segment than to leave an old timestamp.
3. Week boundaries must wrap around. GPS system time counts in weeks (604800 seconds per week); when a segment’s end time crosses a week boundary it must be taken modulo and wrapped, otherwise a single number greater than 604800 is enough to have the whole dataset judged invalid.
4. Skimp on quality checks and the watch goes blind for 12 minutes. My quality gate initially only checked “file is not empty,” so one batch of self-made data with only 1/36 valid windows went live and displaced the watch’s official data that was valid for 3 days. That afternoon I stood outdoors for 12 minutes and 17 seconds without ever getting a fix. Afterwards the quality gate became a hard check for “must cover the current time window.”
5. “It’s on the phone” doesn’t mean “the watch has it.” Many times I eagerly tested whether “the new data had taken effect,” only to find the watch never came to pull it — the watch decides when to update on its own schedule, and if the data hasn’t expired it doesn’t even look. Testing efficiency was extremely low, which is one reason I ultimately gave up on frequent real-world tests.
6. Small-sample correlation is a trap. While analyzing what the coefficients meant, I ran a regression over 24 satellites and got a correlation of 0.9+, thinking I’d found the mapping; when I expanded to all 30 satellites the correlation dropped to 0.49. Fit coefficients are compressed derived quantities — you can’t infer their meaning by linear regression. In the end it was confirmed only through “patching to export intermediate values + physical verification.”
7. On the Bluetooth link, whoever occupies it answers. The official app, resident in the background, would grab the watch’s requests. My solution was to disable it normally, then open a 15-minute window in the early morning to let it “come back to life” and refresh the seed once, then switch back.
8. Network environment pitfalls. Carrier DNS failing to resolve my self-hosted domain, VPN global proxy stalling the download — in the end I added DoH resolution as a fallback and disabled the proxy to get things stable.
8. Current Status and Results
After two weeks of tinkering, both pipelines work:
- Official-source pipeline (currently the primary one in production): every day in the early morning it automatically pulls the latest seed, decodes, and packages it, so the watch always receives data with a full 36/36 windows covering the next 3 days. Measured in practice: fix in seconds.
- Self-made pipeline: the full chain from public data to the watch is working, and the watch has genuinely received ephemeris I made. Compared window-for-window with the official data, 401 of 420 time segments matched byte for byte; the segments I regenerated differ from the official fit by roughly 50 meters in magnitude — entirely sufficient for assisted positioning (it only helps the watch “find satellites”; the final fix still comes from the watch receiving and solving signals itself).
The only gap is forward coverage duration: public predicted orbits only extend about 24 hours into the future, so my self-made data is valid for roughly 21.5 hours, while the official one achieves 72 hours. To make all 3 days self-generated, you’d need to do your own short-arc orbit determination and orbit extrapolation (I built a prototype with a 2.4-kilometer extrapolation error at 12 hours) — that’s another order of magnitude of engineering, and I don’t plan to continue for now. Anyway, dual-source switching plus the fallback inspection script already guarantees the watch never runs out of food.
9. In Closing
Looking back, the most valuable part of this project wasn’t really the result but the process: in order to manufacture a few hundred KB of data, I got hands-on with satellite navigation, orbital mechanics, binary format reverse-engineering, and the BLE protocol stack. Terms we say every day like “dual-frequency,” “six constellations,” and “A-GPS” — take them apart and each one deserves its own article. Consider this one my submitted homework.
The watch is still on my wrist, and every day in the early morning the server refreshes its ephemeris automatically. It doesn’t know who sent the data; it just, right on time, in the second that I raise my wrist, tells me where I am.
To restate: this article is for learning and exchange only, all format analysis is a personal research record, and please do not use it for commercial or improper purposes.
References
- IGS - International GNSS Service (public ephemeris and predicted orbit data)
- GPS SPS Performance Standard (official GPS performance standard document)
- The watch’s official companion app and its built-in decoding library (for personal research analysis only)
评论
0 条评论