Add multi-decoder split and pit timing support
This commit is contained in:
@@ -893,6 +893,96 @@ sequenceDiagram
|
||||
A->>A: rerender timing/overlay
|
||||
```
|
||||
|
||||
### 8.3.1 Multi-decoder topology and ownership
|
||||
|
||||
The decoder runtime supports four explicit roles. Roles are semantic, not inferred from `loop_id`; the WebSocket connection used to receive a message determines how it is processed.
|
||||
|
||||
| Runtime role | Frontend URL setting | Managed AMMC config | Default port | Canonical mutation |
|
||||
|---|---|---|---:|---|
|
||||
| `main` | `settings.wsUrl` | `decoderHost` / `wsPort` | `9000` | validates and appends `result.passings`; increments laps |
|
||||
| `split` | `settings.splitWsUrl` | `splitDecoderHost` / `splitWsPort` | `9001` | appends `result.splits`; updates split fields only |
|
||||
| `pit_entry` | `settings.pitEntryWsUrl` | `pitEntryDecoderHost` / `pitEntryWsPort` | `9002` | opens pit state and appends a pit entry event |
|
||||
| `pit_exit` | `settings.pitExitWsUrl` | `pitExitDecoderHost` / `pitExitWsPort` | `9003` | closes pit state and appends the measured stop |
|
||||
|
||||
Physical and network chain:
|
||||
|
||||
```text
|
||||
physical loop -> physical decoder IP -> one AMMC child process on backend host
|
||||
-> unique AMMC WebSocket port -> browser WebSocket client with fixed role
|
||||
-> processDecoderMessageHelper(message, deps, role)
|
||||
```
|
||||
|
||||
Example complete installation:
|
||||
|
||||
```text
|
||||
Main decoder 192.168.1.10 -> backend AMMC :9000 -> role main
|
||||
Split decoder 192.168.1.11 -> backend AMMC :9001 -> role split
|
||||
Pit entry decoder 192.168.1.12 -> backend AMMC :9002 -> role pit_entry
|
||||
Pit exit decoder 192.168.1.13 -> backend AMMC :9003 -> role pit_exit
|
||||
Browser host 192.168.1.100 uses ws://192.168.1.100:9000 through :9003
|
||||
```
|
||||
|
||||
Invariants:
|
||||
|
||||
- only `main` may change `entry.laps`, `entry.lastTimestamp`, or append to `result.passings`
|
||||
- split timing is measured from the latest main timestamp, or mass-start timestamp before the first completed lap
|
||||
- a duplicate pit-entry signal while `entry.pitActive` is true is ignored
|
||||
- pit exit without a prior pit entry is ignored
|
||||
- an optional channel can disconnect without stopping main lap counting
|
||||
- main, split, pit entry, and pit exit must use distinct AMMC WebSocket ports
|
||||
- every physical decoder normally has a distinct IP; reusing one feed for different roles would process the same crossing with conflicting semantics
|
||||
|
||||
### 8.3.2 Multi-decoder frontend lifecycle
|
||||
|
||||
`src/app.js` owns role-indexed `wsClients` and `reconnectTimers` maps. `connectDecoderHelper()` first disconnects all clients, always connects `main`, then connects enabled optional roles. `connectDecoderRole()` binds the role into every message callback. Auto-reconnect checks the current enable flags before reconnecting optional channels.
|
||||
|
||||
Decoder runtime state is persisted with separate status fields:
|
||||
|
||||
```js
|
||||
decoder: {
|
||||
connected, lastMessageAt, lastError,
|
||||
splitConnected, splitLastMessageAt, splitLastError,
|
||||
pitEntryConnected, pitEntryLastMessageAt, pitEntryLastError,
|
||||
pitExitConnected, pitExitLastMessageAt, pitExitLastError
|
||||
}
|
||||
```
|
||||
|
||||
Race data is stored under `resultsBySession[sessionId]`:
|
||||
|
||||
```js
|
||||
{
|
||||
passings: MainPassing[],
|
||||
splits: SplitPassing[],
|
||||
pitEvents: PitEvent[],
|
||||
pitStops: PitStop[],
|
||||
competitors: {
|
||||
[competitorKey]: {
|
||||
lastSplitMs, previousSplitMs, lastSplitDeltaMs, bestSplitMs,
|
||||
lastSplitTimestamp, lastSplitLap,
|
||||
pitActive, pitEnteredAt, pitStops,
|
||||
lastPitStopMs, totalPitMs, bestPitStopMs
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The full result object is included in `/api/state` persistence. Only main passings are posted to `/api/passings` and the SQLite passings table. This preserves the existing lap ledger and prevents split/pit records from being interpreted as laps. If split or pit history later needs SQL reporting, add dedicated tables and endpoints rather than inserting those records into `passings`.
|
||||
|
||||
### 8.3.3 Managed AMMC process lifecycle
|
||||
|
||||
`server.js` owns four process states in `ammcState.channels`: `main`, `split`, `pitEntry`, and `pitExit`. `startAmmcProcesses()` normalizes and validates the complete configuration before spawning one child process per enabled role. All roles use the same resolved AMMC executable and working directory but receive different `-w <port> <decoderHost>` arguments.
|
||||
|
||||
The AMMC status API remains backward compatible: top-level status fields represent `main`; nested `split`, `pitEntry`, and `pitExit` objects expose optional process status. Combined `lastOutput` records include a `role` field. `stopAmmcProcesses()` stops all roles during an API stop request or server shutdown.
|
||||
|
||||
Configuration validation rejects:
|
||||
|
||||
- ports outside `1..65535`
|
||||
- duplicate ports among enabled channels
|
||||
- an enabled role without its decoder host when AMMC is started
|
||||
- missing AMMC executable
|
||||
|
||||
Changing backend AMMC process code requires `npm restart` or `npm.cmd restart`. Editing browser modules alone is handled by app-version polling, but a server process cannot hot-reload its own implementation.
|
||||
|
||||
## 8.4 Public overlay flow
|
||||
|
||||
```mermaid
|
||||
@@ -1309,9 +1399,11 @@ Start and finish lifecycle announcements are initiated from `timing_views.js`, w
|
||||
- `normalizeAmmcConfig(...)`
|
||||
- validates/stabilizes AMMC config
|
||||
- `buildAmmcStatus()`
|
||||
- current AMMC process status object
|
||||
- `startAmmcProcess()` / `stopAmmcProcess()`
|
||||
- process lifecycle
|
||||
- backward-compatible main status plus nested split/pit channel statuses
|
||||
- `startAmmcProcesses()` / `stopAmmcProcesses()`
|
||||
- lifecycle for every enabled decoder role
|
||||
- `startAmmcProcess(role)` / `stopAmmcProcess(role)`
|
||||
- lifecycle for one AMMC child process
|
||||
- PDF export helpers below `/api/export/pdf`
|
||||
|
||||
#### Critical backend routes
|
||||
|
||||
Reference in New Issue
Block a user