Hibernate on Approach: Considerations

Why a battery-less device changes the requirement

The target device is a small SBC with no internal battery, powered from the same train inverter/socket circuit as the wifi router. There is no graceful low-power warning path — it dies the instant power drops, exactly like the router. This means the hibernate-to-disk write must be fully complete, not just started, before the train reaches the real electrical dead zone. A partial hibernate write risks worse corruption than simply letting the device die uncleanly with no hibernate attempt at all, so “trigger a bit early and finish comfortably” strictly dominates “trigger late and risk an incomplete write.”

Uncertainty budget

Position uncertainty: ~1.7–2.7 km, from comparing observed router-drop locations against documented gazetteer coordinates (see Findings). This is currently the dominant source of error — bigger than polling lag or (probably) hibernate write time.

Speed: up to ~200 km/h (~55 m/s) observed through the relevant stretches. Higher speed directly shrinks the safety margin in time for any given distance uncertainty.

Polling lag: ~4-8s while genuinely in motion (see Data Sources) — small relative to position uncertainty (worst case ~440m), but not zero, and materially worse during the idle→moving transition where the most recent fix could be minutes old.

Hibernate write time: unknown for this specific device/storage combination — the single biggest unmeasured unknown right now. SD card storage could be meaningfully slower than SSD for a RAM-to-disk write, and this hasn't been tested at all yet.

All of these compound in the same direction — every source of error argues for triggering earlier, never later. Current recommendation given the above: treat the two confirmed points with a wide ~5 km trigger radius until real multi-trip data narrows the position estimate and a real hibernate-write measurement replaces the current unknown.

Disentangling outage duration: dead-time vs boot time vs reassociation time

An observed router outage duration is not simply “how long the power was off”. It's a mix of: (a) true electrical dead-time crossing the phase break, (b) router reboot/cold-boot time if power was fully lost, and (c) wifi reassociation time once power/signal is back. A single 4-second blip observed right after a reconnection (see Findings) suggests warm reassociation alone can be very fast — which argues that the 40-50+ second durations at the two confirmed neutral sections are not simply reconnection overhead, and more likely include genuine dead-time. But this is inference from one data point, not a calibrated constant, and a proper cold-boot baseline is still needed (see Todo).

Phase breaks are never at stations

By design, neutral sections have to be on plain running line where a train can coast through under momentum with power cut and the pantograph typically still up, then re-power beyond it. One sited at a platform would strand any stopping train with no power to restart. Practical implication: any router/power drop observed while the train is stationary is definitionally not a phase break — useful as an automatic filter for candidate events, and potentially useful as an incidental stationary calibration point (though not yet observed in the data).

Fixed radius vs time-to-section at speed

The current design uses a fixed ~5 km trigger radius. This is simple but not ideal — the actual requirement is really a time margin (enough time for the hibernate write to complete), and time-to-reach-a-point depends on current speed, which varies by train, schedule, and possibly weather/signalling delays. Once real hibernate-write timing is known, the trigger condition should move to “estimated time to section, at current speed, is less than write-time-plus-safety-margin” rather than a fixed distance. This is more robust across different journeys.

Cause doesn't matter — only the effect on the router

The trigger only cares whether the router loses power at a given point, not why. A genuine electrical phase break and the Maidenhead-area drop (see Findings) have the same practical consequence for the SBC — power gone, hibernate needed — regardless of whether the underlying cause is a documented neutral section, an undocumented one, or something else systematic (e.g. trackside infrastructure, signalling-related switching). This means the target list for the trigger shouldn't be constrained to the five-entry gwml-neutral-sections gazetteer: it should be — or eventually become — a list of observed recurring power-loss points, built empirically from repeated router-drop locations, with the neutral-section gazetteer as one useful prior rather than the definition of the target set. A recurring drop that fails to match any documented section (Maidenhead) is still operationally exactly as important as one that does.

Idea (John, 2026-09-17): decide by location, not by a flag — NOT ADOPTED, kept for later

Context: the 2026-08-17 incident exposed that nothing restarts the hibernator — after the unclean power loss the machine ran unprotected for 18m40s until restarted by hand (see Findings). A systemd unit with Restart=always was proposed and rejected: John does not want it running except on the train. A journey flag file was then proposed. John's counter-idea is better and removes the "did I remember to start it" problem entirely:

Let it always run, and have it cheaply suppress itself by location. In John's words: "it is easy to tell if I am home, my location in my home location. Also if I am in London… my location will be east of Paddington. (I will only be using this on that line; I think other lines do not have the problem) In fact it can normally exit if it is any distance from the railway line (a mile or so)." The script already queries location-db every 5 s, so the test is nearly free. If Restart=always costs nothing when away from the line, the original objection to always-running disappears — and protection then survives the exact failure mode it exists to handle, with nothing to remember.

Build the corridor from location history, not from stations. John, 2026-09-17: "there are quite big gaps between Reading and Bristol Parkway - but you can fill in with existing history." Checked, and he is right on both halves — the station approach has two failure modes that history does not:

Sketch, if it is ever built: take the locations fixes, keep those moving at rail speed (there is already speed data, and it separates travel from walking about at either end), buffer the resulting multipoint by ~1 mile, union it, and store the polygon as a single gazetteer row. The script then does one ST_Contains/ST_DWithin test per poll and only computes nearest-section when inside. Two things to watch: the corridor must be regenerated occasionally or a new route will be outside it (a diversion such as the Chipping Sodbury closure would fall outside a corridor built before it), and the buffer should be generous rather than tight — a false "inside" merely costs a nearest-section query, whereas a false "outside" silently disables the protection, which is the failure this whole idea exists to prevent.

