Lab044: validate arbitrary BPSK packet reception
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
225
docs/lab044_current_state.md
Normal file
225
docs/lab044_current_state.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Lab044: текущее состояние
|
||||
|
||||
## Статус
|
||||
|
||||
Lab044 hardware experiment ВЫПОЛНЕН один раз. RF TX count = 1.
|
||||
Результат: **RESULT C** (PHY sync восстановлен независимо, но protocol packet
|
||||
не восстановлен, CRC NOT_REACHED). CRC **не** PASS — это честный
|
||||
отрицательный, но научно полезный результат.
|
||||
|
||||
Реализация подготовлена поверх рабочей точки Lab043 (checkpoint `4cee6e9`,
|
||||
"Lab043: establish reproducible HackRF BPSK working point") и не меняет её
|
||||
RF-параметры, gain, PHY sync, PRBS7-преамбулу, CRC или независимый детектор.
|
||||
|
||||
## Фактический аппаратный результат (31 августа 2026 года)
|
||||
|
||||
Каталог артефактов:
|
||||
`C:\Users\user\Documents\SDR_Rover_Lab044_raw\20260831_140142_bpsk_packet\`
|
||||
|
||||
- **RF TX count = 1** (ровно одна передача, `TransmitGuard`, без retry).
|
||||
- **Common clock = PASS**: MASTER reg29 = 0x26, SLAVE reg29 = 0x26,
|
||||
SLAVE CLKIN = DETECTED за 69 мс (1 опрос), bounded polling 0.05/0.05/0.5 с.
|
||||
- **PHY SYNC = PASS**: независимый PRBS7-детектор преамбулы,
|
||||
peak/median +38.3 dB, peak/second +11.6 dB, payload не использовался.
|
||||
- **FRAME HEADER**: PHY SYNC 0xD391 декодирован верно, но поле PHY LENGTH
|
||||
искажено битовой ошибкой — декодировано 264 вместо 268 байт. Структурно
|
||||
`frame_header_pass=true` (sync совпал, длина в границах 1..1024), но длина
|
||||
фактически неверна.
|
||||
- **PROTOCOL HEADER = FAIL** (`PAYLOAD_LENGTH_MISMATCH`): protocol SYNC
|
||||
0xAA55, version 1, message type 2 (TELEMETRY), sequence 44 декодированы
|
||||
верно, но payload_length = 2304 вместо 256 (битовая ошибка в старшем
|
||||
байте длины). `expected_packet_bytes = 2316 ≠ 264`.
|
||||
- **CRC = NOT_REACHED** (packet не собран корректно, до CRC decode не дошло).
|
||||
- **Classification = C**.
|
||||
|
||||
### Радиометрика
|
||||
|
||||
- independent preamble detected: **да**
|
||||
- preamble errors: **8 / 1280** (physical-layer BER ≈ **0.625 %**,
|
||||
согласуется с ~0.4586 % Lab043)
|
||||
- CFO: **0.101 Hz** (общий опорный генератор ⇒ почти нулевой сдвиг несущей)
|
||||
- SNR: **7.19 dB**
|
||||
- separation: **4.574**
|
||||
- timing offset: **−3 samples**
|
||||
- ADC peak: peak_complex **5.10** code, peak_component **5.0** code
|
||||
- ADC p99.9: **3.606** code (headroom 122 code до предела int8)
|
||||
- clipping: **0** (fraction 0.0), near-int8: 0
|
||||
- RX overruns: **0**, TX underruns: **0** (оба лога чистые)
|
||||
|
||||
### Ошибки данных (только ПОСЛЕ независимого CRC decision)
|
||||
|
||||
CRC-решение было NOT_REACHED, поэтому packet/payload BER **не вычисляются
|
||||
как «после успешного decode»**. Диагностическое сравнение с TX-артефактами
|
||||
показало:
|
||||
|
||||
- packet: `comparable=false` — RX 264 байт против TX 268 байт (длины не
|
||||
совпадают из-за искажения PHY LENGTH), побитовый BER не определён.
|
||||
- payload: `comparable=false` — `rx_payload.bin` не создан (protocol header
|
||||
не прошёл), payload не извлечён.
|
||||
|
||||
Первичный вывод: канал по-прежнему даёт ~0.6 % BER (как в Lab043), и в этом
|
||||
единственном кадре ошибки попали именно в поля длины (PHY LENGTH и protocol
|
||||
payload_length), что структурно сорвало разбор пакета раньше CRC. Это не
|
||||
новый дефект, а известная чувствительность незащищённого кадра к одиночным
|
||||
битовым ошибкам в критических полях — прямой довод в пользу FEC/защиты
|
||||
заголовка на следующем этапе.
|
||||
|
||||
### BEFORE / DURING / AFTER
|
||||
|
||||
Границы построены по независимо найденному preamble start (sample 6 122 876)
|
||||
и декодированной PHY LENGTH.
|
||||
|
||||
| Регион | Длительность | RMS (после DC) | p95 | p99 | p99.9 | peak | clipping |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| BEFORE | 0.765 с | 0.776 | 3.162 | 3.606 | 3.606 | 5.00 | 0 |
|
||||
| DURING | 0.171 с | 0.833 | 3.162 | 3.606 | 3.606 | 4.47 | 0 |
|
||||
| AFTER | 3.063 с | 0.782 | 3.162 | 3.606 | 3.606 | 5.10 | 0 |
|
||||
|
||||
DURING отличается от фоновых BEFORE/AFTER повышенным RMS после удаления DC
|
||||
(0.833 против 0.776/0.782) — то есть на интервале кадра действительно
|
||||
присутствует переданный сигнал над фоном; хвосты амплитуды (p95/p99/p99.9)
|
||||
и clipping идентичны, перегрузки АЦП нет.
|
||||
|
||||
### Артефакты и SHA-256 (пересчитаны независимо, совпали с metadata.json)
|
||||
|
||||
- `tx_payload.bin` (256 B): `aae1ee74…e27109`
|
||||
- `tx_packet.bin` (268 B): `cc179e93…e4554b`
|
||||
- `tx_bpsk_cs8.iq` (2 798 400 B): `15260dbc…f42e7`
|
||||
- `rx_cs8.iq` (64 000 000 B): `8d5f4cd1…1019f1` (в Git не добавляется)
|
||||
- `rx_packet.bin` (264 B): `b6ebbce3…c694e1`
|
||||
- `rx_payload.bin`: **не создан** (SHA-256 = null, фиктивный файл не создаётся)
|
||||
|
||||
### Cleanup
|
||||
|
||||
MASTER CLKOUT OFF (SLAVE CLKIN = "no clock signal detected"),
|
||||
SLAVE CLKOUT OFF, зависших `hackrf_transfer.exe` нет.
|
||||
|
||||
## Цель Lab044
|
||||
|
||||
Впервые передать по реальному RF BPSK-пакет с произвольным payload, который
|
||||
приёмник заранее не знает: `arbitrary bytes → protocol packet → PHY frame →
|
||||
BPSK → RF → independent synchronization → demodulation → frame parsing →
|
||||
CRC verification`. CRC PASS желателен, но не обязателен — CRC FAIL при
|
||||
BER около 0.4586% (унаследованном из Lab043) является нормальным научно
|
||||
полезным результатом (RESULT B).
|
||||
|
||||
## Формат (подтверждён по фактическому коду, не принят на веру)
|
||||
|
||||
- `protocol/packet.py`: `HEADER_FORMAT = ">HBBHH"` (8 байт: SYNC 0xAA55,
|
||||
version, message type, sequence number, payload length), CRC-32
|
||||
(`zlib.crc32`, big-endian uint32, 4 байта), CRC покрывает header+payload.
|
||||
- Payload = **256 bytes**, protocol packet = **268 bytes**
|
||||
(8 + 256 + 4), сгенерирован через `secrets.token_bytes` — системный
|
||||
источник случайности, один раз.
|
||||
- PHY frame = `[1280-bit PRBS7 preamble Lab043][PHY SYNC 0xD391 + PHY LENGTH
|
||||
uint16, ">HH", big-endian][protocol packet]` = **3456 BPSK-символов**
|
||||
(1280 + 32 + 268×8). Преамбула — та же аппаратно проверенная
|
||||
1280-битная PRBS7 (`pilot_prbs7`, `PILOT_INITIAL_STATE=0x7F`) из
|
||||
`experiments/lab043_hackrf_bpsk.py`, не generic-маркер из
|
||||
`protocol/bpsk_radio.build_radio_frame()`.
|
||||
- Waveform = **1,399,200 сэмплов** при 8 MS/s ⇒ длительность
|
||||
**≈ 174.9 мс** (после RRC-формирования Lab042: rolloff 0.35,
|
||||
span 10 символов, guard 16 символов).
|
||||
- Bit order: MSB-first, явно (`np.unpackbits(..., bitorder="big")`).
|
||||
- MAX_PACKET_BYTES = 1024 (fail-closed предел для PHY LENGTH).
|
||||
|
||||
## RF-параметры (без изменений от Lab043)
|
||||
|
||||
RF center 1080 MHz, sample rate 8 MS/s, symbol rate 20 ksym/s, digital
|
||||
carrier offset +50 kHz, TX VGA = 10 dB, RX LNA = 24 dB, RX VGA = 24 dB,
|
||||
RF amp OFF, common clock: JYEC TCXO 10 MHz MASTER → MASTER CLKOUT → SLAVE
|
||||
CLKIN, bounded common-clock preflight (initial_settle=0.05s,
|
||||
poll_interval=0.05s, timeout=0.5s), reg29 = 0x26 на обеих платах.
|
||||
|
||||
## Пробелы аудита A/B/C — закрыты
|
||||
|
||||
**A. TRUNCATED PROTOCOL PACKET TEST — FIXED.** Добавлен
|
||||
`test_truncated_protocol_packet_is_flagged_without_exception`
|
||||
(`tests/test_lab044_hackrf_bpsk_packet.py`): синтетический capture с
|
||||
полной преамбулой и полным PHY header, но физически обрезанным (не
|
||||
дополненным нулями) телом protocol packet. Ожидаемое и фактическое
|
||||
поведение: `phy_sync_pass=True`, `frame_header_pass=False`,
|
||||
`packet_received=False`, `frame_header["reason"]=="TRUNCATED_PACKET"`,
|
||||
`crc=="NOT_REACHED"`, без исключений, без изменения рабочей
|
||||
классификации. Исходный код `analyze_capture_independently` этот путь уже
|
||||
предусматривал — не хватало только теста, подтверждающего его
|
||||
достижимость и корректность.
|
||||
|
||||
**B. BEFORE/DURING/AFTER DIAGNOSTICS — FIXED.** Добавлена функция
|
||||
`_capture_diagnostics()` в `experiments/lab044_hackrf_bpsk_packet.py`,
|
||||
переиспользующая `lab043._capture_intervals()` и `lab043.raw_statistics()`
|
||||
без изменения самого Lab043 (совместимый набор полей: mean I/Q,
|
||||
RMS после DC removal, p95/p99/p99.9 magnitude, peak, clipping
|
||||
count/fraction — плюс добавленные `sample_range` и
|
||||
`duration_seconds` на каждый регион). Граница DURING строится только по
|
||||
`detection["refined_start_sample"]` (независимый preamble) и по PHY
|
||||
LENGTH, если она уже декодирована; если PHY header ещё не восстановлен —
|
||||
по максимально возможной длине кадра Lab044 (MAX_PACKET_BYTES), payload
|
||||
никогда не используется. Если независимый preamble не найден вовсе,
|
||||
DURING остаётся `None` с явной пометкой в поле `note`, сохраняется только
|
||||
`whole_capture`. Результат добавлен как `analysis["capture_intervals"]` во
|
||||
всех пяти точках возврата `analyze_capture_independently` (RESULT A/B/C/D).
|
||||
Тесты: `test_capture_intervals_present_for_independently_decoded_frame`,
|
||||
`test_capture_intervals_undefined_when_no_independent_start`.
|
||||
|
||||
**C. SHA-256 ДЛЯ ВСЕХ BINARY ARTIFACTS — FIXED.** Добавлена функция
|
||||
`_artifact_sha256()`, переиспользующая `lab043._sha256_file()`. После
|
||||
`run_hardware_experiment()` в `metadata.json` появляется поле
|
||||
`artifact_sha256` с хэшем каждого РЕАЛЬНО существующего файла:
|
||||
`tx_payload.bin`, `tx_packet.bin`, `tx_bpsk_cs8.iq`, `rx_cs8.iq`,
|
||||
`rx_packet.bin`, `rx_payload.bin`. Для `rx_packet.bin`/`rx_payload.bin`,
|
||||
если decode не дошёл до их создания, значение — `None`; фиктивный файл не
|
||||
создаётся (это поведение уже было в коде и не менялось). Тесты:
|
||||
`test_artifact_sha256_recorded_for_every_real_rx_and_tx_file`,
|
||||
`test_no_fake_rx_artifacts_or_hashes_when_packet_not_decoded`.
|
||||
|
||||
Ничего из RF-параметров, gain, packet format, PHY sync, PRBS7-преамбулы,
|
||||
payload size, CRC, независимого детектора или порогов Lab043 не менялось.
|
||||
FEC/QPSK/JPEG/video не добавлялись.
|
||||
|
||||
## Что реально проверено в этой сессии
|
||||
|
||||
Эта сессия работает без `device_bash` на машине пользователя и без доступа
|
||||
к сети из своей песочницы (нет pip/apt-репозитория), поэтому команда
|
||||
`pytest` в буквальном смысле **NOT EXECUTED IN THIS SESSION**.
|
||||
|
||||
Вместо этого фактически выполнено (все результаты — реальный вывод
|
||||
интерпретатора, не предположение):
|
||||
|
||||
- `ast.parse()` по `experiments/lab044_hackrf_bpsk_packet.py` и
|
||||
`tests/test_lab044_hackrf_bpsk_packet.py` — синтаксис корректен.
|
||||
- Полный импорт `experiments.lab044_hackrf_bpsk_packet` со всей цепочкой
|
||||
зависимостей (`protocol.*`, `experiments.lab043_*`) — импортируется без
|
||||
ошибок.
|
||||
- Все 22 функции `test_*` из `tests/test_lab044_hackrf_bpsk_packet.py`
|
||||
(17 существовавших + 5 новых для A/B/C) вызваны напрямую, как обычные
|
||||
Python-функции, с самодельными подстановками фикстур `tmp_path` и
|
||||
`monkeypatch` (реальный pytest недоступен для установки в этой
|
||||
песочнице — нет сетевого доступа к PyPI/apt) — **22/22 PASS**. Это
|
||||
честная проверка логики, но не замена запуску `pytest` как CLI-команды.
|
||||
- `python tools/quick_gate.py` запущен реально: синтаксис и импорт
|
||||
`protocol/*` прошли, единственная сообщённая ошибка — сам pytest не
|
||||
установлен (`No module named pytest`), что ожидаемо и не является
|
||||
дефектом кода.
|
||||
- Аппаратный RF-эксперимент не запускался и не имитировался.
|
||||
|
||||
## Что должен сделать локальный агент перед RF
|
||||
|
||||
1. `python tools/quick_gate.py` — уже в этой сессии проходит синтаксис и
|
||||
импорт; локально должен пройти целиком, включая `pytest tests -q`.
|
||||
2. `python -m pytest tests/ -q` — полный реальный прогон (в этой сессии
|
||||
недоступен физически, см. выше). Ожидается: все прежние тесты Lab043
|
||||
зелёные, все 22 теста `test_lab044_hackrf_bpsk_packet.py` зелёные.
|
||||
3. `git diff --check` — конфликт-маркеры/whitespace.
|
||||
4. Только после полного PASS — `python experiments/lab044_hackrf_bpsk_packet.py --hardware --i-understand-hardware`.
|
||||
5. STOP conditions: common-clock preflight FAIL, RX завершился до TX,
|
||||
TX/RX вернули ненулевой код, log содержит overrun/underrun, любое
|
||||
исключение — во всех случаях RF TX должен остаться равным 0 (гарантия
|
||||
`TransmitGuard`, покрыта тестами `test_common_clock_failure_means_zero_tx`,
|
||||
`test_frame_validation_failure_means_zero_tx`,
|
||||
`test_rx_startup_failure_means_zero_tx`).
|
||||
6. Artifacts появятся в `~/Documents/SDR_Rover_Lab044_raw/<timestamp>_bpsk_packet/`:
|
||||
`tx_payload.bin`, `tx_packet.bin`, `tx_bpsk_cs8.iq`, `rx_cs8.iq`,
|
||||
`rx_packet.bin` (если извлечён), `rx_payload.bin` (если извлечён),
|
||||
`metadata.json`, `analysis.json`.
|
||||
7. Raw IQ (`rx_cs8.iq`, `tx_bpsk_cs8.iq`) в Git не добавлять.
|
||||
Reference in New Issue
Block a user