From 34f5ace54540f947311ada7e81a463c08abfd2d3 Mon Sep 17 00:00:00 2001 From: LittleSam129 <1qwe432@gmail.com> Date: Mon, 21 Sep 2026 15:55:49 +0300 Subject: [PATCH] Prepare first RF-001A OTA run --- docs/handoff/NEXT_RX_TASK.md | 452 +++++++++++++++++++++-------------- docs/handoff/NEXT_TX_TASK.md | 221 +++++++++++++++-- 2 files changed, 472 insertions(+), 201 deletions(-) diff --git a/docs/handoff/NEXT_RX_TASK.md b/docs/handoff/NEXT_RX_TASK.md index f62357b..788afaa 100644 --- a/docs/handoff/NEXT_RX_TASK.md +++ b/docs/handoff/NEXT_RX_TASK.md @@ -1,7 +1,7 @@ -TASK_ID = RF001A_VERIFY_AND_CAPTURE_001 -TASK_TITLE = Verify RF-001A common clock and start IQ capture +TASK_ID = RF001A_OTA_CAPTURE_001 +TASK_TITLE = Capture first RF-001A QPSK OTA transmission SOURCE_BRANCH = video-development -EXPECTED_HEAD = fa370ccfa23dedc633346d688547e9c1c77ff8eb +EXPECTED_HEAD = 1e3aa25d7007c7c8e84608cddb02f06f77e63396 REQUIRES_APPROVAL = YES HARDWARE_ACCESS = YES RF_RX = YES @@ -12,95 +12,59 @@ RISK_LEVEL = YELLOW 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. -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 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 -RF001A_CLKIN_001 -RF001A_CAPTURE_001 +COMMON CLOCK VERIFIED = YES +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 -After approval, run `git pull --ff-only origin video-development`, then verify: +After one explicit `[A] Approve` for this task: -- branch = `video-development`; -- `git status --short --untracked-files=no` produces no output; -- local HEAD equals `origin/video-development` and `EXPECTED_HEAD` is its ancestor; -- `hackrf_info` = PASS; -- RX serial = `000000000000000097c063dc2e5327d3`; -- the physical cable is connected from MASTER CLKOUT to SLAVE CLKIN; -- the existing hardware-independent shared RF-001A spec test passes: - -```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: +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 RX serial `000000000000000097c063dc2e5327d3`. +7. Run the official read-only clock check and preserve complete stdout: ```powershell & '' -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 -CLKIN status: clock signal detected -``` +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. -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 - -If SLAVE CLKIN was detected, perform one short, finite RX-start on the same device: - -```powershell -& '' -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 -& '' -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: +Only this capture is authorized: ```text RX serial = 000000000000000097c063dc2e5327d3 @@ -110,23 +74,21 @@ RX LNA = 24 dB RX VGA = 24 dB RF amp = OFF antenna bias = OFF -duration = 20 s -sample count = 160000000 complex samples +duration = 60 s +sample count = 480000000 complex samples 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 capture arguments are: +The required `hackrf_transfer.exe` arguments are: ```text -hackrf_transfer.exe -d 000000000000000097c063dc2e5327d3 --r \rf001a_capture.cs8 +-r \rf001a_ota_capture.cs8 -f 1400000000 -s 8000000 --n 160000000 +-n 480000000 -l 24 -g 24 -a 0 @@ -134,154 +96,290 @@ hackrf_transfer.exe -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 $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' -$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" } 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' $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 = '' -$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', - '-r', $capturePath, + '-r', $CapturePath, '-f', '1400000000', '-s', '8000000', - '-n', '160000000', + '-n', '480000000', '-l', '24', '-g', '24', '-a', '0', '-p', '0', '-B' ) -$captureProcess = Start-Process -FilePath $hackrfTransfer -ArgumentList $captureArguments ` - -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath ` - -PassThru -WindowStyle Hidden -$growthDeadline = (Get-Date).AddSeconds(2) -do { - Start-Sleep -Milliseconds 100 - $captureProcess.Refresh() - $captureBytes = if (Test-Path -LiteralPath $capturePath) { - (Get-Item -LiteralPath $capturePath).Length +$startedAt = [DateTimeOffset]::Now +$transferProcess = $null +$exitCode = $null +$runnerError = $null + +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 { 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. - -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: +Start the runner as a controlled background process. Quote every path passed through `-ArgumentList`: ```powershell -$captureProcess.WaitForExit() -$finishedAt = Get-Date -$captureProcess.Refresh() -$exitCode = $captureProcess.ExitCode -$captureSize = (Get-Item -LiteralPath $capturePath).Length -$captureDurationSeconds = ($finishedAt - $startedAt).TotalSeconds -$captureHash = (Get-FileHash -LiteralPath $capturePath -Algorithm SHA256).Hash.ToLowerInvariant() +$currentPowerShell = (Get-Process -Id $PID).Path +$runnerArguments = @( + '-NoLogo', + '-NoProfile', + '-NonInteractive', + '-ExecutionPolicy', 'Bypass', + '-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`; -- file size = exactly `320000000` bytes; -- RX overruns = `0`; -- USB errors = `0`; -- file write errors = `0`. +do { + Start-Sleep -Milliseconds 100 + $runnerProcess.Refresh() + if (Test-Path -LiteralPath $startedStatusPath) { + $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 TASK_ID = -RF001A_VERIFY_AND_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 +RF001A_OTA_CAPTURE_001 RX CAPTURE STARTED = -YES / NO +YES RX READY FOR TX = -YES / NO +YES CAPTURE PID = + CAPTURE PATH = + -RX CAPTURE COMPLETE = -YES / NO / NOT_RUN +CAPTURE START TIME = + -CAPTURE SIZE = - -CAPTURE DURATION = - -RX OVERRUNS = - -USB ERRORS = - -FILE WRITE ERRORS = - -RAW IQ SHA256 = +EXPECTED END TIME = + RF TX = 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 '' ` + --iq-format cs8 ` + --truth-metadata data/raw/rf001/rf001a_common_clock_001/tx_metadata.json ` + --report '\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. diff --git a/docs/handoff/NEXT_TX_TASK.md b/docs/handoff/NEXT_TX_TASK.md index 2c40322..3b6dd28 100644 --- a/docs/handoff/NEXT_TX_TASK.md +++ b/docs/handoff/NEXT_TX_TASK.md @@ -1,42 +1,215 @@ -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 spec, and current Git HEAD conflict, stop and report the conflict. +TASK_ID = RF001A_OTA_TX_001 +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 TASK = -WAIT FOR RF-001A SLAVE CLKIN VERIFICATION +TRANSMIT FIRST RF-001A QPSK PACKET BURST -MASTER SERIAL = -000000000000000097c063dc323b97d3 +This is a new RED task. It does not inherit the RX task approval. Publication of this file is not permission to transmit. -MASTER CLKOUT COMMAND = -PASS +Before any RF TX, the user must: -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 -hackrf_clock.exe -d 000000000000000097c063dc323b97d3 -o 1 +A normal approval, an earlier RF authorization, or an RX approval is insufficient. + +## 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 = -WAIT FOR RF-001A SLAVE CLKIN VERIFICATION -BRANCH = -HEAD = -MASTER SERIAL = -MASTER CLKOUT COMMAND = -RX COMMON CLOCK REPORT = -COMMON CLOCK VERIFIED = +Verify the prepared waveform without accessing radio hardware: + +```powershell +python -B -X utf8 tools/rf001a_prepare.py --verify-dir data/raw/rf001/rf001a_common_clock_001 +``` + +Then verify the actual hardware-format bytes through the existing receiver. The report path must be new and outside Git: + +```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 '\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 -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 + +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 = 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.