Harden RF handoff and stream OTA decode

This commit is contained in:
LittleSam129
2026-09-22 17:43:58 +03:00
parent 168e03d9e7
commit a94f2db4ee
17 changed files with 1548 additions and 497 deletions

View File

@@ -1,142 +1,96 @@
TASK_ID = RF001A_OTA_TX_003
TASK_TITLE = Transmit one coordinated RF-001A QPSK burst
TASK_ID = RF001A_OTA_TX_004
TASK_TITLE = Send one finite RF-001A burst into RX capture 004
SOURCE_BRANCH = video-development
EXPECTED_HEAD = bc55d0516dc1ee36cefa1ff096e7d86f60645303
EXPECTED_HEAD = 168e03d9e778ab2ec63502552d385ec7be3ef677
REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES
RF_RX = NO
RF_TX = YES
ALLOW_COMMIT = NO
ALLOW_PUSH = NO
ALLOW_PUSH = TX_RESULT_ONLY
RISK_LEVEL = RED
This file is the authoritative current task for the MAIN PC TX node.
Read TX_NODE_STATE.md first. If chat history, this file, the shared RF-001A spec or current Git HEAD conflicts, stop and report the conflict.
---
# Current TX task
# RF-001A TX 004
TASK =
TRANSMIT ONE COORDINATED RF-001A QPSK BURST
This task is prepared only. Do not execute it now. Old approval for any earlier
TASK_ID is invalid. The MAIN watcher waits for a fresh
`RF001A_OTA_CAPTURE_004` READY event from `origin/rx-node`.
This is a new RED task. Publication of this file is not permission to transmit. It does not inherit any approval from RF001A_OTA_TX_001, RF001A_OTA_TX_002, the RX task, a previous watcher prompt or a previous RF run.
## 1. Watcher gate
## 1. Coordination gate
The watcher must require all of the following:
Start the local model-free watcher before the RX task begins:
- RX TASK_ID = `RF001A_OTA_CAPTURE_004`;
- state = `RX_READY_FOR_TX`;
- source HEAD is the current `video-development` HEAD;
- COMMON_CLOCK_VERIFIED = YES;
- live child and growing capture are both true;
- planned RX duration is 59–61 seconds;
- READY is no more than 30 seconds old;
- at least 10 seconds remain in the capture window;
- the observed commit is the current valid `origin/rx-node` HEAD.
tools\start_tx_watcher.cmd
The watcher then requests exactly:
The watcher polls origin/rx-node every 5 seconds. It processes every new result-branch commit from `last_processed_rx_node_commit` to the current `origin/rx-node` tip in chronological order. It accepts only a strict result for:
RX TASK_ID = RF001A_OTA_CAPTURE_003
STATE = RX_READY_FOR_TX
RX_READY_FOR_TX = YES
PROCESS_ALIVE = YES
FILE_GROWING = YES
COMMON_CLOCK_VERIFIED = YES
RF_TX = 0
The watcher records RF_HANDOFF_CHANNEL_TEST_001 READY and COMPLETE states without prompting for RED approval and never treats them as production readiness. A production READY publication must be no more than 30 seconds old. Its planned capture window must be 120 seconds. At least 10 seconds must remain. COMMON_CLOCK_VERIFIED must be YES, source_head must equal the expected video-development HEAD, and the result commit must be the current valid origin/rx-node HEAD.
When a valid new READY result appears, the watcher prints:
RX IS READY
TASK ID =
CAPTURE STARTED =
CAPTURE END EXPECTED =
TX TASK =
RF001A_OTA_TX_003
RF TX REQUIRES RED APPROVAL
Enter:
AUTHORIZE RF TX
Without the exact phrase AUTHORIZE RF TX, do not transmit.
Any other response blocks TX. After the exact phrase, fetch `origin/rx-node`
again and repeat every commit, digest, task, source HEAD, freshness, common
clock and remaining-time check. A changed or stale result requires a new RX
capture and a new exact RED approval.
After the phrase, the watcher fetches rx-node again and repeats the result commit, digest, task ID, source HEAD, READY, freshness, common-clock and capture-window checks. If the result changed or less than 10 seconds remain, it refuses authorization and requires a new RX capture.
On success publish a schema-version-2 `TX_AUTHORIZED` event to `origin/tx-node`
with the exact source RX commit and digest, TX TASK_ID `RF001A_OTA_TX_004`,
`RF_TX_AUTHORIZED = YES`, `RF_TX_STARTED = NO` and `RF_TX = 0`. Also write the
local authorization record. This event is permission evidence, not proof of a
transmission.
The watcher never opens HackRF hardware. On success it writes one local authorization record under:
## 2. Read-only TX preflight
.local\tx_authorizations\
Before opening TX hardware:
The record has rf_tx_started = false and rf_tx = 0. It is the handoff to the separately controlled TX executor. It is not evidence that transmission occurred.
1. Run `git pull --ff-only origin video-development`.
2. Require branch `video-development`, a clean tracked worktree and local HEAD
equal to `origin/video-development`; `EXPECTED_HEAD` must be its ancestor.
3. Run `hackrf_info`; require TX serial
`000000000000000097c063dc323b97d3`.
4. Require the recorded MASTER CLKOUT command = PASS, both antennas connected,
approximately 2 m distance, clear LOS, and video disabled.
5. Verify the waveform without radio access:
## 2. Fixed RF-001A parameters
python -B -X utf8 tools/rf001a_prepare.py --verify-dir data/raw/rf001/rf001a_common_clock_001
center frequency = 1400000000 Hz
sample rate = 8000000 samples/s
modulation = QPSK
symbol rate = 500000 symbols/s
inner FEC = 2/3
payload = 256 bytes
packet count = 100
inter-packet gap = 5 ms
digital carrier offset = +125000 Hz
TX VGA = 20 dB
RF amp = OFF
antenna bias = OFF
common 10 MHz reference = VERIFIED
distance = approximately 2 m
LOS = YES
video = OFF
6. Require:
The waveform is one finite burst. Never add repeat mode.
## 3. Read-only preflight
Before the watcher can request RED approval:
1. Run git pull --ff-only origin video-development.
2. Require branch video-development.
3. Require git status --short --untracked-files=no to produce no output.
4. Require local HEAD to equal origin/video-development and EXPECTED_HEAD to be its ancestor.
5. Run hackrf_info and preserve complete stdout.
6. Require TX serial 000000000000000097c063dc323b97d3.
7. Require the recorded MASTER CLKOUT command result = PASS.
8. Require confirmation that both antennas are connected, the nodes are approximately 2 m apart, and LOS is clear.
9. Verify the prepared waveform without radio access:
python -B -X utf8 tools/rf001a_prepare.py --verify-dir data/raw/rf001/rf001a_common_clock_001
10. Verify the actual cs8 bytes through the existing offline receiver:
python -B -X utf8 tools/qpsk_rf_rx.py --iq-file data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8 --iq-format cs8 --truth-metadata configs/rf/rf001a_truth_metadata.json --report <new-path-outside-git>\rf001a_tx_offline_verify.json
Require:
canonical waveform = PASS
hardware-format cs8 = PASS
offline hardware-format roundtrip = 100/100 exact
TX waveform path = data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8
TX waveform size = 17667656 bytes
TX waveform SHA256 = 75f3fa39bfa4ee419f0676c54e4b9c486d7ff64bd0fe2fc06343b20c9d117ecd
sample count = 8833828 complex samples
expected duration = 1.1042285 s
waveform path = data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8
waveform size = 17667656 bytes
waveform SHA256 = 75f3fa39bfa4ee419f0676c54e4b9c486d7ff64bd0fe2fc06343b20c9d117ecd
sample count = 8833828 complex samples
duration = 1.1042285 s
offline exact packets = 100/100
If any value differs, do not transmit.
## 4. Final check before opening TX hardware
## 3. Final executor gate
The controlled TX executor must read the authorization record immediately before TX and require:
Immediately before hardware access, the controlled executor must read the
local authorization and re-fetch `origin/rx-node`. Require:
- task_id = RF001A_OTA_TX_003;
- authorization = AUTHORIZE RF TX;
- rx_task_id = RF001A_OTA_CAPTURE_003;
- dry_run = false;
- action = MANUAL_EXECUTOR_GATE_OPEN;
- rf_tx_started = false;
- rf_tx = 0;
- the authorization references the latest fetched rx-node result commit and digest;
- current time is still inside the READY capture window with at least 10 seconds remaining.
- TX TASK_ID = `RF001A_OTA_TX_004`;
- RX TASK_ID = `RF001A_OTA_CAPTURE_004`;
- authorization phrase = `AUTHORIZE RF TX`;
- authorization binds the current READY commit and digest;
- `rf_tx_started = false` and `rf_tx = 0` in the authorization record;
- at least 10 seconds remain in the same READY window.
Fetch origin/rx-node once more and compare its current result digest and commit with the authorization record. Re-run the READY and time checks. If anything differs, if the result is stale, or if fewer than 10 seconds remain, do not transmit and require a new RX capture and a new exact RED approval.
If any check fails, do not open HackRF.
## 5. One authorized TX execution
## 4. One finite TX burst
Only after every preceding condition passes may the executor create a new unique evidence directory outside Git and run exactly one finite command:
Only after all gates pass may the executor create a unique evidence directory
outside Git and run exactly one finite command:
hackrf_transfer.exe
-d 000000000000000097c063dc323b97d3
@@ -149,71 +103,48 @@ Only after every preceding condition passes may the executor create a new unique
-p 0
-B
Run it once, synchronously, and preserve:
Never add repeat mode and never retry automatically.
- exact process PID;
- start and end UTC timestamps;
- exact exit code;
- complete stdout and stderr;
- every TX underrun line;
- every USB error;
- waveform path, size and SHA256;
- command parameters.
At the exact process start, publish `TX_STARTED` to `origin/tx-node`, preserving
the authorization binding and recording `TX_STARTED_AT` as accurately as the
host permits. Set `RF_TX_STARTED = YES` and `RF_TX = 1` because this event is
evidence of an actual started process.
Set RF TX STOPPED = YES only after the finite process exits. Never retry automatically. Never start a second burst.
Write the strict schema-version-2 event JSON outside Git and publish it with:
## 6. Final TX report
python -B tools/tx_agent_watcher.py --publish-tx-event <started-json> --acknowledgement-branch tx-node
TASK_ID =
RF001A_OTA_TX_003
After that exact process exits, publish `TX_COMPLETE` with `TX_FINISHED_AT`,
the integer process exit code, underrun count and error count. Preserve the
complete stdout and stderr outside Git. The event reports what happened; it
does not by itself prove that packets were received. Captured IQ remains the
source for exact packet verification.
RED AUTHORIZATION =
AUTHORIZE RF TX / MISSING
Publish the completion JSON with the same `--publish-tx-event` command. The
publisher requires the matching `TX_AUTHORIZED` and `TX_STARTED` history.
RX READY FOR TX =
YES / NO
The only files permitted on `tx-node` are:
docs/handoff/results/TX_RESULT.json
docs/handoff/results/TX_RESULT.md
Do not publish waveform, IQ, video, source code, local state or large logs.
## 5. Final report
TASK_ID = RF001A_OTA_TX_004
RX READY FOR TX = YES / NO
RX RESULT HEAD =
RX RESULT DIGEST =
TX SERIAL =
TX WAVEFORM SHA256 =
TX START TIME =
TX END TIME =
TX EXIT CODE =
TX AUTHORIZED EVENT = PASS / FAIL
TX STARTED AT =
TX FINISHED AT =
TX PROCESS EXIT CODE =
TX UNDERRUNS =
TX ERRORS =
TX COMPLETE EVENT = PASS / FAIL
RF TX STOPPED = YES / NO
USB ERRORS =
RF TX STOPPED =
YES / NO
BURSTS TRANSMITTED =
0 / 1
RF TX =
0
At report time, RF TX = 0 means no transmission process remains active.
## 7. Prohibited actions
Do not:
- transmit without the fresh READY state and exact RED phrase;
- reuse an earlier authorization record;
- use repeat mode;
- transmit a second burst;
- retry automatically;
- change frequency, gains, clock configuration or waveform;
- update firmware or CPLD;
- use Zadig or change USB drivers;
- commit or push from this task.
STOP after the single finite burst or any failed gate.
STOP after the one finite burst and event publication. Firmware, CPLD, Zadig,
USB driver changes, clock reconfiguration, a second burst, commit or push to
`video-development` are forbidden.