Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ shape of the app, then the feature doc for the area you are changing.
| [architecture.md](architecture.md) | App startup, blueprints, sessions and CSRF, service status, the apply service, stack reconcile, the restart lock and its couplings to other repos, home and summary pages |
| [features/auto-calibrate.md](features/auto-calibrate.md) | Auto-Calibrate: the tower, gain and LNA search, overload handling, wedge recovery, the wizard path |
| [features/setup-wizard.md](features/setup-wizard.md) | The onboarding wizard, step by step |
| [features/tutorial.md](features/tutorial.md) | The guided tutorial of the Summary, Home and Configuration pages |
| [features/device-state-and-telemetry.md](features/device-state-and-telemetry.md) | What the node persists about itself, telemetry consent and claim, the telemetry status document |
| [features/remote-access.md](features/remote-access.md) | LAN vs tunnel access, identity and login, Mender Connect, SSH keys |
| [features/fleet-and-naming.md](features/fleet-and-naming.md) | mDNS peer discovery, the fleet bar, node names |
Expand All @@ -34,6 +35,7 @@ Use this to find the doc to update when you change a file.
| `templates/base.html`, `templates/index.html`, `templates/summary.html`, `templates/eula.html`, `static/flight-sim.js`, `static/common.css` | [architecture.md](architecture.md) |
| `src/calibrator.py`, `src/routes/calibrate.py`, `src/blah2_client.py`, `static/calibrate.js`, `templates/setup/_calibrate.html` | [features/auto-calibrate.md](features/auto-calibrate.md) |
| `src/routes/setup.py`, `static/setup.js`, `templates/setup.html`, `templates/setup/*` | [features/setup-wizard.md](features/setup-wizard.md) |
| `src/tutorial.py`, `src/routes/tutorial.py`, `static/tutorial.js`, `static/tutorial.css`, `static/tutorial/*` | [features/tutorial.md](features/tutorial.md) |
| `src/device_state.py`, `src/telemetry_status.py` | [features/device-state-and-telemetry.md](features/device-state-and-telemetry.md) |
| `src/remote_access.py`, `src/access_identity.py`, `src/mender_connect.py`, `src/ssh_keys.py`, `src/routes/remote_access.py`, `templates/login.html`, `templates/remote_denied.html` | [features/remote-access.md](features/remote-access.md) |
| `src/mdns_peers.py`, `src/routes/fleet.py`, `src/node_name.py`, `templates/_fleet_bar.html` | [features/fleet-and-naming.md](features/fleet-and-naming.md) |
Expand Down
54 changes: 47 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,18 @@ Route modules are imported at the bottom of `app.py`, after the app exists, beca
| `tracker`, `tracker_legacy` | [routes/tracker.py](../src/routes/tracker.py) | [tracker.md](features/tracker.md) |
| `fleet` | [routes/fleet.py](../src/routes/fleet.py) | [fleet-and-naming.md](features/fleet-and-naming.md), [Summary page](#summary-page) |
| `remote_access` | [routes/remote_access.py](../src/routes/remote_access.py) | [remote-access.md](features/remote-access.md) |
| `tutorial` | [routes/tutorial.py](../src/routes/tutorial.py) | [tutorial.md](features/tutorial.md) |

`inject_globals` (a context processor) adds to every template: `node_id`, the owl-os and
retina-node versions, `is_remote` and `pathway` (which way the request arrived, defaulting to
LAN outside a request), and `fleet_nodes` for the banner. The banner is in `base.html`, so the
node list has to come from here rather than per route. It is an in-memory list copied and
sorted, cheap enough per render.

It also adds `tutorial`, which is `None` on every ordinary request and the guided tutorial's
payload when the address names a tour step that lives on the page being rendered. See
[tutorial.md](features/tutorial.md#how-a-run-moves).

## Request hooks

Flask runs app-level `before_request` handlers in registration order. The order in `app.py`
Expand Down Expand Up @@ -518,8 +523,11 @@ redirects to `/set-up` while the setup wizard is in progress. Otherwise it shows
the node identifier (`node_ref`) and state, and the `detail` prose from retina-telemetry
verbatim: mapping its states to our own wording would be a second vocabulary to keep in step
with a file we do not own. See [device-state-and-telemetry.md](features/device-state-and-telemetry.md).
- Service cards (Passive Radar, Max-Hold, Controller, ADS-B Map, Tracker), or the spectrum
- Service cards (Passive Radar, Max-Hold, ADS-B Map, Tracker, Controller), or the spectrum
iframe or SDRconnect panel in those modes (see [sdr-mode.md](features/sdr-mode.md)).
Controller is last because it is the way into every view the other cards do not cover. The
Tracker card says how much it keeps (up to 4 hours, the sidecar's limit; see
[tracker.md](features/tracker.md#view-window)).

The page is host-agnostic: `owl.local` and the node's own `ret<node_id>.local` produce the same
page. The shared alias is a way in, and the fleet banner moves you between nodes from there.
Expand Down Expand Up @@ -556,14 +564,29 @@ blanks the node sub-nav: Home and Config would be about whichever node served th
two sections cannot drift apart. They are separate from the node grid because those cards
stand for hardware on this network. The outbound arrow means "this leaves for another page",
so a `mailto` card does not wear one.
- **How your node sees**: the flight-path diagram below.
- **Doppler Mapping Tool**: the flight-path diagram below.

### Flight simulator

A cut-down version of the primer's closing simulation. The left panel shows a tower, the node,
the wedge of sky the node hears and a flight path; the right panel plots what the node would
A cut-down version of the primer's closing simulation. The top panel shows a tower, the node,
the wedge of sky the node hears and a flight path; the panel under it plots what the node would
record, range (bistatic excess path) across and Doppler up.

The two panels are stacked, each the full width of the page's column, at every window size.
They used to sit side by side, which left each too small to draw a path in comfortably and cut
the beam off at the edges of the sky. The sky's `viewBox` in `summary.html` is sized from two
numbers in `flight-sim.js`, `BEAM_R` (how far the node hears) and `WIDEST` (the widest the beam
opens): pointing up, as it starts, the whole wedge is in the picture at its widest. The view is
1.3 times the box that would just hold that wedge, so the tower, the node and the beam sit in the
middle with sky to spare all round, and a path can start and end well outside the beam. Steered
far enough off vertical, a wide beam still runs off the bottom edge; fitting every direction would
need a square panel. `tests/test_fleet.py` holds the view to the two numbers, and the area a path
can be drawn in (`SKY`) to the view.

Because the far arc of the wedge is now in the picture, it is also a real limit: an aircraft
beyond it is not heard (`inBeam`). Before, only the angle was checked, which did not show while
the arc was off the edge of the panel.

It is progressive. The markup alone is a correct still diagram, with the beam and a default
flight path drawn in. [flight-sim.js](../static/flight-sim.js) only adds the ability to fly it,
so the controls ship `hidden` and the script reveals them. With no script, no pointer events, or
Expand All @@ -572,17 +595,21 @@ rather than inline so the browser caches it.

How the numbers are made:

- Range is `d1 + d2 - baseline` scaled by `KM` (0.15 km per SVG unit, baseline about 27 km).
- Range is `d1 + d2 - baseline` scaled by `KM` (0.125 km per SVG unit: baseline about 22 km, and
the beam reaches 25 km).
- Doppler uses the real bistatic relation, `-(v . (u1 + u2)) / lambda`, with a fixed 213 MHz
centre frequency and 170 m/s aircraft speed. It is zero when the heading is perpendicular to
the sum of the two unit legs, which is not the same as perpendicular to the node. That sum
has magnitude at most 2, which caps Doppler at `2v/lambda`.
- The scale is chosen so the picture cannot reach its own axes: drawn into the furthest corner
the scene tops out around 51 km and 242 Hz, inside a plot of 60 km and 300 Hz. A track pinned
- The scale is chosen so the picture cannot reach its own axes: heard at the far end of the beam
the scene tops out around 50 km and 242 Hz, inside a plot of 60 km and 300 Hz. A track pinned
to a bound would say the aircraft stopped changing when really the plot ran out. The clamps in
the plotting code are a backstop, not a working part.
- Leg lengths are floored so a path drawn straight over the tower or the node does not divide by
zero and make the track vanish.
- The plot's box is written twice, in the markup and as `PLOT` in the script, as is the default
flight path (so the still diagram does not jump when the script takes over). Tests hold both
pairs together.
- Where the aircraft is outside the beam, the track has a gap. Nothing was heard, so nothing is
drawn; that break is the point of the beam.

Expand All @@ -593,7 +620,15 @@ Behaviour:
- The loop runs at most 30 fps and stops (not just hides) when the tab is hidden or the section
scrolls out of view.
- Drawn samples closer than 4 units are dropped; a path is a shape, not a recording of the hand.
- The aircraft's heading turns steadily between one drawn point and the next (`tangent`, `at`).
Read off each straight piece of the path, it jumps at every point and takes Doppler with it,
which drew the track as a sawtooth; small in the old half-width plot, obvious in this one.
- The beam can be steered by dragging the ring or with the arrow keys, and narrowed or widened.
The ring is labelled "Grab to Aim". The label ships undrawn (`display="none"`) and the
script reveals it, for the same reason the controls ship hidden.
- The ring takes keyboard focus so the arrow keys work, but it never shows the browser's focus
outline: a mouse grab focuses it too, and the outline then sat round it as a black box.
Keyboard focus is shown by the ring thickening (`:focus-visible`).

The plot axes carry their real limits, because a plot whose bounds are a mystery teaches the
wrong lesson about where a track sits. The link to the full primer shows only when `PRIMER_URL`
Expand All @@ -610,4 +645,9 @@ owner pathway; the LAN has no session to end.
[common.css](../static/common.css) holds the shared design-system styles for every page. Its
comments explain individual rules in place.

When `tutorial` is set, `base.html` also loads `tutorial.css`, the tour payload and
`tutorial.js`, which draws the guided tutorial over the page. Elements on the Summary, Home and
Configuration pages, in the fleet bar and on the sub-nav's Config link carry `data-tutorial` attributes naming what the tour
points at; they have no other effect. See [tutorial.md](features/tutorial.md#targets).

[eula.html](../templates/eula.html) is a placeholder, linked from the wizard.
Loading
Loading