Prepare first RF-001A OTA run

This commit is contained in:
LittleSam129
2026-09-21 15:55:49 +03:00
parent 1e3aa25d70
commit 34f5ace545
2 changed files with 472 additions and 201 deletions

View File

@@ -1,7 +1,7 @@
TASK_ID = RF001A_VERIFY_AND_CAPTURE_001 TASK_ID = RF001A_OTA_CAPTURE_001
TASK_TITLE = Verify RF-001A common clock and start IQ capture TASK_TITLE = Capture first RF-001A QPSK OTA transmission
SOURCE_BRANCH = video-development SOURCE_BRANCH = video-development
EXPECTED_HEAD = fa370ccfa23dedc633346d688547e9c1c77ff8eb EXPECTED_HEAD = 1e3aa25d7007c7c8e84608cddb02f06f77e63396
REQUIRES_APPROVAL = YES REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES HARDWARE_ACCESS = YES
RF_RX = YES RF_RX = YES
@@ -12,95 +12,59 @@ RISK_LEVEL = YELLOW
This file is the authoritative current task for the RX laptop. This file is the authoritative current task for the RX laptop.
The RX assistant must read RX_NODE_STATE.md first, then execute this file. The RX assistant must read RX_NODE_STATE.md first, then execute this file.
If chat history conflicts with these files and current Git HEAD, stop and report the conflict. If chat history conflicts with these files, the shared RF-001A spec or current Git HEAD, stop and report the conflict.
# Current RX task # Current RX task
TASK = TASK =
VERIFY RF-001A COMMON CLOCK AND START IQ CAPTURE CAPTURE FIRST RF-001A QPSK OTA TRANSMISSION
This is one combined YELLOW task. One explicit `[A] Approve` authorizes the bounded CLKIN verification, the bounded short RX-start and, only after that gate passes, the finite 20-second RX capture. Do not request a second approval between those steps. This is a new YELLOW task. Do not reuse approval or completion state from any earlier task ID.
Do not reuse the approval, state or completion record from either old task ID: ## Accepted preflight evidence
The user supplied these completed preflight results:
```text ```text
RF001A_CLKIN_001 COMMON CLOCK VERIFIED = YES
RF001A_CAPTURE_001 CLKIN BEFORE SHORT RX = DETECTED
SHORT RX = PASS, 65536 samples, overruns 0
CLKIN AFTER SHORT RX = DETECTED
CLKIN AFTER 20 s CAPTURE = DETECTED
CAPTURE SIZE = 320000000 bytes
CAPTURE DURATION = 20.00486 s
RX OVERRUNS = 0
USB ERRORS = 0
FILE ERRORS = 0
RAW IQ SHA256 = c0abdc2a41a0265a8037b4a5296d20b843eb75bcee83a6f2fac5813a533b7147
RX HARDWARE PREFLIGHT = PASS WITH PROCESS-EXIT-CODE OBSERVABILITY LIMITATION
``` ```
The exact child process exit code was not retained. Do not repeat the 20-second preflight solely to recover that missing value. This task fixes exit-code observability for the new OTA capture.
## 1. Preconditions ## 1. Preconditions
After approval, run `git pull --ff-only origin video-development`, then verify: After one explicit `[A] Approve` for this task:
- branch = `video-development`; 1. Run `git pull --ff-only origin video-development`.
- `git status --short --untracked-files=no` produces no output; 2. Require branch `video-development`.
- local HEAD equals `origin/video-development` and `EXPECTED_HEAD` is its ancestor; 3. Require `git status --short --untracked-files=no` to produce no output.
- `hackrf_info` = PASS; 4. Require local HEAD to equal `origin/video-development` and `EXPECTED_HEAD` to be its ancestor.
- RX serial = `000000000000000097c063dc2e5327d3`; 5. Run `hackrf_info` and preserve complete stdout.
- the physical cable is connected from MASTER CLKOUT to SLAVE CLKIN; 6. Require RX serial `000000000000000097c063dc2e5327d3`.
- the existing hardware-independent shared RF-001A spec test passes: 7. Run the official read-only clock check and preserve complete stdout:
```powershell
python -B -m pytest tests/test_rf001a_prepare.py -q -p no:cacheprovider --basetemp .local/pytest-rf001a-verify-capture
```
If any precondition fails, do not start the short RX or the 20-second capture. Report the failed condition, keep `RF TX = 0`, and STOP.
## 2. Verify SLAVE CLKIN before RX
Run the official read-only partner-side check with the explicit RX serial and preserve its complete stdout:
```powershell ```powershell
& '<verified hackrf_clock.exe path>' -d 000000000000000097c063dc2e5327d3 -i & '<verified hackrf_clock.exe path>' -d 000000000000000097c063dc2e5327d3 -i
``` ```
Accept only the exact line: Require the exact line `CLKIN status: clock signal detected`. If it is absent, do not start the capture; keep `RF TX = 0` and STOP. Do not reconfigure the clock.
```text Create a new unique capture directory outside the Git checkout. Refuse any existing directory and never overwrite an earlier capture. Before starting, require at least `1100000000` free bytes on the target volume and record the measured free-byte count.
CLKIN status: clock signal detected
```
Set `SLAVE CLKIN BEFORE RX = DETECTED` only when that line is present. Otherwise set `COMMON CLOCK VERIFIED = NO`, do not start the short RX or capture, keep `RF TX = 0`, and STOP. ## 2. Authorized finite RX capture
## 3. Bounded short RX-start Only this capture is authorized:
If SLAVE CLKIN was detected, perform one short, finite RX-start on the same device:
```powershell
& '<verified hackrf_transfer.exe path>' -d 000000000000000097c063dc2e5327d3 -r NUL -f 1400000000 -s 8000000 -n 65536 -l 0 -g 0 -a 0 -p 0 -B
```
This is RX only. It must not transmit. Require:
- exit code = `0`;
- exactly `65536` complex samples transferred;
- RX overruns = `0`;
- USB errors = `0`;
- file write errors = `0`.
If any condition fails, set `SHORT RX START = FAIL` and `COMMON CLOCK VERIFIED = NO`. Do not start the 20-second capture. Keep `RF TX = 0` and STOP.
## 4. Verify SLAVE CLKIN after RX
Immediately repeat the same read-only check and preserve its complete stdout:
```powershell
& '<verified hackrf_clock.exe path>' -d 000000000000000097c063dc2e5327d3 -i
```
Accept only the exact line `CLKIN status: clock signal detected`.
Set `COMMON CLOCK VERIFIED = YES` only when all three conditions pass:
- `SLAVE CLKIN BEFORE RX = DETECTED`;
- `SHORT RX START = PASS`;
- `SLAVE CLKIN AFTER RX = DETECTED`.
If CLKIN is not detected after the short RX-start, set `COMMON CLOCK VERIFIED = NO`, do not start the capture, keep `RF TX = 0`, and STOP. Do not alter clock configuration automatically.
## 5. Authorized finite RX capture
When and only when `COMMON CLOCK VERIFIED = YES`, continue immediately under this same task ID and the same YELLOW approval. Start this finite RX capture:
```text ```text
RX serial = 000000000000000097c063dc2e5327d3 RX serial = 000000000000000097c063dc2e5327d3
@@ -110,23 +74,21 @@ RX LNA = 24 dB
RX VGA = 24 dB RX VGA = 24 dB
RF amp = OFF RF amp = OFF
antenna bias = OFF antenna bias = OFF
duration = 20 s duration = 60 s
sample count = 160000000 complex samples sample count = 480000000 complex samples
hardware format = cs8 hardware format = cs8
expected file size = 320000000 bytes expected file size = 960000000 bytes
common clock = DETECTED
``` ```
Create a new directory outside the Git checkout for every attempt. Use a timestamp plus a random suffix, refuse an existing path, and never overwrite an earlier capture. Keep the IQ file, stdout, stderr and metadata there. Do not add them to Git. The required `hackrf_transfer.exe` arguments are:
The required capture arguments are:
```text ```text
hackrf_transfer.exe
-d 000000000000000097c063dc2e5327d3 -d 000000000000000097c063dc2e5327d3
-r <unique_capture_path>\rf001a_capture.cs8 -r <unique_capture_path>\rf001a_ota_capture.cs8
-f 1400000000 -f 1400000000
-s 8000000 -s 8000000
-n 160000000 -n 480000000
-l 24 -l 24
-g 24 -g 24
-a 0 -a 0
@@ -134,154 +96,290 @@ hackrf_transfer.exe
-B -B
``` ```
## 6. Controlled background start ## 3. Durable process and exit-code recording
Keep one PowerShell session open for the start and completion sequence. Resolve the verified local path to `hackrf_transfer.exe`, then start the controlled background process: Use a local runner outside Git. The runner owns the `hackrf_transfer` process, records its actual PID immediately, waits for that exact child, and writes the exact child `ExitCode` to an atomic result JSON file. This remains observable even if the assistant turn or PowerShell parent that started the runner ends.
Prepare paths:
```powershell ```powershell
$runId = '{0}_{1}' -f (Get-Date -Format 'yyyyMMdd_HHmmss'), ([guid]::NewGuid().ToString('N').Substring(0, 8)) $runId = '{0}_{1}' -f (Get-Date -Format 'yyyyMMdd_HHmmss'), ([guid]::NewGuid().ToString('N').Substring(0, 8))
$captureRoot = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'SDR_Rover_Captures' $captureRoot = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'SDR_Rover_Captures'
$captureDirectory = Join-Path $captureRoot "RF001A_VERIFY_AND_CAPTURE_001_$runId" $captureDirectory = Join-Path $captureRoot "RF001A_OTA_CAPTURE_001_$runId"
if (Test-Path -LiteralPath $captureDirectory) { throw "Capture directory already exists: $captureDirectory" } if (Test-Path -LiteralPath $captureDirectory) { throw "Capture directory already exists: $captureDirectory" }
New-Item -ItemType Directory -Path $captureDirectory -ErrorAction Stop | Out-Null New-Item -ItemType Directory -Path $captureDirectory -ErrorAction Stop | Out-Null
$capturePath = Join-Path $captureDirectory 'rf001a_capture.cs8' $targetRoot = [System.IO.Path]::GetPathRoot([System.IO.Path]::GetFullPath($captureDirectory))
$targetDrive = Get-PSDrive -Name $targetRoot.Substring(0, 1)
if ($targetDrive.Free -lt 1100000000) { throw "Insufficient free space: $($targetDrive.Free) bytes" }
$capturePath = Join-Path $captureDirectory 'rf001a_ota_capture.cs8'
$stdoutPath = Join-Path $captureDirectory 'hackrf_transfer.stdout.log' $stdoutPath = Join-Path $captureDirectory 'hackrf_transfer.stdout.log'
$stderrPath = Join-Path $captureDirectory 'hackrf_transfer.stderr.log' $stderrPath = Join-Path $captureDirectory 'hackrf_transfer.stderr.log'
$startedStatusPath = Join-Path $captureDirectory 'capture_started.json'
$resultStatusPath = Join-Path $captureDirectory 'capture_result.json'
$runnerPath = Join-Path $captureDirectory 'capture_runner.ps1'
$runnerStdoutPath = Join-Path $captureDirectory 'capture_runner.stdout.log'
$runnerStderrPath = Join-Path $captureDirectory 'capture_runner.stderr.log'
$hackrfTransfer = '<verified absolute path to hackrf_transfer.exe>' $hackrfTransfer = '<verified absolute path to hackrf_transfer.exe>'
$startedAt = Get-Date ```
$captureArguments = @(
Write this runner to `$runnerPath`:
```powershell
param(
[Parameter(Mandatory = $true)][string]$HackrfTransfer,
[Parameter(Mandatory = $true)][string]$CapturePath,
[Parameter(Mandatory = $true)][string]$StdoutPath,
[Parameter(Mandatory = $true)][string]$StderrPath,
[Parameter(Mandatory = $true)][string]$StartedStatusPath,
[Parameter(Mandatory = $true)][string]$ResultStatusPath
)
function Write-JsonAtomically {
param([Parameter(Mandatory = $true)][object]$Value,
[Parameter(Mandatory = $true)][string]$Path)
$temporaryPath = "$Path.tmp"
$Value | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $temporaryPath -Encoding Ascii
Move-Item -LiteralPath $temporaryPath -Destination $Path -Force
}
$arguments = @(
'-d', '000000000000000097c063dc2e5327d3', '-d', '000000000000000097c063dc2e5327d3',
'-r', $capturePath, '-r', $CapturePath,
'-f', '1400000000', '-f', '1400000000',
'-s', '8000000', '-s', '8000000',
'-n', '160000000', '-n', '480000000',
'-l', '24', '-l', '24',
'-g', '24', '-g', '24',
'-a', '0', '-a', '0',
'-p', '0', '-p', '0',
'-B' '-B'
) )
$captureProcess = Start-Process -FilePath $hackrfTransfer -ArgumentList $captureArguments `
-RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath `
-PassThru -WindowStyle Hidden
$growthDeadline = (Get-Date).AddSeconds(2) $startedAt = [DateTimeOffset]::Now
do { $transferProcess = $null
Start-Sleep -Milliseconds 100 $exitCode = $null
$captureProcess.Refresh() $runnerError = $null
$captureBytes = if (Test-Path -LiteralPath $capturePath) {
(Get-Item -LiteralPath $capturePath).Length try {
$transferProcess = Start-Process -FilePath $HackrfTransfer -ArgumentList $arguments `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath `
-PassThru -WindowStyle Hidden
Write-JsonAtomically -Path $StartedStatusPath -Value ([ordered]@{
state = 'RUNNING'
capture_pid = $transferProcess.Id
started_at = $startedAt.ToString('o')
capture_path = $CapturePath
})
$transferProcess.WaitForExit()
$transferProcess.Refresh()
$exitCode = $transferProcess.ExitCode
} catch {
$runnerError = $_.Exception.ToString()
} finally {
$finishedAt = [DateTimeOffset]::Now
$captureSize = if (Test-Path -LiteralPath $CapturePath) {
(Get-Item -LiteralPath $CapturePath).Length
} else { } else {
0 0
} }
} until ($captureProcess.HasExited -or $captureBytes -gt 0 -or (Get-Date) -ge $growthDeadline)
$sha256 = $null
try {
if (Test-Path -LiteralPath $CapturePath) {
$sha256 = (Get-FileHash -LiteralPath $CapturePath -Algorithm SHA256).Hash.ToLowerInvariant()
}
} catch {
$errorParts = @($runnerError, $_.Exception.ToString()) | Where-Object { $null -ne $_ -and $_ -ne '' }
$runnerError = $errorParts -join [Environment]::NewLine
}
Write-JsonAtomically -Path $ResultStatusPath -Value ([ordered]@{
state = 'COMPLETE'
capture_pid = if ($null -ne $transferProcess) { $transferProcess.Id } else { $null }
started_at = $startedAt.ToString('o')
finished_at = $finishedAt.ToString('o')
duration_seconds = ($finishedAt - $startedAt).TotalSeconds
exit_code = $exitCode
capture_path = $CapturePath
capture_size_bytes = $captureSize
sha256 = $sha256
runner_error = $runnerError
})
}
if ($null -eq $exitCode) { exit 255 }
exit $exitCode
``` ```
The start passes only if the process remains active and the capture file exists and has grown above zero bytes. If either check fails, report `RX CAPTURE STARTED = NO`, `RX READY FOR TX = NO`, preserve the logs, keep `RF TX = 0`, and STOP. Start the runner as a controlled background process. Quote every path passed through `-ArgumentList`:
When both checks pass, immediately output:
```text
TASK_ID =
RF001A_VERIFY_AND_CAPTURE_001
COMMON CLOCK VERIFIED =
YES
RX CAPTURE STARTED =
YES
RX READY FOR TX =
YES
CAPTURE PID =
CAPTURE PATH =
RF TX =
0
```
Do not wait for capture completion, calculate SHA256, or perform another slow action before sending this status. `RX READY FOR TX = YES` only reports that the RX capture is active. It does not authorize RF TX on either computer. RF TX on the RX laptop remains forbidden.
## 7. Capture completion
Only after sending the immediate start status, wait for the same process in the same PowerShell session:
```powershell ```powershell
$captureProcess.WaitForExit() $currentPowerShell = (Get-Process -Id $PID).Path
$finishedAt = Get-Date $runnerArguments = @(
$captureProcess.Refresh() '-NoLogo',
$exitCode = $captureProcess.ExitCode '-NoProfile',
$captureSize = (Get-Item -LiteralPath $capturePath).Length '-NonInteractive',
$captureDurationSeconds = ($finishedAt - $startedAt).TotalSeconds '-ExecutionPolicy', 'Bypass',
$captureHash = (Get-FileHash -LiteralPath $capturePath -Algorithm SHA256).Hash.ToLowerInvariant() '-File', ('"{0}"' -f $runnerPath),
'-HackrfTransfer', ('"{0}"' -f $hackrfTransfer),
'-CapturePath', ('"{0}"' -f $capturePath),
'-StdoutPath', ('"{0}"' -f $stdoutPath),
'-StderrPath', ('"{0}"' -f $stderrPath),
'-StartedStatusPath', ('"{0}"' -f $startedStatusPath),
'-ResultStatusPath', ('"{0}"' -f $resultStatusPath)
)
$runnerProcess = Start-Process -FilePath $currentPowerShell -ArgumentList $runnerArguments `
-RedirectStandardOutput $runnerStdoutPath -RedirectStandardError $runnerStderrPath `
-PassThru -WindowStyle Hidden
``` ```
Inspect the saved stdout and stderr. Record the exit code, actual file size, actual duration, RX overruns, USB errors, file write errors and raw IQ SHA256. Wait only long enough to prove that the child process is alive and the file is growing:
The capture is complete only if: ```powershell
$startDeadline = (Get-Date).AddSeconds(5)
$startedStatus = $null
$captureBytes = 0
$captureAlive = $false
- exit code = `0`; do {
- file size = exactly `320000000` bytes; Start-Sleep -Milliseconds 100
- RX overruns = `0`; $runnerProcess.Refresh()
- USB errors = `0`; if (Test-Path -LiteralPath $startedStatusPath) {
- file write errors = `0`. $startedStatus = Get-Content -Raw -LiteralPath $startedStatusPath | ConvertFrom-Json
$captureProcess = Get-Process -Id ([int]$startedStatus.capture_pid) -ErrorAction SilentlyContinue
$captureAlive = $null -ne $captureProcess
}
if (Test-Path -LiteralPath $capturePath) {
$captureBytes = (Get-Item -LiteralPath $capturePath).Length
}
} until ($runnerProcess.HasExited -or ($captureAlive -and $captureBytes -gt 0) -or (Get-Date) -ge $startDeadline)
```
A smaller file is incomplete. Preserve the evidence and do not retry automatically. Write machine-readable metadata to a new file in `$captureDirectory`, including the task ID, RX serial, requested settings, clock results, PID, timestamps, exit code, size, duration, error counts and SHA256. The metadata and logs remain outside Git. The start passes only when:
Finish with: ```text
runner process alive = YES
hackrf_transfer child alive = YES
capture file created = YES
capture file growing = YES
```
If any check fails, report `RX CAPTURE STARTED = NO`, preserve all local files, keep `RF TX = 0`, and STOP.
## 4. Immediate READY report
When all start checks pass, immediately return control to the user with:
```text ```text
TASK_ID = TASK_ID =
RF001A_VERIFY_AND_CAPTURE_001 RF001A_OTA_CAPTURE_001
SLAVE CLKIN BEFORE RX =
DETECTED / NOT_DETECTED
SHORT RX START =
PASS / FAIL / NOT_RUN
SLAVE CLKIN AFTER RX =
DETECTED / NOT_DETECTED / NOT_RUN
COMMON CLOCK VERIFIED =
YES / NO
RX CAPTURE STARTED = RX CAPTURE STARTED =
YES / NO YES
RX READY FOR TX = RX READY FOR TX =
YES / NO YES
CAPTURE PID = CAPTURE PID =
<actual hackrf_transfer child PID>
CAPTURE PATH = CAPTURE PATH =
<absolute path>
RX CAPTURE COMPLETE = CAPTURE START TIME =
YES / NO / NOT_RUN <started_status started_at>
CAPTURE SIZE = EXPECTED END TIME =
<started_at plus 60 seconds>
CAPTURE DURATION =
RX OVERRUNS =
USB ERRORS =
FILE WRITE ERRORS =
RAW IQ SHA256 =
RF TX = RF TX =
0 0
STOP.
``` ```
## 8. Prohibited actions Do not wait for the 60-second capture to finish before sending `RX READY FOR TX = YES`. This status means only that RX is actively recording. It does not authorize RF TX on the RX laptop or MAIN PC.
On the RX laptop, do not perform RF TX, firmware or CPLD updates, Zadig operations, USB driver changes, clock reconfiguration, continuous RX, automatic retries, commits to `video-development`, or pushes to `video-development`. ## 5. Completion evidence
`ALLOW_PUSH = RX_RESULT_ONLY` permits only a separately approved small result publication under the handoff protocol. It does not permit raw IQ, large logs, a commit to `video-development`, or a push to `video-development`. After the capture window, read `capture_result.json`. The exact child process exit code must be present. Also preserve complete stdout, stderr and runner logs.
Require and report:
- process exit code;
- actual capture size;
- actual duration;
- SHA256;
- every RX overrun counter or event from the complete logs;
- every USB error;
- every file write error;
- CLKIN status after capture.
Run the read-only clock check again. Require `CLKIN status: clock signal detected`.
The capture is complete only when:
```text
exit code = 0
capture size = 960000000 bytes
duration = consistent with 60 s
RX overruns = 0
USB errors = 0
file write errors = 0
CLKIN after capture = DETECTED
```
Do not infer zero from a missing counter. Use `NOT_REPORTED` when the tool did not report a value. Treat every nonzero intermediate or final overrun/error line as evidence; do not keep only the last counter. Do not retry automatically.
## 6. Offline decode
Use the saved OTA IQ without changing the receiver for this capture:
```powershell
python -B -X utf8 tools/qpsk_rf_rx.py `
--iq-file '<absolute capture path>' `
--iq-format cs8 `
--truth-metadata data/raw/rf001/rf001a_common_clock_001/tx_metadata.json `
--report '<new unique path outside Git>\rf001a_ota_decode.json'
```
Require the existing truth metadata for the transmitted waveform. If it is absent or inconsistent, preserve the IQ file and report `OFFLINE DECODE = BLOCKED_BY_TRUTH_METADATA`; do not alter the receiver or invent results.
Report at minimum:
```text
DETECTED PREAMBLES =
CRC VALID PACKETS =
EXACT PACKETS =
MISSING PACKETS =
DUPLICATES =
OUT OF ORDER =
RAW BER =
RAW BER SCOPE =
POST-FEC ERROR RATE =
CFO MEDIAN =
CFO P95 =
TIMING OFFSET =
TIMING DRIFT =
RX CLIPPING =
```
Use the existing receiver JSON fields directly. Derive `OUT OF ORDER` only from `packet_sequences`. Derive timing summaries only from `phase_and_timing_estimates`; if the available data does not support a value, report `NOT_MEASURED`. Do not fabricate a metric.
Classify this first smoke test:
```text
99-100 exact = GOOD
95-98 exact = PARTIAL / DIAGNOSTIC
<95 exact = FAIL FOR VIDEO PROGRESSION
```
One run is not statistical reliability evidence.
## 7. Prohibited actions
On the RX laptop, do not perform RF TX, firmware or CPLD updates, Zadig operations, USB driver changes, clock reconfiguration, continuous RX, automatic capture retries, commits to `video-development`, or pushes to `video-development`.
`ALLOW_PUSH = RX_RESULT_ONLY` permits only a separately approved small result publication under the handoff protocol. Raw IQ, large logs and local runner files remain outside Git.

