Prepare first RF-001A OTA run
This commit is contained in:
@@ -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
|
||||
& '<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
|
||||
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
|
||||
& '<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:
|
||||
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 <unique_capture_path>\rf001a_capture.cs8
|
||||
-r <unique_capture_path>\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 = '<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',
|
||||
'-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 =
|
||||
<actual hackrf_transfer child PID>
|
||||
|
||||
CAPTURE PATH =
|
||||
<absolute path>
|
||||
|
||||
RX CAPTURE COMPLETE =
|
||||
YES / NO / NOT_RUN
|
||||
CAPTURE START TIME =
|
||||
<started_status started_at>
|
||||
|
||||
CAPTURE SIZE =
|
||||
|
||||
CAPTURE DURATION =
|
||||
|
||||
RX OVERRUNS =
|
||||
|
||||
USB ERRORS =
|
||||
|
||||
FILE WRITE ERRORS =
|
||||
|
||||
RAW IQ SHA256 =
|
||||
EXPECTED END TIME =
|
||||
<started_at plus 60 seconds>
|
||||
|
||||
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 '<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.
|
||||
|
||||
@@ -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 '<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
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user