Measured 2026-09-17: the line passes about 860 m (0.53 miles) from home. John raised this — "home is not that far from the railway, so compare the distance between home and the track to see how far out you can reasonably go" — and it changes the design. A 1-mile corridor buffer would contain home, so the corridor test on its own can never distinguish "at home" from "on the train". An explicit home-exclusion test is required in addition, not as an optimisation. Shrinking the buffer instead is not a fix: 860 m leaves no useful margin against GPS error, and a buffer tight enough to exclude home would risk false "outside" readings mid-route — the silent-disable failure this whole idea exists to prevent.

Route variance measured 2026-09-17 — and it CORRECTS the conclusion above. John asked for the variance across all journeys since the Bath diversion ended (Chipping Sodbury reopened 2026-08-03), excluding fixes without good claimed accuracy. Result: 15 journey days, 12,280 qualifying fixes, median deviation from the route centreline of 4.0 m and p95 of 23.2 m. His expectation that "it isn't bad" was a considerable understatement.

Deviation from centrelineShare of fixes
under 50 m96.401%
50-200 m2.915%
200-500 m0.171%
500 m - 1 mile0.138%
over 1 mile0.375%

Consequence: a ~500 m buffer works, and the home problem dissolves. 99.5% of travel lies within 500 m of the centreline, while home sits at 859 m — so a 500 m corridor captures essentially all journeys and still clears home by about 360 m. This supersedes the statement above that an explicit home-exclusion test is REQUIRED; that was reasoned from an assumed 1-mile buffer, before the variance was measured, and a mile turns out to be roughly 30x wider than needed. A home test is still worth having as belt-and-braces, and it is trivially cheap, but it is no longer load-bearing. Prefer something in the 300-500 m range: wide enough to swallow GPS error and the 2.9% of fixes between 50 m and 200 m, narrow enough to leave real margin against home.

John's proposed rule (2026-09-17), and the result of testing it against history. Proposal: take the home-to-railway distance, halve it (859/2 ≈ 430 m); inside that, consider the machine "on track"; if beyond it for two consecutive distinct location fixes with good reported accuracy — not two polls of the same fix — quit. Both design choices are sound and should be kept. The two-distinct-fixes requirement matters more than it looks: the hibernator polls every 5 s but the DB frequently returns the same fix repeatedly (the log shows age=8s then age=14s with identical coordinates), so counting polls would let a single bad fix trip the quit twice. Deriving the threshold from measured geometry also guarantees a 2x margin at home by construction rather than by choosing a round number.

But measured against real journeys the rule would have quit mid-route, and the cause is the centreline, not the rule. Replaying every journey since 2026-08-03 against a 430 m threshold, runs of two or more consecutive off-track good-accuracy fixes occur on several genuine journey days (2026-08-06, 08-13, 08-19, 08-20). Inspecting the worst, they all cluster at about 51.526, -0.22 to -0.23 — the Paddington throat around Acton / Old Oak Common, where the line fans into parallel tracks and a single median centreline stops describing it; deviations of 496-988 m appear there on entirely normal running. The 2026-08-19 16:54 pair is the damaging case: that is the westbound departure from Paddington, so the rule would have quit before Airport Junction and lost protection for the whole journey west.

Overrides (John, 2026-09-17): two of them — "use Bath route" and "disable the quit logic entirely". Both are right to want. Notes on implementing them:

Refinement (John, 2026-09-17): the buffer does not have to be uniform — slacken it away from home. "You can slacken the tolerance on that too - it runs away from home." The 430 m figure was derived from one constraint only: the corridor must not swallow home, which sits 859 m from the line. That constraint applies solely in the neighbourhood of home. Bath Spa is about 19 km away, so the Bath corridor can be buffered at a kilometre or more with no effect on the home test at all. Conveniently this cancels out the one weakness of the Bath corridor — that its history is thin (153 fixes) — because a generous buffer is affordable in exactly the place where the data is sparse.

Generalising: make the buffer a function of distance from home. Tight (about 400 m) within a few km of home where the separation has to be made; slack (1-2 km) everywhere else. This follows directly from the asymmetry noted earlier — a corridor that is too wide merely leaves the script running a little more often, whereas one that is too narrow silently disables the protection mid-route, which is the failure the whole design exists to prevent. So the default should be generous, and tightened only where something specific forces it.

The timetable already knows the route — a better approach than any of the above

John, 2026-09-17: "Trains don't tend to change ad hoc mid journey - these timetable things are planned well in advance. You have read such timetables." He is right, and it undercuts most of the corridor design above. The WTT data is already loaded on gravlax and is current: 83,113 journeys and 1,494,642 calls, validity 2025-05-18 to 2026-12-13, with 75,877 journeys still valid today. train_calls carries seq, wtt_name, station_id, arr, dep, pass, is_pass, platform, line and activities — so it includes non-stopping passing points with booked times, which is exactly what distinguishes one route from another.

The passing points ARE the neutral sections. A sample Paddington→Bristol service passes LADBROKE GROVE, ACTON WEST, SOUTHALL, HEATHROW AIRPORT JN, SLOUGH, MAIDENHEAD, TWYFORD, KENNET BRIDGE JN, READING, GORING & STREATLEY, DIDCOT PARKWAY, WANTAGE ROAD… — i.e. the timetable already books a time at, or beside, every location this project has spent months triangulating from wifi dropouts. Matching the gazetteer's neutral sections to their nearest geocoded WTT point gives: Airport Junction → HAYES & HARLINGTON at 95 m, Maidenhead (EB) → MAIDENHEAD at 716 m, Steventon/Milton (EB) → DIDCOT PARKWAY at 1,698 m, Chipping Sodbury → YATE at 6,504 m, Royal Wootton Bassett (WB) → KEMBLE at 14,121 m.