View File

@@ -1,42 +1,215 @@
This file is the authoritative current task for the main PC TX node. TASK_ID = RF001A_OTA_TX_001
Read TX_NODE_STATE.md first. If chat history, this file, the shared spec, and current Git HEAD conflict, stop and report the conflict. TASK_TITLE = Transmit first RF-001A QPSK packet burst
SOURCE_BRANCH = video-development
EXPECTED_HEAD = 1e3aa25d7007c7c8e84608cddb02f06f77e63396
REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES
RF_RX = NO
RF_TX = YES
ALLOW_COMMIT = NO
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.
# Current TX task # Current TX task
TASK = TASK =
WAIT FOR RF-001A SLAVE CLKIN VERIFICATION TRANSMIT FIRST RF-001A QPSK PACKET BURST
MASTER SERIAL = This is a new RED task. It does not inherit the RX task approval. Publication of this file is not permission to transmit.
000000000000000097c063dc323b97d3
MASTER CLKOUT COMMAND = Before any RF TX, the user must:
PASS
The official source-side command completed with exit code 0: 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`.
```powershell A normal approval, an earlier RF authorization, or an RX approval is insufficient.
hackrf_clock.exe -d 000000000000000097c063dc323b97d3 -o 1
## 1. Fixed RF-001A parameters
```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
``` ```
The earlier `hackrf_clock -r 3` output and register 29 are diagnostic information only. They do not prove whether the physical MASTER output reaches SLAVE. Do not use them as blocking criteria for common-clock verification. The waveform is one finite burst. Never add `-R`.
The common clock remains unverified until RX laptop task `VERIFY RF-001A SLAVE CLKIN` reports the exact `CLKIN status: clock signal detected` line both before and after its finite 65536-sample RX-start. Only then may the combined result be recorded as `COMMON CLOCK VERIFIED = YES`. ## 2. Preconditions
The physical checkpoint reported by the user is: both antennas connected, both SDR USB links connected, MASTER CLKOUT connected by coaxial cable to SLAVE CLKIN, about 2 m LOS, and equal antenna polarization. Before asking for RED authorization, prepare all read-only evidence:
Do not repeat RF-001A transmission preparation or start RF TX in this task. Do not change PLL, sample clock, other clock outputs, registers, firmware, CPLD, Zadig or USB drivers. 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 = Verify the prepared waveform without accessing radio hardware:
WAIT FOR RF-001A SLAVE CLKIN VERIFICATION
BRANCH = ```powershell
HEAD = python -B -X utf8 tools/rf001a_prepare.py --verify-dir data/raw/rf001/rf001a_common_clock_001
MASTER SERIAL = ```
MASTER CLKOUT COMMAND =
RX COMMON CLOCK REPORT = Then verify the actual hardware-format bytes through the existing receiver. The report path must be new and outside Git:
COMMON CLOCK VERIFIED =
```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'
```
Require the receiver report to contain `crc_valid_packets = 100`, `exact_packets = 100`, no missing packets and no duplicates.
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
```
If any value differs, if RX is not ready, or if the RX capture window has ended, do not transmit and STOP.
Only after every precondition passes may the user provide the separate exact phrase `AUTHORIZE RF TX`.
## 3. One authorized TX execution
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.
Use this single finite command:
```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
```
Run it once, synchronously, and preserve the exact process exit code:
```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
$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'
)
$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
```
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.
Inspect every buffer-statistics line. Record:
```text
TX start time =
TX end time =
TX PID =
TX exit code =
TX underruns =
USB errors =
RF TX STOPPED =
YES / NO YES / NO
TX READY FOR RF-001A = ```
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.
Set `RF TX STOPPED = YES` only after the finite process exits. Do not start a second burst for any reason. Do not retry automatically.
## 4. Final TX report
```text
TASK_ID =
RF001A_OTA_TX_001
RED AUTHORIZATION =
AUTHORIZE RF TX / MISSING
RX READY FOR TX =
YES / NO YES / NO
TX SERIAL =
TX WAVEFORM SHA256 =
TX START TIME =
TX END TIME =
TX EXIT CODE =
TX UNDERRUNS =
USB ERRORS =
RF TX STOPPED =
YES / NO
BURSTS TRANSMITTED =
0 / 1
RF TX = RF TX =
0 0
STOP. ```
At report time, `RF TX = 0` means no transmission process remains active.
## 5. Prohibited actions
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.