Add bidirectional RF handoff coordination
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
TASK_ID = RF001A_OTA_TX_001
|
||||
TASK_TITLE = Transmit first RF-001A QPSK packet burst
|
||||
TASK_ID = RF001A_OTA_TX_002
|
||||
TASK_TITLE = Transmit one coordinated RF-001A QPSK burst
|
||||
SOURCE_BRANCH = video-development
|
||||
EXPECTED_HEAD = 1e3aa25d7007c7c8e84608cddb02f06f77e63396
|
||||
EXPECTED_HEAD = 34f5ace54540f947311ada7e81a463c08abfd2d3
|
||||
REQUIRES_APPROVAL = YES
|
||||
HARDWARE_ACCESS = YES
|
||||
RF_RX = NO
|
||||
@@ -11,205 +11,209 @@ ALLOW_PUSH = NO
|
||||
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 and current Git HEAD conflict, stop and report the conflict.
|
||||
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
|
||||
|
||||
TASK =
|
||||
TRANSMIT FIRST RF-001A QPSK PACKET BURST
|
||||
TRANSMIT ONE COORDINATED RF-001A QPSK BURST
|
||||
|
||||
This is a new RED task. It does not inherit the RX task approval. Publication of this file is not permission to transmit.
|
||||
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, the RX task, a previous watcher prompt or a previous RF run.
|
||||
|
||||
Before any RF TX, the user must:
|
||||
## 1. Coordination gate
|
||||
|
||||
1. separately provide the exact authorization `AUTHORIZE RF TX`;
|
||||
2. confirm the live RX status `RX READY FOR TX = YES` for task `RF001A_OTA_CAPTURE_001`.
|
||||
Start the local model-free watcher before the RX task begins:
|
||||
|
||||
A normal approval, an earlier RF authorization, or an RX approval is insufficient.
|
||||
tools\start_tx_watcher.cmd
|
||||
|
||||
## 1. Fixed RF-001A parameters
|
||||
The watcher polls origin/rx-node every 5 seconds and accepts only a strict result for:
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
RX TASK_ID = RF001A_OTA_CAPTURE_002
|
||||
STATE = RX_READY_FOR_TX
|
||||
RX_READY_FOR_TX = YES
|
||||
PROCESS_ALIVE = YES
|
||||
FILE_GROWING = YES
|
||||
COMMON_CLOCK_VERIFIED = YES
|
||||
RF_TX = 0
|
||||
|
||||
The waveform is one finite burst. Never add `-R`.
|
||||
The READY publication must be no more than 30 seconds old. Its planned capture window must be 120 seconds. At least 10 seconds must remain.
|
||||
|
||||
## 2. Preconditions
|
||||
When a valid new READY result appears, the watcher prints:
|
||||
|
||||
Before asking for RED authorization, prepare all read-only evidence:
|
||||
RX IS READY
|
||||
|
||||
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 the user to confirm both TX and RX antennas connected, approximately 2 m LOS and the intended antenna geometry.
|
||||
9. Require the current RX report to show `RX READY FOR TX = YES`, task ID `RF001A_OTA_CAPTURE_001`, a live capture PID, capture path, start time and expected end time.
|
||||
10. Require current time to be before the RX expected end time.
|
||||
TASK ID =
|
||||
CAPTURE STARTED =
|
||||
CAPTURE END EXPECTED =
|
||||
|
||||
Verify the prepared waveform without accessing radio hardware:
|
||||
TX TASK =
|
||||
RF001A_OTA_TX_002
|
||||
|
||||
```powershell
|
||||
python -B -X utf8 tools/rf001a_prepare.py --verify-dir data/raw/rf001/rf001a_common_clock_001
|
||||
```
|
||||
RF TX REQUIRES RED APPROVAL
|
||||
|
||||
Then verify the actual hardware-format bytes through the existing receiver. The report path must be new and outside Git:
|
||||
Enter:
|
||||
AUTHORIZE RF TX
|
||||
|
||||
```powershell
|
||||
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 data/raw/rf001/rf001a_common_clock_001/tx_metadata.json `
|
||||
--report '<new unique path outside Git>\rf001a_tx_offline_verify.json'
|
||||
```
|
||||
Without the exact phrase AUTHORIZE RF TX, do not transmit.
|
||||
|
||||
Require the receiver report to contain `crc_valid_packets = 100`, `exact_packets = 100`, no missing packets and no duplicates.
|
||||
After the phrase, the watcher fetches rx-node again and repeats all READY, freshness and capture-window checks. If the result changed or less than 10 seconds remain, it refuses authorization and requires a new RX capture.
|
||||
|
||||
The watcher never opens HackRF hardware. On success it writes one local authorization record under:
|
||||
|
||||
.local\tx_authorizations\
|
||||
|
||||
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.
|
||||
|
||||
## 2. Fixed RF-001A parameters
|
||||
|
||||
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
|
||||
|
||||
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 data/raw/rf001/rf001a_common_clock_001/tx_metadata.json --report <new-path-outside-git>\rf001a_tx_offline_verify.json
|
||||
|
||||
Require:
|
||||
|
||||
```text
|
||||
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 = 75f3fa39bfa88b623b7e4e5a02e3a80b52185007397ea56ceaacc13353974b2
|
||||
sample count = 8833828 complex samples
|
||||
expected duration = 1.1042285 s
|
||||
```
|
||||
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 = 75f3fa39bfa88b623b7e4e5a02e3a80b52185007397ea56ceaacc13353974b2
|
||||
sample count = 8833828 complex samples
|
||||
expected duration = 1.1042285 s
|
||||
|
||||
If any value differs, if RX is not ready, or if the RX capture window has ended, do not transmit and STOP.
|
||||
If any value differs, do not transmit.
|
||||
|
||||
Only after every precondition passes may the user provide the separate exact phrase `AUTHORIZE RF TX`.
|
||||
## 4. Final check before opening TX hardware
|
||||
|
||||
## 3. One authorized TX execution
|
||||
The controlled TX executor must read the authorization record immediately before TX and require:
|
||||
|
||||
After the separate RED authorization and the live `RX READY FOR TX = YES` confirmation, create a new unique local evidence directory outside Git. Refuse an existing directory and never overwrite earlier evidence.
|
||||
- task_id = RF001A_OTA_TX_002;
|
||||
- authorization = AUTHORIZE RF TX;
|
||||
- rx_task_id = RF001A_OTA_CAPTURE_002;
|
||||
- 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.
|
||||
|
||||
Use this single finite command:
|
||||
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.
|
||||
|
||||
```text
|
||||
hackrf_transfer.exe
|
||||
-d 000000000000000097c063dc323b97d3
|
||||
-t data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8
|
||||
-f 1400000000
|
||||
-s 8000000
|
||||
-n 8833828
|
||||
-x 20
|
||||
-a 0
|
||||
-p 0
|
||||
-B
|
||||
```
|
||||
## 5. One authorized TX execution
|
||||
|
||||
Run it once, synchronously, and preserve the exact process exit code:
|
||||
Only after every preceding condition passes may the executor create a new unique evidence directory outside Git and run exactly one finite command:
|
||||
|
||||
```powershell
|
||||
$txRunId = '{0}_{1}' -f (Get-Date -Format 'yyyyMMdd_HHmmss'), ([guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
$txEvidenceRoot = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'SDR_Rover_TX_Evidence'
|
||||
$txEvidenceDirectory = Join-Path $txEvidenceRoot "RF001A_OTA_TX_001_$txRunId"
|
||||
if (Test-Path -LiteralPath $txEvidenceDirectory) { throw "TX evidence directory already exists: $txEvidenceDirectory" }
|
||||
New-Item -ItemType Directory -Path $txEvidenceDirectory -ErrorAction Stop | Out-Null
|
||||
hackrf_transfer.exe
|
||||
-d 000000000000000097c063dc323b97d3
|
||||
-t data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8
|
||||
-f 1400000000
|
||||
-s 8000000
|
||||
-n 8833828
|
||||
-x 20
|
||||
-a 0
|
||||
-p 0
|
||||
-B
|
||||
|
||||
$hackrfTransfer = 'C:\Users\user\Tools\HackRF\Library\bin\hackrf_transfer.exe'
|
||||
$waveformPath = (Resolve-Path -LiteralPath 'data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8').Path
|
||||
$stdoutPath = Join-Path $txEvidenceDirectory 'hackrf_transfer.stdout.log'
|
||||
$stderrPath = Join-Path $txEvidenceDirectory 'hackrf_transfer.stderr.log'
|
||||
$txArguments = @(
|
||||
'-d', '000000000000000097c063dc323b97d3',
|
||||
'-t', $waveformPath,
|
||||
'-f', '1400000000',
|
||||
'-s', '8000000',
|
||||
'-n', '8833828',
|
||||
'-x', '20',
|
||||
'-a', '0',
|
||||
'-p', '0',
|
||||
'-B'
|
||||
)
|
||||
Run it once, synchronously, and preserve:
|
||||
|
||||
$txStartedAt = [DateTimeOffset]::Now
|
||||
$txProcess = Start-Process -FilePath $hackrfTransfer -ArgumentList $txArguments `
|
||||
-RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath `
|
||||
-PassThru -Wait -WindowStyle Hidden
|
||||
$txFinishedAt = [DateTimeOffset]::Now
|
||||
$txProcess.Refresh()
|
||||
$txExitCode = $txProcess.ExitCode
|
||||
```
|
||||
- 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.
|
||||
|
||||
Immediately write the exit code, process ID, timestamps, duration, command parameters, waveform size and waveform SHA256 to a new JSON metadata file in `$txEvidenceDirectory`. Preserve complete stdout and stderr.
|
||||
Set RF TX STOPPED = YES only after the finite process exits. Never retry automatically. Never start a second burst.
|
||||
|
||||
Inspect every buffer-statistics line. Record:
|
||||
## 6. Final TX report
|
||||
|
||||
```text
|
||||
TX start time =
|
||||
TX end time =
|
||||
TX PID =
|
||||
TX exit code =
|
||||
TX underruns =
|
||||
USB errors =
|
||||
RF TX STOPPED =
|
||||
YES / NO
|
||||
```
|
||||
TASK_ID =
|
||||
RF001A_OTA_TX_002
|
||||
|
||||
Do not infer zero from a missing counter. Use `NOT_REPORTED` when no counter exists. Any nonzero intermediate or final underrun/error line remains evidence even if a later line reports zero.
|
||||
RED AUTHORIZATION =
|
||||
AUTHORIZE RF TX / MISSING
|
||||
|
||||
Set `RF TX STOPPED = YES` only after the finite process exits. Do not start a second burst for any reason. Do not retry automatically.
|
||||
RX READY FOR TX =
|
||||
YES / NO
|
||||
|
||||
## 4. Final TX report
|
||||
RX RESULT HEAD =
|
||||
|
||||
```text
|
||||
TASK_ID =
|
||||
RF001A_OTA_TX_001
|
||||
RX RESULT DIGEST =
|
||||
|
||||
RED AUTHORIZATION =
|
||||
AUTHORIZE RF TX / MISSING
|
||||
TX SERIAL =
|
||||
|
||||
RX READY FOR TX =
|
||||
YES / NO
|
||||
TX WAVEFORM SHA256 =
|
||||
|
||||
TX SERIAL =
|
||||
TX START TIME =
|
||||
|
||||
TX WAVEFORM SHA256 =
|
||||
TX END TIME =
|
||||
|
||||
TX START TIME =
|
||||
TX EXIT CODE =
|
||||
|
||||
TX END TIME =
|
||||
TX UNDERRUNS =
|
||||
|
||||
TX EXIT CODE =
|
||||
USB ERRORS =
|
||||
|
||||
TX UNDERRUNS =
|
||||
RF TX STOPPED =
|
||||
YES / NO
|
||||
|
||||
USB ERRORS =
|
||||
BURSTS TRANSMITTED =
|
||||
0 / 1
|
||||
|
||||
RF TX STOPPED =
|
||||
YES / NO
|
||||
RF TX =
|
||||
0
|
||||
|
||||
BURSTS TRANSMITTED =
|
||||
0 / 1
|
||||
At report time, RF TX = 0 means no transmission process remains active.
|
||||
|
||||
RF TX =
|
||||
0
|
||||
```
|
||||
## 7. Prohibited actions
|
||||
|
||||
At report time, `RF TX = 0` means no transmission process remains active.
|
||||
Do not:
|
||||
|
||||
## 5. Prohibited actions
|
||||
- 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.
|
||||
|
||||
Do not transmit before the separate exact RED authorization. Do not inherit approval from the RX task. Do not use repeat mode, start a second burst, change frequency, gains, clock configuration, waveform, firmware, CPLD, Zadig or USB drivers. Do not commit or push from this task.
|
||||
STOP after the single finite burst or any failed gate.
|
||||
|
||||
Reference in New Issue
Block a user