Kozai::Api
The HTTP JSON API, and the server that also hands out the web interface.
Every answer carries the age of the elements behind it
This is the one thing the API does that a naive one would not. SGP4's
accuracy decays with time from the element set epoch — a few hundred metres
after a day, kilometres after a week, and eventually nonsense — but the
numbers coming out never look any less confident. A client that cannot see
the age has no way to tell a good prediction from a useless one, so every
response that depends on a satellite reports elements_age_seconds and a
stale flag.
Routing
Hand-rolled, splitting the path on slashes. Regular expressions would pull PCRE2 into a binary that otherwise has no C dependencies beyond the runtime, and for a dozen fixed routes they would not earn their place anyway.
Constants
Age beyond which element sets are reported as stale.
NOTE: three days is a judgement, not a threshold with physics behind it. Below a day the error is normally under a kilometre; by a week a low satellite can be tens of seconds out in time of pass, which is enough to miss it. Three days is where a warning starts being more useful than annoying.
Constructors
Class methods
Compares two secrets without leaking where they first differ.
NOTE: written out rather than taken from the standard library, for the
same reason the asset ETag is a hand-rolled FNV-1a: crypto/subtle is not
on the list of requires this project allows itself, and CI greps for that.
Eight lines is a smaller price than a C library.
The lengths are compared first and separately, which does leak the length of the secret. That is deliberate and harmless — an attacker who knows how many characters to guess is no closer to knowing which.
Instance methods
Binds and serves until interrupted.
Whether an operator has taken the antenna by hand.
NOTE: set by POST /api/rotator/track and cleared by stop, and the
scheduler keeps its hands off while it is set. Without it the daemon would
take the antenna back at the next slot boundary — in the middle of
whatever the operator had deliberately pointed it at.
Whether an operator has taken the antenna by hand.
NOTE: set by POST /api/rotator/track and cleared by stop, and the
scheduler keeps its hands off while it is set. Without it the daemon would
take the antenna back at the next slot boundary — in the middle of
whatever the operator had deliberately pointed it at.
The station passes are computed for.
NOTE: replaceable, because the settings page can move the station. The loops that use it read it from here on every pass rather than capturing it at start, or a station that corrected its coordinates would keep pointing at the old ones until it was restarted.
Called when the settings change, to rebuild whatever the daemon owns — the rotator and receiver connections, which the API has no business constructing itself.
Called when the settings change, to rebuild whatever the daemon owns — the rotator and receiver connections, which the API has no business constructing itself.
Called with a candidate configuration before it is written anywhere.
NOTE: this is the difference between a settings page and a way to brick a station. A field can be individually valid and collectively fatal — a transponder path is a perfectly good string and a missing file — and writing it first meant the daemon then refused to start, leaving the operator to delete a file from a volume, which is exactly what somebody holding a phone cannot do. Anything that raises here is refused before it reaches the disk.
Called with a candidate configuration before it is written anywhere.
NOTE: this is the difference between a settings page and a way to brick a station. A field can be individually valid and collectively fatal — a transponder path is a perfectly good string and a missing file — and writing it first meant the daemon then refused to start, leaving the operator to delete a file from a volume, which is exactly what somebody holding a phone cannot do. Anything that raises here is refused before it reaches the disk.
Takes a changed configuration into use without a restart.
NOTE: everything derived from the configuration has to be rebuilt here, and the list is the interesting part. The observer, obviously. The watchlist, because it is parsed from the configuration and cached against the catalogue's load time — which has not changed, so it would otherwise keep the old answer. The schedule, because a plan built for one place is wrong in another. And the rotator and receiver, through the callback, because a port or a host may now be different.
Missing any one of these gives a station that says it moved and behaves as though it had not, which is worse than requiring a restart. Rebuilt from the configuration as it stood before the settings page, plus whatever the page holds now. Not by writing over the live one: applying a store can only set values, so a field the operator cleared would keep its old value until the daemon was restarted — and "no restart" is the entire point of the page.
Takes a changed configuration into use without a restart.
NOTE: everything derived from the configuration has to be rebuilt here, and the list is the interesting part. The observer, obviously. The watchlist, because it is parsed from the configuration and cached against the catalogue's load time — which has not changed, so it would otherwise keep the old answer. The schedule, because a plan built for one place is wrong in another. And the rotator and receiver, through the callback, because a port or a host may now be different.
Missing any one of these gives a station that says it moved and behaves as though it had not, which is worse than requiring a restart. Rebuilt from the configuration as it stood before the settings page, plus whatever the page holds now. Not by writing over the live one: applying a store can only set values, so a field the operator cleared would keep its old value until the daemon was restarted — and "no restart" is the entire point of the page.
Replaces the hardware control after a settings change.
The satellites a catalogue-wide search should cover.
The watchlist when there is one and it matched something, the whole catalogue otherwise. A list that matched nothing falls back rather than returning an empty station: the entries are reported as unknown, and an operator staring at a blank pass table learns less than one staring at every pass plus a line saying which of their satellites was not found.
The configured satellites, matched against whatever is loaded now.
NOTE: memoised against the catalogue's load time rather than computed per request. Resolving a name fragment is a scan of the catalogue, and on a full one that is tens of thousands of string comparisons for an answer that only changes when the elements are reloaded.
Points the receiver at satellite, using id if one was asked for and the satellite's default transponder otherwise.
Returns the transponder tuned, or nil if there is no radio or nothing is
known about that satellite. Raises ArgumentError when a transponder was
named and either does not exist or belongs to a different satellite —
silently tuning something else would be worse than refusing.