Compare commits
3 Commits
cff226e0bf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af7c05f2da | ||
|
|
b27a5cf00e | ||
|
|
dacf04f2d0 |
140
PROJECT_LOG.md
@@ -2861,3 +2861,143 @@ Quick gate после сеанса прошёл:986 тестов ядра,37 ф
|
||||
полное сравнение L3/L0 и двенадцать спектральных значений непосредственно из
|
||||
CS8. Расхождений нет, вердикт `чисто`. Оборудование при проверке не опрашивалось,
|
||||
RF TX остаётся равным нулю.
|
||||
|
||||
# Запись 060
|
||||
|
||||
## Дата
|
||||
|
||||
15 сентября 2026 года
|
||||
|
||||
## Тема
|
||||
|
||||
Lab064 зафиксирована; подготовка Lab065 при TX VGA 20 дБ и RX-only preflight
|
||||
|
||||
## Выполнено
|
||||
|
||||
После проверки уже подготовленного индекса по прямому разрешению пользователя
|
||||
создан коммит `cff226e0bf9e36a627829ea11a4ca3292ae20953` с сообщением
|
||||
`Lab064: measure OTA level linearity at TX VGA 10 dB`. В коммит вошли только
|
||||
12 файлов и фрагментов Lab064; изменения Lab054 и Lab058 и сырые IQ не вошли.
|
||||
Push не выполнялся.
|
||||
|
||||
Пользователь подтвердил неизменность геометрии, антенн, поляризации, USB,
|
||||
clock-кабеля и RF-кабелей. Для Lab065 TX VGA изменён с 10 до 20 дБ. Частота
|
||||
1400000000 Гц, RX LNA/VGA 24/24 дБ, RF amplifier и Bias-Tee OFF, PRBS7/BPSK/RRC,
|
||||
20000 символов/с, Fs 8000000 отсчётов/с, несущая +50000 Гц и лестница
|
||||
−18/−12/−6/0 дБ сохранены. TX-файл побайтово совпадает с Lab064: 6000000
|
||||
отсчётов, 12000000 байт, 0,75 с, SHA-256
|
||||
fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9.
|
||||
|
||||
Подготовлены RX-first runner без повторов, fail-closed проверка максимальных
|
||||
счётчиков и анализ двух видов линейности: внутри Lab065 и для одинаковых
|
||||
уровней Lab064/Lab065. До RF численно заданы границы пяти классификаций.
|
||||
Software gate и 12 целевых тестов прошли.
|
||||
|
||||
RX-only preflight прошёл. Обе HackRF One r9 и роли подтверждены,
|
||||
MASTER/SLAVE reg29=0x26, SLAVE CLKIN=DETECTED на третьем опросе через201,986мс.
|
||||
RX записал800000 отсчётов фона: полный RMS2,333068, AC RMS1,056565 кода,
|
||||
max|I|/|Q|=6/8, clipping0, overruns0, неожиданно сильного сигнала нет.
|
||||
Процессы до и после CLEAR, cleanup без ошибок.
|
||||
|
||||
После отдельного прямого разрешения выполнен ровно один OTA-сеанс Lab065.
|
||||
Перед TX MASTER/SLAVE reg29=0x26, SLAVE CLKIN=DETECTED на втором опросе через
|
||||
136,897мс. RX стартовал раньше TX. TX underrun maximum0, RX overrun maximum0,
|
||||
clipping0, cleanup без ошибок; процессы после CLEAR. RX:16000000 отсчётов,
|
||||
2,0с, SHA-256 845d7b7dcaef70e179ab69e6ba0041601859630786d2f4d51c16a8c1c7869d7e.
|
||||
Автоматических и ручных повторов не было.
|
||||
|
||||
Внутренние ошибки ступеней Lab065 равны−0,077363/+0,034934/−0,013858дБ;
|
||||
компрессии L2/L3 нет. Относительно Lab064 matched power вырос на
|
||||
11,179328/11,119466/11,323670/11,287650дБ. Медианный прирост+11,233489дБ,
|
||||
медианное изменение остатка+0,517004дБ, улучшение matched/residual+10,734495дБ.
|
||||
Фон стабилен: разброс1,002840дБ при границе3дБ. Остаточные полосы50/100/150кГц
|
||||
на L3 выросли на7,304593/6,432281/4,996232дБ, то есть слабее matched power.
|
||||
Классификация `LINEAR_GAIN_IMPROVEMENT`.
|
||||
|
||||
Независимая read-only проверка пересчитала RX SHA и размер, все счётчики,
|
||||
локализацию, фон, matched/residual, комплексный h, CFO, внутренние ступени,
|
||||
сравнение с Lab064 и полосы50/100/150кГц. Расхождений нет. Adversary подтвердил
|
||||
одноразовость runner и нашёл неоднозначную подпись текущего состояния RF после
|
||||
сеанса; она исправлена на `RF TX NOW=0` и закрыта отдельным тестом. Финальные
|
||||
шлюзы: quick gate1000/38/13 PASS, pytest1000 passed, diff check PASS.
|
||||
|
||||
RF TX=0. Lab065 не коммитировалась, push не выполнялся, Lab066 не создана.
|
||||
Следующий этап: STOP, рассмотрение результата и отдельное разрешение на коммит
|
||||
Lab065; повторная передача или изменение усилений запрещены.
|
||||
|
||||
# Запись 061
|
||||
|
||||
## Дата
|
||||
|
||||
15 сентября 2026 года
|
||||
|
||||
## Тема
|
||||
|
||||
Lab065 зафиксирована; подготовка исправленного Lab066 и RX-only preflight
|
||||
|
||||
## Выполнено
|
||||
|
||||
По прямому разрешению пользователя создан локальный коммит Lab065
|
||||
`dacf04f2d0a15c0c1b56a7fb527db61fc2e38462` с сообщением
|
||||
`Lab065: validate OTA gain improvement at TX VGA 20 dB`. Push не выполнялся.
|
||||
|
||||
Lab066 подготовлена как исправленный повтор цели Lab062. Исторический результат
|
||||
Lab062 сохранён: `RF_SESSION_INVALID_TX_UNDERRUN`, legacy/T2 0/16. В Lab066
|
||||
использованы16 штатных полных PHY-пакетов по256 байт при500ксимв/с, Fs8Мвыб/с,
|
||||
цифровой несущей+50кГц, RF1400МГц, TX VGA20дБ и RX LNA/VGA24/24дБ. RF amplifier
|
||||
и Bias-Tee выключены; геометрия около1,2м, LOS, антенны и поляризация прежние.
|
||||
|
||||
Каждый packet waveform содержит92256 отсчётов. Между пакетами записаны15
|
||||
фактических нулевых пауз по40000 отсчётов, то есть по5мс. Добавлены начальный и
|
||||
конечный guards по800000 отсчётов, или по100мс. Длина без guards равна2076096,
|
||||
полная длина3676096 отсчётов,7352192 байта,0,459512с. SHA-256 TX:
|
||||
621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199.
|
||||
|
||||
Весь конечный TX-файл независимо обработан legacy и T2. Оба режима дали16/16
|
||||
HEADER, FEC, CRC, packet output и точных payload в правильном порядке. Позиции
|
||||
пакетов совпали с разметкой пауз. Между режимами fallback отсутствует; T2 для
|
||||
каждого пакета заново оценивает состояние по его собственной преамбуле.
|
||||
|
||||
Программные шлюзы: quick gate1013 тестов ядра/39 функциональных/13 аппаратных
|
||||
пропусков PASS; pytest1013 passed; diff check PASS. Затем выполнен один RX-only
|
||||
preflight. Обе HackRF One r9 и роли подтверждены. MASTER/SLAVE reg29=0x26,
|
||||
SLAVE CLKIN=DETECTED на втором опросе через132,113мс. Фон:800000 отсчётов,
|
||||
полный RMS2,225641, AC RMS0,827637, max|I|/|Q|=4/5, clipping0, maximum
|
||||
overruns0, сильного сигнала нет. Cleanup без ошибок, процессы после CLEAR.
|
||||
|
||||
Независимый verifier пересчитал SHA, длину,15 нулевых пауз, оба guards,
|
||||
16 канонических packet waveform, payload и RX-only метрики; расхождений нет.
|
||||
Read-only adversary проверил запрет TX, одноразовость, связь preflight/capture
|
||||
с конфигурацией, максимум счётчиков, live-фон перед TX и границу диагностик;
|
||||
после исправлений открытых замечаний нет.
|
||||
|
||||
RF TX=0. Lab066 не коммитировалась, push не выполнялся, Lab067 не создавалась.
|
||||
Следующий этап: STOP и ожидание отдельного прямого разрешения на один TX Lab066.
|
||||
|
||||
После отдельного прямого разрешения выполнен ровно один OTA RF-сеанс Lab066.
|
||||
Передача всего потока запущена одним вызовом, автоматических повторов не было.
|
||||
Перед TX MASTER/SLAVE reg29=0x26, SLAVE CLKIN=DETECTED на втором опросе через
|
||||
131,291мс. Live RX-фон: AC RMS1,173567, max|I|/|Q|=4/5, clipping0. TX underrun
|
||||
observations0/0, RX overrun observations0/0/0/0; максимумы0/0. RX записал
|
||||
16000000 отсчётов за2,0с, SHA-256
|
||||
72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0.
|
||||
Cleanup без ошибок, процессы после CLEAR; аппаратная запись валидна.
|
||||
|
||||
Одна RX-запись независимо обработана legacy и T2 без fallback. Оба режима дали
|
||||
145 кандидатов,10 HEADER/FEC/CRC PASS,10 packet output и10 точных payload.
|
||||
Оба приняли пакеты4–8 и10–14; пакеты1–3,9,15,16 остановились на PHY header.
|
||||
Выигрыш T2 равен нулю.
|
||||
|
||||
Диагностика всех16 пакетов после строгого результата дала SER0,0110466,
|
||||
RMS-отклонение0,528374, matched/residual+5,542117дБ и медианную неравномерность
|
||||
полосы2,870070дБ. Это лучше Lab056 B на0,0239123 SER,0,316976 RMS,
|
||||
4,077271дБ matched/residual и2,601312дБ неравномерности. Относительно Lab062
|
||||
улучшения составляют0,0882400 SER,0,692027 RMS,7,272168дБ matched/residual и
|
||||
3,790271дБ неравномерности; сравнение с Lab062 контекстное из-за её underrun.
|
||||
|
||||
Классификация `PARTIAL_PACKET_RECOVERY_NO_T2_GAIN`: аппаратно чистый сеанс
|
||||
существенно улучшил физическое качество, но не достиг16/16, а T2 не изменил
|
||||
строгий результат. RF TX=0, повтор не выполнялся. Lab066 не коммитировалась,
|
||||
push не выполнялся, Lab067 не создавалась. Следующий возможный одиночный шаг —
|
||||
отдельно разрешённый офлайн-анализ PHY header не прошедших пакетов по сохранённой
|
||||
IQ-записи; новый TX не нужен.
|
||||
|
||||
232
data/processed/lab065/capture_result.json
Normal file
@@ -0,0 +1,232 @@
|
||||
{
|
||||
"lab": "Lab065",
|
||||
"stage": "SINGLE_RF_LEVEL_CAPTURE",
|
||||
"passed": true,
|
||||
"rf_tx_count": 1,
|
||||
"automatic_tx_repeats": 0,
|
||||
"point": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 20000,
|
||||
"samples_per_symbol": 400,
|
||||
"digital_carrier_hz": 50000,
|
||||
"rrc_rolloff": 0.35,
|
||||
"rrc_span_symbols": 10,
|
||||
"prbs": "PRBS7 x^7+x^6+1, deterministic initial state",
|
||||
"full_phy_packet": false,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN",
|
||||
"levels_db_relative_nominal": [
|
||||
-18.0,
|
||||
-12.0,
|
||||
-6.0,
|
||||
0.0
|
||||
],
|
||||
"geometry_unchanged_from_lab064": true,
|
||||
"waveform_source": "Lab064 exact CS8 copy"
|
||||
},
|
||||
"tx_sha256": "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9",
|
||||
"process_before": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 292,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"inventory": {
|
||||
"passed": true,
|
||||
"serials": [
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"000000000000000097c063dc323b97d3"
|
||||
],
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3",
|
||||
"raw_output": "hackrf_info version: 2024.02.1\nlibhackrf version: 2024.02.1 (0.9)\nFound HackRF\nIndex: 0\nSerial number: 000000000000000097c063dc323b97d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0x00744f5a\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One\n\nFound HackRF\nIndex: 1\nSerial number: 000000000000000097c063dc2e5327d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0xbc4b4739\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One"
|
||||
},
|
||||
"clock": {
|
||||
"passed": true,
|
||||
"master_reg29": 38,
|
||||
"slave_reg29": 38,
|
||||
"slave_clkin_status": "DETECTED",
|
||||
"slave_clock_wait": {
|
||||
"state": "DETECTED",
|
||||
"queries": 2,
|
||||
"time_to_detect_ms": 136.89709990285337,
|
||||
"elapsed_ms": 136.89709990285337,
|
||||
"polls": [
|
||||
{
|
||||
"query": 1,
|
||||
"start_ms": 50.08170008659363,
|
||||
"end_ms": 67.4550998955965,
|
||||
"raw": "CLKIN status: no clock signal detected",
|
||||
"state": "NOT_DETECTED",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"query": 2,
|
||||
"start_ms": 117.92290001176298,
|
||||
"end_ms": 136.89709990285337,
|
||||
"raw": "CLKIN status: clock signal detected",
|
||||
"state": "DETECTED",
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3"
|
||||
},
|
||||
"rx_level_before_tx": {
|
||||
"samples": 65536,
|
||||
"mean_i": 0.9449310302734375,
|
||||
"mean_q": 1.8423919677734375,
|
||||
"power_code2": 4.87310791015625,
|
||||
"ac_power_code2": 0.5858050952665499,
|
||||
"rms_code": 2.2075117010236323,
|
||||
"peak_magnitude_code": 4.47213595499958,
|
||||
"max_abs_i": 3.0,
|
||||
"max_abs_q": 4.0,
|
||||
"papr_db": 6.132239671543243,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
|
||||
},
|
||||
"tx_process": {
|
||||
"returncode": 0,
|
||||
"stdout_file": "tx_stdout.txt",
|
||||
"stderr_file": "tx_stderr.txt"
|
||||
},
|
||||
"tx_counter_summary": {
|
||||
"expected_kind": "underrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "underrun",
|
||||
"text": "0 underruns",
|
||||
"character_offset": 337
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "underrun",
|
||||
"text": "0 underruns",
|
||||
"character_offset": 509
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_samples": 16000000,
|
||||
"rx_bytes": 32000000,
|
||||
"rx_duration_seconds": 2.0,
|
||||
"rx_counter_summary": {
|
||||
"expected_kind": "overrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 337
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 459
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 581
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 752
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_sha256": "845d7b7dcaef70e179ab69e6ba0041601859630786d2f4d51c16a8c1c7869d7e",
|
||||
"rx_level": {
|
||||
"samples": 16000000,
|
||||
"mean_i": 0.942780625,
|
||||
"mean_q": 1.8349653125,
|
||||
"power_code2": 5.6163274375,
|
||||
"ac_power_code2": 1.3603944325463857,
|
||||
"rms_code": 2.3698792031451728,
|
||||
"peak_magnitude_code": 10.816653826391969,
|
||||
"max_abs_i": 10.0,
|
||||
"max_abs_q": 10.0,
|
||||
"papr_db": 13.187334420841685,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
|
||||
},
|
||||
"rx_clipping": false,
|
||||
"process_after": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 292,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"cleanup_errors": []
|
||||
}
|
||||
181
data/processed/lab065/preflight_result.json
Normal file
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"lab": "Lab065",
|
||||
"stage": "COMMON_CLOCK_AND_RX_ONLY_PREFLIGHT",
|
||||
"passed": true,
|
||||
"rf_tx_count": 0,
|
||||
"stop_before_tx": true,
|
||||
"point": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 20000,
|
||||
"samples_per_symbol": 400,
|
||||
"digital_carrier_hz": 50000,
|
||||
"rrc_rolloff": 0.35,
|
||||
"rrc_span_symbols": 10,
|
||||
"prbs": "PRBS7 x^7+x^6+1, deterministic initial state",
|
||||
"full_phy_packet": false,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN",
|
||||
"levels_db_relative_nominal": [
|
||||
-18.0,
|
||||
-12.0,
|
||||
-6.0,
|
||||
0.0
|
||||
],
|
||||
"geometry_unchanged_from_lab064": true,
|
||||
"waveform_source": "Lab064 exact CS8 copy"
|
||||
},
|
||||
"tx_sha256": "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9",
|
||||
"process_before": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 289,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"inventory": {
|
||||
"passed": true,
|
||||
"serials": [
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"000000000000000097c063dc323b97d3"
|
||||
],
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3",
|
||||
"raw_output": "hackrf_info version: 2024.02.1\nlibhackrf version: 2024.02.1 (0.9)\nFound HackRF\nIndex: 0\nSerial number: 000000000000000097c063dc323b97d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0x00744f5a\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One\n\nFound HackRF\nIndex: 1\nSerial number: 000000000000000097c063dc2e5327d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0xbc4b4739\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One"
|
||||
},
|
||||
"clock": {
|
||||
"passed": true,
|
||||
"master_reg29": 38,
|
||||
"slave_reg29": 38,
|
||||
"slave_clkin_status": "DETECTED",
|
||||
"slave_clock_wait": {
|
||||
"state": "DETECTED",
|
||||
"queries": 3,
|
||||
"time_to_detect_ms": 201.98559993878007,
|
||||
"elapsed_ms": 201.98559993878007,
|
||||
"polls": [
|
||||
{
|
||||
"query": 1,
|
||||
"start_ms": 50.617999862879515,
|
||||
"end_ms": 66.89479993656278,
|
||||
"raw": "CLKIN status: no clock signal detected",
|
||||
"state": "NOT_DETECTED",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"query": 2,
|
||||
"start_ms": 117.23309988155961,
|
||||
"end_ms": 136.1208998132497,
|
||||
"raw": "CLKIN status: no clock signal detected",
|
||||
"state": "NOT_DETECTED",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"query": 3,
|
||||
"start_ms": 187.1324998792261,
|
||||
"end_ms": 201.98559993878007,
|
||||
"raw": "CLKIN status: clock signal detected",
|
||||
"state": "DETECTED",
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3"
|
||||
},
|
||||
"rx_process": {
|
||||
"returncode": 0,
|
||||
"stdout": "",
|
||||
"stderr": "call hackrf_set_sample_rate(8000000 Hz/8.000 MHz)\ncall hackrf_set_hw_sync_mode(0)\ncall hackrf_set_freq(1400000000 Hz/1400.000 MHz)\ncall hackrf_set_amp_enable(0)\ncall hackrf_set_antenna_enable(0)\nsamples_to_xfer 800000/0Mio\nStop with Ctrl-C\n 1.8 MiB / 0.116 sec = 15.9 MiB/second, average power -34.8 dBfs, 9024 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.11556 s\nhackrf_stop_rx() done\nTransfer statistics:\n1847520 bytes transferred by M0\n1835008 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n"
|
||||
},
|
||||
"rx_counter_summary": {
|
||||
"expected_kind": "overrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 333
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 502
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_level": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.94889625,
|
||||
"mean_q": 1.85107375,
|
||||
"power_code2": 5.4432075,
|
||||
"ac_power_code2": 1.1163293787968742,
|
||||
"rms_code": 2.333068258752838,
|
||||
"peak_magnitude_code": 8.06225774829855,
|
||||
"max_abs_i": 6.0,
|
||||
"max_abs_q": 8.0,
|
||||
"papr_db": 10.770584663112091,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION",
|
||||
"symbol_energy": "NOT_APPLICABLE_TO_BACKGROUND",
|
||||
"ac_rms_code": 1.0565648956864289,
|
||||
"unexpected_strong_signal": false
|
||||
},
|
||||
"rx_sha256": "be3c61dfa1bc40d1a9f6777689026804a1ccfc340b82680d3ec5304ddd68f78c",
|
||||
"rx_overruns": 0,
|
||||
"process_after": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 289,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"cleanup_errors": []
|
||||
}
|
||||
219
data/processed/lab065/preparation.json
Normal file
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"lab": "Lab065",
|
||||
"stage": "PREPARATION_ONLY",
|
||||
"rf_tx_count": 0,
|
||||
"head": "cff226e0bf9e36a627829ea11a4ca3292ae20953",
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 20000,
|
||||
"samples_per_symbol": 400,
|
||||
"digital_carrier_hz": 50000,
|
||||
"rrc_rolloff": 0.35,
|
||||
"rrc_span_symbols": 10,
|
||||
"prbs": "PRBS7 x^7+x^6+1, deterministic initial state",
|
||||
"full_phy_packet": false,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN",
|
||||
"levels_db_relative_nominal": [
|
||||
-18.0,
|
||||
-12.0,
|
||||
-6.0,
|
||||
0.0
|
||||
],
|
||||
"geometry_unchanged_from_lab064": true,
|
||||
"waveform_source": "Lab064 exact CS8 copy"
|
||||
},
|
||||
"segments": [
|
||||
{
|
||||
"label": "L0",
|
||||
"level_db": -18.0,
|
||||
"start_sample": 800000,
|
||||
"samples": 800000,
|
||||
"duration_seconds": 0.1,
|
||||
"analysis_start_sample": 808400,
|
||||
"analysis_samples": 783200,
|
||||
"metrics": {
|
||||
"samples": 800000,
|
||||
"mean_i": -9.625e-05,
|
||||
"mean_q": 2.75e-05,
|
||||
"power_code2": 30.87065625,
|
||||
"ac_power_code2": 30.870656239979688,
|
||||
"rms_code": 5.556136809870686,
|
||||
"peak_magnitude_code": 9.219544457292887,
|
||||
"max_abs_i": 9.0,
|
||||
"max_abs_q": 9.0,
|
||||
"papr_db": 4.398730638796093,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0015435328125,
|
||||
"requested_relative_power_db": -18.0,
|
||||
"tx_rms_code": 5.556136809870686,
|
||||
"tx_peak_abs_i_code": 9.0,
|
||||
"tx_peak_abs_q_code": 9.0,
|
||||
"tx_peak_magnitude_code": 9.219544457292887,
|
||||
"digital_clipping": false,
|
||||
"actual_relative_power_db": -17.97368512020099
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L1",
|
||||
"level_db": -12.0,
|
||||
"start_sample": 2000000,
|
||||
"samples": 800000,
|
||||
"duration_seconds": 0.1,
|
||||
"analysis_start_sample": 2008400,
|
||||
"analysis_samples": 783200,
|
||||
"metrics": {
|
||||
"samples": 800000,
|
||||
"mean_i": -5e-05,
|
||||
"mean_q": 5e-05,
|
||||
"power_code2": 122.2567675,
|
||||
"ac_power_code2": 122.25676749499999,
|
||||
"rms_code": 11.056978226441435,
|
||||
"peak_magnitude_code": 17.804493814764857,
|
||||
"max_abs_i": 17.0,
|
||||
"max_abs_q": 17.0,
|
||||
"papr_db": 4.1378635346865025,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.006112838375,
|
||||
"requested_relative_power_db": -12.0,
|
||||
"tx_rms_code": 11.056978226441435,
|
||||
"tx_peak_abs_i_code": 17.0,
|
||||
"tx_peak_abs_q_code": 17.0,
|
||||
"tx_peak_magnitude_code": 17.804493814764857,
|
||||
"digital_clipping": false,
|
||||
"actual_relative_power_db": -11.99641465105681
|
||||
},
|
||||
"tx_input_difference_from_previous_db": 5.9772704691441785
|
||||
},
|
||||
{
|
||||
"label": "L2",
|
||||
"level_db": -6.0,
|
||||
"start_sample": 3200000,
|
||||
"samples": 800000,
|
||||
"duration_seconds": 0.1,
|
||||
"analysis_start_sample": 3208400,
|
||||
"analysis_samples": 783200,
|
||||
"metrics": {
|
||||
"samples": 800000,
|
||||
"mean_i": 5.625e-05,
|
||||
"mean_q": 2.625e-05,
|
||||
"power_code2": 486.5064875,
|
||||
"ac_power_code2": 486.50648749614686,
|
||||
"rms_code": 22.05689206347984,
|
||||
"peak_magnitude_code": 35.12833614050059,
|
||||
"max_abs_i": 34.0,
|
||||
"max_abs_q": 35.0,
|
||||
"papr_db": 4.0422652379446395,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.024325324375,
|
||||
"requested_relative_power_db": -6.0,
|
||||
"tx_rms_code": 22.05689206347984,
|
||||
"tx_peak_abs_i_code": 34.0,
|
||||
"tx_peak_abs_q_code": 35.0,
|
||||
"tx_peak_magnitude_code": 35.12833614050059,
|
||||
"digital_clipping": false,
|
||||
"actual_relative_power_db": -5.998257379520232
|
||||
},
|
||||
"tx_input_difference_from_previous_db": 5.998157271536577
|
||||
},
|
||||
{
|
||||
"label": "L3",
|
||||
"level_db": 0.0,
|
||||
"start_sample": 4400000,
|
||||
"samples": 800000,
|
||||
"duration_seconds": 0.1,
|
||||
"analysis_start_sample": 4408400,
|
||||
"analysis_samples": 783200,
|
||||
"metrics": {
|
||||
"samples": 800000,
|
||||
"mean_i": 8.25e-05,
|
||||
"mean_q": 0.00011125,
|
||||
"power_code2": 1936.04021375,
|
||||
"ac_power_code2": 1936.0402137308172,
|
||||
"rms_code": 44.00045697205883,
|
||||
"peak_magnitude_code": 69.6419413859206,
|
||||
"max_abs_i": 69.0,
|
||||
"max_abs_q": 69.0,
|
||||
"papr_db": 3.9882736474748155,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0968020106875,
|
||||
"requested_relative_power_db": 0.0,
|
||||
"tx_rms_code": 44.00045697205883,
|
||||
"tx_peak_abs_i_code": 69.0,
|
||||
"tx_peak_abs_q_code": 69.0,
|
||||
"tx_peak_magnitude_code": 69.6419413859206,
|
||||
"digital_clipping": false,
|
||||
"actual_relative_power_db": 0.0
|
||||
},
|
||||
"tx_input_difference_from_previous_db": 5.998257379520232
|
||||
}
|
||||
],
|
||||
"tx_samples": 6000000,
|
||||
"tx_bytes": 12000000,
|
||||
"tx_duration_seconds": 0.75,
|
||||
"tx_sha256": "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9",
|
||||
"source_tx_sha256": "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9",
|
||||
"waveform_unchanged_from_lab064": true,
|
||||
"automatic_retry": false,
|
||||
"tx_command_prepared_not_run": [
|
||||
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
|
||||
"-d",
|
||||
"000000000000000097c063dc323b97d3",
|
||||
"-t",
|
||||
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab065\\tx_level_staircase.cs8",
|
||||
"-f",
|
||||
"1400000000",
|
||||
"-s",
|
||||
"8000000",
|
||||
"-n",
|
||||
"6000000",
|
||||
"-x",
|
||||
"20",
|
||||
"-a",
|
||||
"0",
|
||||
"-p",
|
||||
"0",
|
||||
"-B"
|
||||
],
|
||||
"rx_command_prepared_not_run": [
|
||||
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
|
||||
"-d",
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"-r",
|
||||
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab065\\rx_stream.cs8",
|
||||
"-f",
|
||||
"1400000000",
|
||||
"-s",
|
||||
"8000000",
|
||||
"-n",
|
||||
"16000000",
|
||||
"-l",
|
||||
"24",
|
||||
"-g",
|
||||
"24",
|
||||
"-a",
|
||||
"0",
|
||||
"-p",
|
||||
"0",
|
||||
"-B"
|
||||
]
|
||||
}
|
||||
515
data/processed/lab065/rf_result.json
Normal file
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"lab": "Lab065",
|
||||
"stage": "OFFLINE_RF_LEVEL_LINEARITY_ANALYSIS",
|
||||
"packet_receiver_used": false,
|
||||
"location": {
|
||||
"start_sample": 4217216,
|
||||
"correlation": 0.28566040369708917,
|
||||
"coarse_cfo_hz": 0.0,
|
||||
"stride": 32
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"label": "L0",
|
||||
"level_db": -18.0,
|
||||
"rx_start_sample": 5017215,
|
||||
"alignment_adjustment_samples": -1,
|
||||
"full_iq": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.9456825,
|
||||
"mean_q": 1.849345,
|
||||
"power_code2": 5.91858,
|
||||
"ac_power_code2": 1.6041876801687494,
|
||||
"rms_code": 2.4328131864160882,
|
||||
"peak_magnitude_code": 7.280109889280518,
|
||||
"max_abs_i": 6.0,
|
||||
"max_abs_q": 7.0,
|
||||
"papr_db": 9.520583473606806,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.000295929,
|
||||
"ac_rms_code": 1.2665653082919766
|
||||
},
|
||||
"background_label": "INITIAL_GUARD",
|
||||
"background": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.9375625,
|
||||
"mean_q": 1.83864375,
|
||||
"power_code2": 4.84290625,
|
||||
"ac_power_code2": 0.5832719691796884,
|
||||
"rms_code": 2.200660412239926,
|
||||
"peak_magnitude_code": 4.47213595499958,
|
||||
"max_abs_i": 3.0,
|
||||
"max_abs_q": 4.0,
|
||||
"papr_db": 6.1592393370019245,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.00024214531250000003,
|
||||
"ac_rms_code": 0.7637224425009969
|
||||
},
|
||||
"matched_signal_power": 0.009849829325472823,
|
||||
"residual_power": 1.147671646182205,
|
||||
"matched_to_residual_db": -20.663889470509574,
|
||||
"complex_gain_h": {
|
||||
"real": -0.0069147093491316635,
|
||||
"imag": 0.016278708105995708,
|
||||
"phase_rad_at_active_start": 1.9724722764994853
|
||||
},
|
||||
"gain_abs": 0.01768642256034779,
|
||||
"gain_power_abs_h_squared": 0.00031280954298317925,
|
||||
"cfo_hz": 0.051385857393477286,
|
||||
"residual_spectrum": {
|
||||
"residual_band_h1_power_code2": 0.005700729949335216,
|
||||
"residual_band_h2_power_code2": 0.004639110794845057,
|
||||
"residual_band_h3_power_code2": 0.003007515042889266,
|
||||
"interpretation": "Diagnostic residual bands at 50/100/150 kHz; not calibrated RF harmonic power"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L1",
|
||||
"level_db": -12.0,
|
||||
"rx_start_sample": 6217220,
|
||||
"alignment_adjustment_samples": 4,
|
||||
"full_iq": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.943035,
|
||||
"mean_q": 1.84948125,
|
||||
"power_code2": 5.45620125,
|
||||
"ac_power_code2": 1.1463053446734381,
|
||||
"rms_code": 2.335851290215197,
|
||||
"peak_magnitude_code": 7.810249675906654,
|
||||
"max_abs_i": 7.0,
|
||||
"max_abs_q": 7.0,
|
||||
"papr_db": 10.48439454272797,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.00027281006250000003,
|
||||
"ac_rms_code": 1.0706565017191265
|
||||
},
|
||||
"background_label": "GAP_L0_L1",
|
||||
"background": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.94049375,
|
||||
"mean_q": 1.8457,
|
||||
"power_code2": 4.89708125,
|
||||
"ac_power_code2": 0.6059442662109387,
|
||||
"rms_code": 2.2129349854887286,
|
||||
"peak_magnitude_code": 5.0990195135927845,
|
||||
"max_abs_i": 4.0,
|
||||
"max_abs_q": 5.0,
|
||||
"papr_db": 7.250360382891476,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002448540625,
|
||||
"ac_rms_code": 0.7784242199539648
|
||||
},
|
||||
"matched_signal_power": 0.038319468321711674,
|
||||
"residual_power": 0.6202840436902609,
|
||||
"matched_to_residual_db": -12.091711347697867,
|
||||
"complex_gain_h": {
|
||||
"real": -0.010179637303215595,
|
||||
"imag": 0.014270629544800722,
|
||||
"phase_rad_at_active_start": 2.1904112551609387
|
||||
},
|
||||
"gain_abs": 0.017529286443833295,
|
||||
"gain_power_abs_h_squared": 0.00030727588322995773,
|
||||
"cfo_hz": -0.015456037277818255,
|
||||
"residual_spectrum": {
|
||||
"residual_band_h1_power_code2": 0.0075272705585461884,
|
||||
"residual_band_h2_power_code2": 0.005178438128918921,
|
||||
"residual_band_h3_power_code2": 0.0030044589971250603,
|
||||
"interpretation": "Diagnostic residual bands at 50/100/150 kHz; not calibrated RF harmonic power"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L2",
|
||||
"level_db": -6.0,
|
||||
"rx_start_sample": 7417219,
|
||||
"alignment_adjustment_samples": 3,
|
||||
"full_iq": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.94256,
|
||||
"mean_q": 1.85174125,
|
||||
"power_code2": 5.55749625,
|
||||
"ac_power_code2": 1.2401312394484378,
|
||||
"rms_code": 2.3574342514691686,
|
||||
"peak_magnitude_code": 7.280109889280518,
|
||||
"max_abs_i": 6.0,
|
||||
"max_abs_q": 7.0,
|
||||
"papr_db": 9.793966912760688,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002778748125,
|
||||
"ac_rms_code": 1.1136117992588073
|
||||
},
|
||||
"background_label": "GAP_L1_L2",
|
||||
"background": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.94511875,
|
||||
"mean_q": 1.8435375,
|
||||
"power_code2": 4.89561875,
|
||||
"ac_power_code2": 0.6037387844921858,
|
||||
"rms_code": 2.2126045173053406,
|
||||
"peak_magnitude_code": 8.06225774829855,
|
||||
"max_abs_i": 7.0,
|
||||
"max_abs_q": 7.0,
|
||||
"papr_db": 11.231057671986083,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002447809375,
|
||||
"ac_rms_code": 0.7770062963015073
|
||||
},
|
||||
"matched_signal_power": 0.15371936701352645,
|
||||
"residual_power": 0.7099975700355287,
|
||||
"matched_to_residual_db": -6.645282748943733,
|
||||
"complex_gain_h": {
|
||||
"real": -0.00913356439809709,
|
||||
"imag": 0.015044605957089683,
|
||||
"phase_rad_at_active_start": 2.1164192954656
|
||||
},
|
||||
"gain_abs": 0.01760006156291179,
|
||||
"gain_power_abs_h_squared": 0.000309762167018285,
|
||||
"cfo_hz": -0.02238057704898943,
|
||||
"residual_spectrum": {
|
||||
"residual_band_h1_power_code2": 0.016197144376941752,
|
||||
"residual_band_h2_power_code2": 0.007835153285271243,
|
||||
"residual_band_h3_power_code2": 0.003956469302302745,
|
||||
"interpretation": "Diagnostic residual bands at 50/100/150 kHz; not calibrated RF harmonic power"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L3",
|
||||
"level_db": 0.0,
|
||||
"rx_start_sample": 8617220,
|
||||
"alignment_adjustment_samples": 4,
|
||||
"full_iq": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.941795,
|
||||
"mean_q": 1.85647375,
|
||||
"power_code2": 6.38914875,
|
||||
"ac_power_code2": 2.055676143535938,
|
||||
"rms_code": 2.52767655169723,
|
||||
"peak_magnitude_code": 8.602325267042627,
|
||||
"max_abs_i": 7.0,
|
||||
"max_abs_q": 8.0,
|
||||
"papr_db": 10.637887203839098,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.00031945743750000003,
|
||||
"ac_rms_code": 1.4337629314276255
|
||||
},
|
||||
"background_label": "GAP_L2_L3",
|
||||
"background": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.9481,
|
||||
"mean_q": 1.8589625,
|
||||
"power_code2": 4.9655625,
|
||||
"ac_power_code2": 0.6109273135937494,
|
||||
"rms_code": 2.228354213315289,
|
||||
"peak_magnitude_code": 6.324555320336759,
|
||||
"max_abs_i": 5.0,
|
||||
"max_abs_q": 6.0,
|
||||
"papr_db": 9.060915387364618,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.000248278125,
|
||||
"ac_rms_code": 0.7816183938430246
|
||||
},
|
||||
"matched_signal_power": 0.609773488771332,
|
||||
"residual_power": 1.8217771923731447,
|
||||
"matched_to_residual_db": -4.753267221075902,
|
||||
"complex_gain_h": {
|
||||
"real": -0.009107358713761582,
|
||||
"imag": 0.015027742846344474,
|
||||
"phase_rad_at_active_start": 2.1156425161969685
|
||||
},
|
||||
"gain_abs": 0.017572052748526183,
|
||||
"gain_power_abs_h_squared": 0.0003087770377969866,
|
||||
"cfo_hz": -0.006443832004222744,
|
||||
"residual_spectrum": {
|
||||
"residual_band_h1_power_code2": 0.05148134451389538,
|
||||
"residual_band_h2_power_code2": 0.019651663054535452,
|
||||
"residual_band_h3_power_code2": 0.008610269373925542,
|
||||
"interpretation": "Diagnostic residual bands at 50/100/150 kHz; not calibrated RF harmonic power"
|
||||
}
|
||||
}
|
||||
],
|
||||
"background_windows": [
|
||||
{
|
||||
"label": "INITIAL_GUARD",
|
||||
"tx_relative_start_sample": 320000,
|
||||
"samples": 160000,
|
||||
"duration_seconds": 0.02,
|
||||
"rx_start_sample": 4537216,
|
||||
"metrics": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.9375625,
|
||||
"mean_q": 1.83864375,
|
||||
"power_code2": 4.84290625,
|
||||
"ac_power_code2": 0.5832719691796884,
|
||||
"rms_code": 2.200660412239926,
|
||||
"peak_magnitude_code": 4.47213595499958,
|
||||
"max_abs_i": 3.0,
|
||||
"max_abs_q": 4.0,
|
||||
"papr_db": 6.1592393370019245,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.00024214531250000003,
|
||||
"ac_rms_code": 0.7637224425009969
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "GAP_L0_L1",
|
||||
"tx_relative_start_sample": 1760000,
|
||||
"samples": 160000,
|
||||
"duration_seconds": 0.02,
|
||||
"rx_start_sample": 5977216,
|
||||
"metrics": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.94049375,
|
||||
"mean_q": 1.8457,
|
||||
"power_code2": 4.89708125,
|
||||
"ac_power_code2": 0.6059442662109387,
|
||||
"rms_code": 2.2129349854887286,
|
||||
"peak_magnitude_code": 5.0990195135927845,
|
||||
"max_abs_i": 4.0,
|
||||
"max_abs_q": 5.0,
|
||||
"papr_db": 7.250360382891476,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002448540625,
|
||||
"ac_rms_code": 0.7784242199539648
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "GAP_L1_L2",
|
||||
"tx_relative_start_sample": 2960000,
|
||||
"samples": 160000,
|
||||
"duration_seconds": 0.02,
|
||||
"rx_start_sample": 7177216,
|
||||
"metrics": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.94511875,
|
||||
"mean_q": 1.8435375,
|
||||
"power_code2": 4.89561875,
|
||||
"ac_power_code2": 0.6037387844921858,
|
||||
"rms_code": 2.2126045173053406,
|
||||
"peak_magnitude_code": 8.06225774829855,
|
||||
"max_abs_i": 7.0,
|
||||
"max_abs_q": 7.0,
|
||||
"papr_db": 11.231057671986083,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002447809375,
|
||||
"ac_rms_code": 0.7770062963015073
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "GAP_L2_L3",
|
||||
"tx_relative_start_sample": 4160000,
|
||||
"samples": 160000,
|
||||
"duration_seconds": 0.02,
|
||||
"rx_start_sample": 8377216,
|
||||
"metrics": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.9481,
|
||||
"mean_q": 1.8589625,
|
||||
"power_code2": 4.9655625,
|
||||
"ac_power_code2": 0.6109273135937494,
|
||||
"rms_code": 2.228354213315289,
|
||||
"peak_magnitude_code": 6.324555320336759,
|
||||
"max_abs_i": 5.0,
|
||||
"max_abs_q": 6.0,
|
||||
"papr_db": 9.060915387364618,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.000248278125,
|
||||
"ac_rms_code": 0.7816183938430246
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "FINAL_GUARD",
|
||||
"tx_relative_start_sample": 5440000,
|
||||
"samples": 160000,
|
||||
"duration_seconds": 0.02,
|
||||
"rx_start_sample": 9657216,
|
||||
"metrics": {
|
||||
"samples": 160000,
|
||||
"mean_i": 0.94553125,
|
||||
"mean_q": 1.8361,
|
||||
"power_code2": 5.00006875,
|
||||
"ac_power_code2": 0.7347761952734375,
|
||||
"rms_code": 2.2360833504142907,
|
||||
"peak_magnitude_code": 9.486832980505138,
|
||||
"max_abs_i": 9.0,
|
||||
"max_abs_q": 9.0,
|
||||
"papr_db": 12.55266533595234,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.0002500034375,
|
||||
"ac_rms_code": 0.8571908744692966
|
||||
}
|
||||
}
|
||||
],
|
||||
"background_summary": {
|
||||
"ac_power_min_code2": 0.5832719691796884,
|
||||
"ac_power_max_code2": 0.7347761952734375,
|
||||
"ac_rms_min_code": 0.7637224425009969,
|
||||
"ac_rms_max_code": 0.8571908744692966,
|
||||
"maximum_to_minimum_ac_power_db": 1.0028397226073626,
|
||||
"stability_rule_db": 3.0,
|
||||
"stable": true
|
||||
},
|
||||
"comparisons": [
|
||||
{
|
||||
"comparison": "L1/L0",
|
||||
"tx_input_difference_db": 5.9772704691441785,
|
||||
"rx_matched_power_difference_db": 5.899907694072936,
|
||||
"linearity_error_db": -0.07736277507124267
|
||||
},
|
||||
{
|
||||
"comparison": "L2/L1",
|
||||
"tx_input_difference_db": 5.998157271536577,
|
||||
"rx_matched_power_difference_db": 6.033091127942342,
|
||||
"linearity_error_db": 0.03493385640576463
|
||||
},
|
||||
{
|
||||
"comparison": "L3/L2",
|
||||
"tx_input_difference_db": 5.998257379520232,
|
||||
"rx_matched_power_difference_db": 5.9843995108689985,
|
||||
"linearity_error_db": -0.013857868651233396
|
||||
}
|
||||
],
|
||||
"capture_valid": true,
|
||||
"capture_invalid_reasons": [],
|
||||
"tx_underruns_maximum": 0,
|
||||
"rx_overruns_maximum": 0,
|
||||
"rx_clipping": false,
|
||||
"lab064_to_lab065": {
|
||||
"rows": [
|
||||
{
|
||||
"label": "L0",
|
||||
"tx_vga_nominal_difference_db": 10.0,
|
||||
"rx_matched_power_difference_db": 11.179327767173566,
|
||||
"gain_power_abs_h_squared_difference_db": 11.179327767173568,
|
||||
"residual_power_difference_db": -0.24327621738947727,
|
||||
"matched_to_residual_improvement_db": 11.422603984563047,
|
||||
"complex_gain_h_old": {
|
||||
"real": 0.0018760642981310563,
|
||||
"imag": -0.0045080629571906915,
|
||||
"phase_rad_at_active_start": -1.1764391150184978
|
||||
},
|
||||
"complex_gain_h_new": {
|
||||
"real": -0.0069147093491316635,
|
||||
"imag": 0.016278708105995708,
|
||||
"phase_rad_at_active_start": 1.9724722764994853
|
||||
},
|
||||
"complex_gain_phase_change_degrees": -179.5806672053524,
|
||||
"residual_spectral_band_difference_db": {
|
||||
"h1_50000_hz": 2.869745130254167,
|
||||
"h2_100000_hz": 2.6545689216728405,
|
||||
"h3_150000_hz": 2.25865088789477
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L1",
|
||||
"tx_vga_nominal_difference_db": 10.0,
|
||||
"rx_matched_power_difference_db": 11.119465772157373,
|
||||
"gain_power_abs_h_squared_difference_db": 11.119465772157373,
|
||||
"residual_power_difference_db": -2.3662859353114283,
|
||||
"matched_to_residual_improvement_db": 13.4857517074688,
|
||||
"complex_gain_h_old": {
|
||||
"real": 0.004471119012843702,
|
||||
"imag": -0.001937685853593986,
|
||||
"phase_rad_at_active_start": -0.40894566669212534
|
||||
},
|
||||
"complex_gain_h_new": {
|
||||
"real": -0.010179637303215595,
|
||||
"imag": 0.014270629544800722,
|
||||
"phase_rad_at_active_start": 2.1904112551609387
|
||||
},
|
||||
"complex_gain_phase_change_degrees": 148.9321810702975,
|
||||
"residual_spectral_band_difference_db": {
|
||||
"h1_50000_hz": 3.0326799855695237,
|
||||
"h2_100000_hz": 2.378560725133286,
|
||||
"h3_150000_hz": 1.3007160057992286
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L2",
|
||||
"tx_vga_nominal_difference_db": 10.0,
|
||||
"rx_matched_power_difference_db": 11.323669836462136,
|
||||
"gain_power_abs_h_squared_difference_db": 11.323669836462136,
|
||||
"residual_power_difference_db": 1.2772838599246652,
|
||||
"matched_to_residual_improvement_db": 10.046385976537472,
|
||||
"complex_gain_h_old": {
|
||||
"real": 0.0032772531007889995,
|
||||
"imag": -0.003478187284280655,
|
||||
"phase_rad_at_active_start": -0.8151334504253348
|
||||
},
|
||||
"complex_gain_h_new": {
|
||||
"real": -0.00913356439809709,
|
||||
"imag": 0.015044605957089683,
|
||||
"phase_rad_at_active_start": 2.1164192954656
|
||||
},
|
||||
"complex_gain_phase_change_degrees": 167.96559975953804,
|
||||
"residual_spectral_band_difference_db": {
|
||||
"h1_50000_hz": 5.594944314904455,
|
||||
"h2_100000_hz": 4.091263491385863,
|
||||
"h3_150000_hz": 2.888396722883724
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "L3",
|
||||
"tx_vga_nominal_difference_db": 10.0,
|
||||
"rx_matched_power_difference_db": 11.287649575482401,
|
||||
"gain_power_abs_h_squared_difference_db": 11.287649575482403,
|
||||
"residual_power_difference_db": 3.907580735290519,
|
||||
"matched_to_residual_improvement_db": 7.380068840191884,
|
||||
"complex_gain_h_old": {
|
||||
"real": 0.003384387504328541,
|
||||
"imag": -0.003391322182689881,
|
||||
"phase_rad_at_active_start": -0.7864216245934512
|
||||
},
|
||||
"complex_gain_h_new": {
|
||||
"real": -0.009107358713761582,
|
||||
"imag": 0.015027742846344474,
|
||||
"phase_rad_at_active_start": 2.1156425161969685
|
||||
},
|
||||
"complex_gain_phase_change_degrees": 166.2760271435506,
|
||||
"residual_spectral_band_difference_db": {
|
||||
"h1_50000_hz": 7.304592641009381,
|
||||
"h2_100000_hz": 6.432280867349566,
|
||||
"h3_150000_hz": 4.996231985221501
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules_fixed_before_rf": {
|
||||
"internal_linear_max_abs_error_db": 1.0,
|
||||
"compression_upper_step_error_at_most_db": -1.5,
|
||||
"meaningful_matched_gain_median_at_least_db": 3.0,
|
||||
"meaningful_ratio_improvement_median_at_least_db": 3.0,
|
||||
"maximum_level_to_level_gain_spread_db": 3.0,
|
||||
"residual_tracks_if_within_matched_gain_db": 3.0
|
||||
}
|
||||
},
|
||||
"classification": {
|
||||
"case": "LINEAR_GAIN_IMPROVEMENT",
|
||||
"summary": {
|
||||
"median_matched_gain_db": 11.233488671327983,
|
||||
"median_residual_gain_db": 0.5170038212675939,
|
||||
"median_matched_to_residual_improvement_db": 10.73449498055026,
|
||||
"matched_gain_level_spread_db": 0.20420406430476312,
|
||||
"maximum_internal_linearity_error_db": 0.07736277507124267,
|
||||
"upper_step_linearity_error_db": -0.013857868651233396
|
||||
},
|
||||
"rules_fixed_before_rf": {
|
||||
"internal_linear_max_abs_error_db": 1.0,
|
||||
"compression_upper_step_error_at_most_db": -1.5,
|
||||
"meaningful_matched_gain_median_at_least_db": 3.0,
|
||||
"meaningful_ratio_improvement_median_at_least_db": 3.0,
|
||||
"maximum_level_to_level_gain_spread_db": 3.0,
|
||||
"residual_tracks_if_within_matched_gain_db": 3.0
|
||||
}
|
||||
}
|
||||
}
|
||||
17
data/processed/lab065/software_gate.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"lab": "Lab065",
|
||||
"stage": "SOFTWARE_GATE",
|
||||
"rf_tx_count": 0,
|
||||
"passed": true,
|
||||
"tx_sha256": "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9",
|
||||
"waveform_unchanged_from_lab064": true,
|
||||
"lab064_baseline_classification": "LINEAR_BUT_WEAK",
|
||||
"classification_rules": {
|
||||
"internal_linear_max_abs_error_db": 1.0,
|
||||
"compression_upper_step_error_at_most_db": -1.5,
|
||||
"meaningful_matched_gain_median_at_least_db": 3.0,
|
||||
"meaningful_ratio_improvement_median_at_least_db": 3.0,
|
||||
"maximum_level_to_level_gain_spread_db": 3.0,
|
||||
"residual_tracks_if_within_matched_gain_db": 3.0
|
||||
}
|
||||
}
|
||||
224
data/processed/lab066/capture_result.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"lab": "Lab066",
|
||||
"stage": "SINGLE_RF_CAPTURE",
|
||||
"passed": true,
|
||||
"rf_tx_count": 1,
|
||||
"automatic_tx_repeats": 0,
|
||||
"point": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 500000,
|
||||
"samples_per_symbol": 16,
|
||||
"digital_carrier_hz": 50000.0,
|
||||
"packets": 16,
|
||||
"payload_bytes": 256,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"geometry_unchanged": true,
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN"
|
||||
},
|
||||
"tx_sha256": "621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199",
|
||||
"process_before": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 300,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"inventory": {
|
||||
"passed": true,
|
||||
"serials": [
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"000000000000000097c063dc323b97d3"
|
||||
],
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3",
|
||||
"raw_output": "hackrf_info version: 2024.02.1\nlibhackrf version: 2024.02.1 (0.9)\nFound HackRF\nIndex: 0\nSerial number: 000000000000000097c063dc323b97d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0x00744f5a\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One\n\nFound HackRF\nIndex: 1\nSerial number: 000000000000000097c063dc2e5327d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0xbc4b4739\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One"
|
||||
},
|
||||
"clock": {
|
||||
"passed": true,
|
||||
"master_reg29": 38,
|
||||
"slave_reg29": 38,
|
||||
"slave_clkin_status": "DETECTED",
|
||||
"slave_clock_wait": {
|
||||
"state": "DETECTED",
|
||||
"queries": 2,
|
||||
"time_to_detect_ms": 131.2907999381423,
|
||||
"elapsed_ms": 131.2907999381423,
|
||||
"polls": [
|
||||
{
|
||||
"query": 1,
|
||||
"start_ms": 50.125200068578124,
|
||||
"end_ms": 66.83470006100833,
|
||||
"raw": "CLKIN status: no clock signal detected",
|
||||
"state": "NOT_DETECTED",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"query": 2,
|
||||
"start_ms": 116.86519999057055,
|
||||
"end_ms": 131.2907999381423,
|
||||
"raw": "CLKIN status: clock signal detected",
|
||||
"state": "DETECTED",
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3"
|
||||
},
|
||||
"rx_level_before_tx": {
|
||||
"samples": 65536,
|
||||
"mean_i": 0.952423095703125,
|
||||
"mean_q": 1.83642578125,
|
||||
"power_code2": 5.656829833984375,
|
||||
"ac_power_code2": 1.377260430715979,
|
||||
"rms_code": 2.3784090972716143,
|
||||
"peak_magnitude_code": 5.385164807134504,
|
||||
"max_abs_i": 4.0,
|
||||
"max_abs_q": 5.0,
|
||||
"papr_db": 7.0982488319834305,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION",
|
||||
"ac_rms_code": 1.173567395046394,
|
||||
"unexpected_strong_signal": false
|
||||
},
|
||||
"tx_process": {
|
||||
"returncode": 0,
|
||||
"stdout_file": "tx_stdout.txt",
|
||||
"stderr_file": "tx_stderr.txt"
|
||||
},
|
||||
"tx_counter_summary": {
|
||||
"expected_kind": "underrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "underrun",
|
||||
"text": "0 underruns",
|
||||
"character_offset": 336
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "underrun",
|
||||
"text": "0 underruns",
|
||||
"character_offset": 506
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_counter_summary": {
|
||||
"expected_kind": "overrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 337
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 459
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 580
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 751
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_samples": 16000000,
|
||||
"rx_sha256": "72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0",
|
||||
"rx_level": {
|
||||
"samples": 16000000,
|
||||
"mean_i": 0.9510684375,
|
||||
"mean_q": 1.844211625,
|
||||
"power_code2": 5.0153848125,
|
||||
"ac_power_code2": 0.7097371219061666,
|
||||
"rms_code": 2.239505483918269,
|
||||
"peak_magnitude_code": 5.830951894845301,
|
||||
"max_abs_i": 4.0,
|
||||
"max_abs_q": 5.0,
|
||||
"papr_db": 8.311746565484663,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"symbol_energy_code2_seconds": 0.000250769240625,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
|
||||
},
|
||||
"rx_clipping": false,
|
||||
"process_after": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 300,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"cleanup_errors": []
|
||||
}
|
||||
17
data/processed/lab066/packet_comparison.csv
Normal file
@@ -0,0 +1,17 @@
|
||||
packet_index,legacy_header,legacy_fec,legacy_crc,legacy_packet_output,t2_header,t2_fec,t2_crc,t2_packet_output
|
||||
1,False,False,False,False,False,False,False,False
|
||||
2,False,False,False,False,False,False,False,False
|
||||
3,False,False,False,False,False,False,False,False
|
||||
4,True,True,True,True,True,True,True,True
|
||||
5,True,True,True,True,True,True,True,True
|
||||
6,True,True,True,True,True,True,True,True
|
||||
7,True,True,True,True,True,True,True,True
|
||||
8,True,True,True,True,True,True,True,True
|
||||
9,False,False,False,False,False,False,False,False
|
||||
10,True,True,True,True,True,True,True,True
|
||||
11,True,True,True,True,True,True,True,True
|
||||
12,True,True,True,True,True,True,True,True
|
||||
13,True,True,True,True,True,True,True,True
|
||||
14,True,True,True,True,True,True,True,True
|
||||
15,False,False,False,False,False,False,False,False
|
||||
16,False,False,False,False,False,False,False,False
|
||||
|
164
data/processed/lab066/preflight_result.json
Normal file
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"lab": "Lab066",
|
||||
"stage": "RX_ONLY_PREFLIGHT",
|
||||
"passed": true,
|
||||
"rf_tx_count": 0,
|
||||
"point": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 500000,
|
||||
"samples_per_symbol": 16,
|
||||
"digital_carrier_hz": 50000.0,
|
||||
"packets": 16,
|
||||
"payload_bytes": 256,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"geometry_unchanged": true,
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN"
|
||||
},
|
||||
"tx_sha256": "621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199",
|
||||
"stop_before_tx": true,
|
||||
"process_before": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 294,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"inventory": {
|
||||
"passed": true,
|
||||
"serials": [
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"000000000000000097c063dc323b97d3"
|
||||
],
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3",
|
||||
"raw_output": "hackrf_info version: 2024.02.1\nlibhackrf version: 2024.02.1 (0.9)\nFound HackRF\nIndex: 0\nSerial number: 000000000000000097c063dc323b97d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0x00744f5a\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One\n\nFound HackRF\nIndex: 1\nSerial number: 000000000000000097c063dc2e5327d3\nBoard ID Number: 4 (HackRF One)\nFirmware Version: 2024.02.1 (API:1.08)\nPart ID Number: 0xa000cb3c 0xbc4b4739\nHardware Revision: r9\nHardware does not appear to have been manufactured by Great Scott Gadgets.\nHardware supported by installed firmware:\n HackRF One"
|
||||
},
|
||||
"clock": {
|
||||
"passed": true,
|
||||
"master_reg29": 38,
|
||||
"slave_reg29": 38,
|
||||
"slave_clkin_status": "DETECTED",
|
||||
"slave_clock_wait": {
|
||||
"state": "DETECTED",
|
||||
"queries": 2,
|
||||
"time_to_detect_ms": 132.11270002648234,
|
||||
"elapsed_ms": 132.11270002648234,
|
||||
"polls": [
|
||||
{
|
||||
"query": 1,
|
||||
"start_ms": 50.09360006079078,
|
||||
"end_ms": 66.02549995295703,
|
||||
"raw": "CLKIN status: no clock signal detected",
|
||||
"state": "NOT_DETECTED",
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"query": 2,
|
||||
"start_ms": 116.46570009179413,
|
||||
"end_ms": 132.11270002648234,
|
||||
"raw": "CLKIN status: clock signal detected",
|
||||
"state": "DETECTED",
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"master_serial": "000000000000000097c063dc323b97d3",
|
||||
"slave_serial": "000000000000000097c063dc2e5327d3"
|
||||
},
|
||||
"rx_process": {
|
||||
"returncode": 0,
|
||||
"stdout": "",
|
||||
"stderr": "call hackrf_set_sample_rate(8000000 Hz/8.000 MHz)\ncall hackrf_set_hw_sync_mode(0)\ncall hackrf_set_freq(1400000000 Hz/1400.000 MHz)\ncall hackrf_set_amp_enable(0)\ncall hackrf_set_antenna_enable(0)\nsamples_to_xfer 800000/0Mio\nStop with Ctrl-C\n 1.8 MiB / 0.115 sec = 15.9 MiB/second, average power -35.2 dBfs, 11712 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.11546 s\nhackrf_stop_rx() done\nTransfer statistics:\n1850016 bytes transferred by M0\n1835008 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n"
|
||||
},
|
||||
"rx_counter_summary": {
|
||||
"expected_kind": "overrun",
|
||||
"observations": [
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 334
|
||||
},
|
||||
{
|
||||
"count": 0,
|
||||
"role": "UNKNOWN",
|
||||
"kind": "overrun",
|
||||
"text": "0 overruns",
|
||||
"character_offset": 503
|
||||
}
|
||||
],
|
||||
"selected_counts": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_count": 0,
|
||||
"maximum_count": 0,
|
||||
"unparsed_loss_text": false,
|
||||
"clean": true
|
||||
},
|
||||
"rx_level": {
|
||||
"samples": 800000,
|
||||
"mean_i": 0.94753,
|
||||
"mean_q": 1.83594125,
|
||||
"power_code2": 4.95347625,
|
||||
"ac_power_code2": 0.6849828756484371,
|
||||
"rms_code": 2.2256406381084974,
|
||||
"peak_magnitude_code": 5.385164807134504,
|
||||
"max_abs_i": 4.0,
|
||||
"max_abs_q": 5.0,
|
||||
"papr_db": 7.674879128392212,
|
||||
"clipped_components_fraction": 0.0,
|
||||
"clipped_samples_fraction": 0.0,
|
||||
"saturation_detected": false,
|
||||
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION",
|
||||
"symbol_energy": "NOT_APPLICABLE_TO_BACKGROUND",
|
||||
"ac_rms_code": 0.8276369225985735,
|
||||
"unexpected_strong_signal": false
|
||||
},
|
||||
"rx_sha256": "ff817582e6a8fc03b489bf89f46322a9db8a9493335a4360a1b0f9bedad7c4d5",
|
||||
"rx_overruns": 0,
|
||||
"process_after": {
|
||||
"state": "CLEAR",
|
||||
"attempts": [
|
||||
{
|
||||
"state": "CLEAR",
|
||||
"mechanism": "psutil",
|
||||
"matches": [],
|
||||
"inspected": 294,
|
||||
"errors": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"cleanup_errors": []
|
||||
}
|
||||
217
data/processed/lab066/preparation.json
Normal file
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"lab": "Lab066",
|
||||
"stage": "PREPARATION_ONLY",
|
||||
"rf_tx_count": 0,
|
||||
"head": "dacf04f2d0a15c0c1b56a7fb527db61fc2e38462",
|
||||
"parameters": {
|
||||
"rf_frequency_hz": 1400000000,
|
||||
"sample_rate_hz": 8000000,
|
||||
"symbol_rate": 500000,
|
||||
"samples_per_symbol": 16,
|
||||
"digital_carrier_hz": 50000.0,
|
||||
"packets": 16,
|
||||
"payload_bytes": 256,
|
||||
"tx_vga_db": 20,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": false,
|
||||
"bias_tee": false,
|
||||
"geometry": {
|
||||
"frequency_hz": 1400000000,
|
||||
"physical_path": "MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
"path_confirmed": true,
|
||||
"tx_antenna": "existing 1400 MHz antenna near window",
|
||||
"rx_antenna": "existing 1400 MHz antenna on desk",
|
||||
"distance_m": 1.2,
|
||||
"orientation": "same polarization; fixed during preflight and RF session",
|
||||
"visibility": "LOS indoor"
|
||||
},
|
||||
"geometry_unchanged": true,
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN"
|
||||
},
|
||||
"packet_samples": 92256,
|
||||
"inter_packet_gap_seconds": 0.005,
|
||||
"inter_packet_gap_samples": 40000,
|
||||
"number_of_gaps": 15,
|
||||
"zero_guard_before_samples": 800000,
|
||||
"zero_guard_after_samples": 800000,
|
||||
"base_tx_samples": 2076096,
|
||||
"expected_tx_samples": 3676096,
|
||||
"tx_samples": 3676096,
|
||||
"tx_bytes": 7352192,
|
||||
"tx_duration_seconds": 0.459512,
|
||||
"tx_sha256": "621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199",
|
||||
"layout": [
|
||||
{
|
||||
"packet_index": 1,
|
||||
"start_sample": 800000,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "414dee46d0555da2caabe0d4e446eb52d22be2f380ed4529fd7f192420d06465",
|
||||
"following_gap_start_sample": 892256,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 2,
|
||||
"start_sample": 932256,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "f5304773cc626858c4ad1cfe509bfe40418cfee6d9c65718c7c271c664465f2d",
|
||||
"following_gap_start_sample": 1024512,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 3,
|
||||
"start_sample": 1064512,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "e5604108e4a0a0441b962cf65822838d817a228d5df899dfd64dab958f32a59c",
|
||||
"following_gap_start_sample": 1156768,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 4,
|
||||
"start_sample": 1196768,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "43a7b5866bcf826920aae650830abfd2fa75b6e5460078c1bbde3ec7b461703f",
|
||||
"following_gap_start_sample": 1289024,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 5,
|
||||
"start_sample": 1329024,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "46836a613f080ddeba9c1b23bc8d8dd981db24975115074fa01b31699099efb8",
|
||||
"following_gap_start_sample": 1421280,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 6,
|
||||
"start_sample": 1461280,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "6ff8c9b941d37c39834f8cc5be3fdf563b3e55c51fc0c7d7344d4e046a15d13c",
|
||||
"following_gap_start_sample": 1553536,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 7,
|
||||
"start_sample": 1593536,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "273543a396d7499a1e1b325415b38555c72bb2665a227cd88aff5b797e18875d",
|
||||
"following_gap_start_sample": 1685792,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 8,
|
||||
"start_sample": 1725792,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "20051b24e17274d3b0cf3c170ea09559409c18ff429a3020ff28cb83d9b7bb3b",
|
||||
"following_gap_start_sample": 1818048,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 9,
|
||||
"start_sample": 1858048,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "9239a9fdb3fc94354cc8372dfa06867a18500ad13f867ea3a4cd4468e76bb9f0",
|
||||
"following_gap_start_sample": 1950304,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 10,
|
||||
"start_sample": 1990304,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "997be4e3b7a26708a48afd8ac0932460dbb34d67a359f1770dd76214f810e658",
|
||||
"following_gap_start_sample": 2082560,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 11,
|
||||
"start_sample": 2122560,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "fb1d718a4c2fa358f24ba34197b3100384b0df4229efae846944949894526903",
|
||||
"following_gap_start_sample": 2214816,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 12,
|
||||
"start_sample": 2254816,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "778de948620ddcf8c70f639f72ed042f97915234a57055421af9cbeb16c47e1f",
|
||||
"following_gap_start_sample": 2347072,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 13,
|
||||
"start_sample": 2387072,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "cabcd273073d70d686900e180be3c59e970cb3633bb370ba2198e64540727190",
|
||||
"following_gap_start_sample": 2479328,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 14,
|
||||
"start_sample": 2519328,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "3d6fda80bdd274d781399b1d1831cb4cb4a7c17bcd69930330564bf0ed6eb125",
|
||||
"following_gap_start_sample": 2611584,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 15,
|
||||
"start_sample": 2651584,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "ece74586bedf93fc3f5e07f41f64eea4383425d43424fe209ca30d911279c6ff",
|
||||
"following_gap_start_sample": 2743840,
|
||||
"following_gap_samples": 40000
|
||||
},
|
||||
{
|
||||
"packet_index": 16,
|
||||
"start_sample": 2783840,
|
||||
"samples": 92256,
|
||||
"payload_sha256": "bba4133aea193e327938abc18d2a0215f1df0e821d378d530dc0b47498af9a5c"
|
||||
}
|
||||
],
|
||||
"rx_samples": 16000000,
|
||||
"rx_duration_seconds": 2,
|
||||
"automatic_retry": false,
|
||||
"tx_command_prepared_not_run": [
|
||||
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
|
||||
"-d",
|
||||
"000000000000000097c063dc323b97d3",
|
||||
"-t",
|
||||
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab066\\tx_stream.cs8",
|
||||
"-f",
|
||||
"1400000000",
|
||||
"-s",
|
||||
"8000000",
|
||||
"-n",
|
||||
"3676096",
|
||||
"-x",
|
||||
"20",
|
||||
"-a",
|
||||
"0",
|
||||
"-p",
|
||||
"0",
|
||||
"-B"
|
||||
],
|
||||
"rx_command_prepared_not_run": [
|
||||
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
|
||||
"-d",
|
||||
"000000000000000097c063dc2e5327d3",
|
||||
"-r",
|
||||
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab066\\rx_stream.cs8",
|
||||
"-f",
|
||||
"1400000000",
|
||||
"-s",
|
||||
"8000000",
|
||||
"-n",
|
||||
"16000000",
|
||||
"-l",
|
||||
"24",
|
||||
"-g",
|
||||
"24",
|
||||
"-a",
|
||||
"0",
|
||||
"-p",
|
||||
"0",
|
||||
"-B"
|
||||
]
|
||||
}
|
||||
3788
data/processed/lab066/rf_result.json
Normal file
382
data/processed/lab066/rf_summary.json
Normal file
@@ -0,0 +1,382 @@
|
||||
{
|
||||
"lab": "Lab066",
|
||||
"capture_valid": true,
|
||||
"legacy": {
|
||||
"candidates": 145,
|
||||
"header_pass": 10,
|
||||
"fec_started": 10,
|
||||
"fec_pass": 10,
|
||||
"crc_started": 10,
|
||||
"crc_pass": 10,
|
||||
"packet_output": 10,
|
||||
"exact_payload_match": 10,
|
||||
"packet_indexes": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14
|
||||
]
|
||||
},
|
||||
"t2": {
|
||||
"candidates": 145,
|
||||
"header_pass": 10,
|
||||
"fec_started": 10,
|
||||
"fec_pass": 10,
|
||||
"crc_started": 10,
|
||||
"crc_pass": 10,
|
||||
"packet_output": 10,
|
||||
"exact_payload_match": 10,
|
||||
"packet_indexes": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14
|
||||
]
|
||||
},
|
||||
"passed_by_both": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14
|
||||
],
|
||||
"passed_only_by_legacy": [],
|
||||
"passed_only_by_t2": [],
|
||||
"failed_by_both": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
9,
|
||||
15,
|
||||
16
|
||||
],
|
||||
"packet_rows": [
|
||||
{
|
||||
"packet_index": 1,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 2,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 3,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 4,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 5,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 6,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 7,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 8,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 9,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 10,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 11,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 12,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 13,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 14,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": true,
|
||||
"fec": true,
|
||||
"crc": true,
|
||||
"packet_output": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 15,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"packet_index": 16,
|
||||
"legacy": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
},
|
||||
"t2": {
|
||||
"candidate_at_expected_packet": true,
|
||||
"header": false,
|
||||
"fec": false,
|
||||
"crc": false,
|
||||
"packet_output": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"physical": {
|
||||
"symbol_error_rate": 0.011046635434412266,
|
||||
"rms_symbol_deviation": 0.5283737542111951,
|
||||
"matched_component_power": 8.359397909690596,
|
||||
"residual_power": 2.333261015636848,
|
||||
"matched_residual_db": 5.542116733950265,
|
||||
"band_gain_nonuniformity_p95_p05_db_median": 2.8700698316435833,
|
||||
"band_gain_nonuniformity_p95_p05_db_min": 2.7249057267348604,
|
||||
"band_gain_nonuniformity_p95_p05_db_max": 3.1116789732634516
|
||||
},
|
||||
"lab056_B": {
|
||||
"symbol_error_rate": 0.03495896464646465,
|
||||
"rms_symbol_deviation": 0.8453497751219741,
|
||||
"matched_residual_db": 1.4648455366051558,
|
||||
"band_gain_nonuniformity_db": 5.471381501203027
|
||||
},
|
||||
"difference_from_lab056_B": {
|
||||
"symbol_error_rate_absolute": -0.023912329212052382,
|
||||
"rms_symbol_deviation_absolute": -0.3169760209107789,
|
||||
"matched_residual_db": 4.077271197345109,
|
||||
"band_gain_nonuniformity_db": -2.6013116695594434
|
||||
},
|
||||
"lab062": {
|
||||
"symbol_error_rate": 0.0992866269165247,
|
||||
"rms_symbol_deviation": 1.2204009809742542,
|
||||
"matched_residual_db": -1.7300509612417754,
|
||||
"band_gain_nonuniformity_db": 6.660340655081576,
|
||||
"capture_status": "RF_SESSION_INVALID_TX_UNDERRUN"
|
||||
},
|
||||
"difference_from_lab062": {
|
||||
"symbol_error_rate_absolute": -0.08823999148211244,
|
||||
"rms_symbol_deviation_absolute": -0.6920272267630591,
|
||||
"matched_residual_db": 7.27216769519204,
|
||||
"band_gain_nonuniformity_db": -3.790270823437993
|
||||
},
|
||||
"classification": "PARTIAL_PACKET_RECOVERY_NO_T2_GAIN",
|
||||
"primary_result": "Аппаратно валидный Lab066 дал одинаковые 10/16 точных пакетов в legacy и T2; физическое качество лучше Lab056 B и невалидной Lab062.",
|
||||
"recommended_next_single_step": "Отдельно разрешить офлайн-сравнение header-ошибок пакетов 1-3, 9, 15-16 с принятыми пакетами по сохранённому RX; новый TX не нужен."
|
||||
}
|
||||
507
data/processed/lab066/software_gate.json
Normal file
@@ -0,0 +1,507 @@
|
||||
{
|
||||
"lab": "Lab066",
|
||||
"stage": "SOFTWARE_GATE",
|
||||
"rf_tx_count": 0,
|
||||
"tx_sha256": "621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199",
|
||||
"legacy": {
|
||||
"mode": "legacy",
|
||||
"candidates": 16,
|
||||
"header_pass": 16,
|
||||
"fec_started": 16,
|
||||
"fec_pass": 16,
|
||||
"crc_started": 16,
|
||||
"crc_pass": 16,
|
||||
"packet_output": 16,
|
||||
"exact_payload_match": 16,
|
||||
"packet_indexes": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16
|
||||
],
|
||||
"exact_ordered_payloads": true,
|
||||
"t2_state_scope": "NOT_APPLICABLE",
|
||||
"fallback_between_modes": false,
|
||||
"passed": true,
|
||||
"rows": [
|
||||
{
|
||||
"absolute_refined": 800000,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 1,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 932256,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 2,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1064512,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 3,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1196768,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 4,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1329024,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 5,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1461280,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 6,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1593536,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 7,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1725792,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 8,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1858048,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 9,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1990304,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 10,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2122560,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 11,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2254816,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 12,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2387072,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 13,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2519328,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 14,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2651584,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 15,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2783840,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 16,
|
||||
"t2_tracking_runs_for_candidate": 0
|
||||
}
|
||||
],
|
||||
"detector_statistics": {
|
||||
"total_local_peaks": 80004,
|
||||
"first_stage_candidates": 21193,
|
||||
"cheap_stage_checked": 21193,
|
||||
"cheap_stage_rejected": 20800,
|
||||
"clusters": 37,
|
||||
"representative_checks": 16,
|
||||
"inside_accepted_frame": 377
|
||||
},
|
||||
"candidate_overflow": 0
|
||||
},
|
||||
"t2": {
|
||||
"mode": "t2",
|
||||
"candidates": 16,
|
||||
"header_pass": 16,
|
||||
"fec_started": 16,
|
||||
"fec_pass": 16,
|
||||
"crc_started": 16,
|
||||
"crc_pass": 16,
|
||||
"packet_output": 16,
|
||||
"exact_payload_match": 16,
|
||||
"packet_indexes": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16
|
||||
],
|
||||
"exact_ordered_payloads": true,
|
||||
"t2_state_scope": "NEW_PREAMBLE_ESTIMATE_PER_PACKET",
|
||||
"fallback_between_modes": false,
|
||||
"passed": true,
|
||||
"rows": [
|
||||
{
|
||||
"absolute_refined": 800000,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 1,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 932256,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 2,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1064512,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 3,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1196768,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 4,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1329024,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 5,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1461280,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 6,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1593536,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 7,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1725792,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 8,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1858048,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 9,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 1990304,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 10,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2122560,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 11,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2254816,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 12,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2387072,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 13,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2519328,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 14,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2651584,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 15,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
},
|
||||
{
|
||||
"absolute_refined": 2783840,
|
||||
"reason": "CRC_PASS",
|
||||
"header": true,
|
||||
"fec_started": true,
|
||||
"fec": true,
|
||||
"crc_started": true,
|
||||
"crc": true,
|
||||
"packet_output": true,
|
||||
"packet_index_after_verdict": 16,
|
||||
"t2_tracking_runs_for_candidate": 2
|
||||
}
|
||||
],
|
||||
"detector_statistics": {
|
||||
"total_local_peaks": 80004,
|
||||
"first_stage_candidates": 21193,
|
||||
"cheap_stage_checked": 21193,
|
||||
"cheap_stage_rejected": 20800,
|
||||
"clusters": 37,
|
||||
"representative_checks": 16,
|
||||
"inside_accepted_frame": 377
|
||||
},
|
||||
"candidate_overflow": 0,
|
||||
"t2_state_reset_evidence": {
|
||||
"estimate_source": "each candidate's own preamble",
|
||||
"context_scope": "new PacketMeasure context for every candidate",
|
||||
"tracking_runs_per_accepted_packet": [
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
"all_accepted_packets_have_fresh_tracking_runs": true,
|
||||
"persistent_tracking_state_in_packet_processor": false
|
||||
}
|
||||
},
|
||||
"gaps_verified_zero": true,
|
||||
"t2_state_reset_verified_by_fresh_preamble_per_packet": true,
|
||||
"passed": true
|
||||
}
|
||||
146
data/processed/lab067/candidate_analysis.csv
Normal file
@@ -0,0 +1,146 @@
|
||||
candidate_number,position,nearest_packet,difference_samples,category,strict_reason
|
||||
1,4902630,1,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
2,4900598,1,-2032,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
3,4904662,1,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
4,4898566,1,-4064,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
5,4896534,1,-6096,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
6,4906694,1,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
7,4894502,1,-8128,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
8,4908726,1,6096,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
9,4910758,1,8128,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
10,4902630,1,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
11,4900598,1,-2032,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
12,4904662,1,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
13,4898566,1,-4064,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
14,4906694,1,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
15,4902630,1,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
16,4900598,1,-2032,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
17,4904662,1,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
18,4968617,1,65987,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
19,4958408,1,55778,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
20,4982295,2,-52591,UNRELATED,PHY_HEADER_MAGIC_MISMATCH
|
||||
21,5034886,2,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
22,5032854,2,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
23,5036918,2,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
24,5030822,2,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
25,5028790,2,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
26,5038949,2,4063,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
27,5026758,2,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
28,5040981,2,6095,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
29,5043013,2,8127,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
30,5034886,2,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
31,5032854,2,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
32,5034886,2,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
33,5036918,2,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
34,5032854,2,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
35,5036918,2,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
36,5030822,2,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
37,5167142,3,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
38,5165110,3,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
39,5169174,3,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
40,5163078,3,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
41,5161046,3,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
42,5171206,3,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
43,5159014,3,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
44,5173238,3,6096,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
45,5175270,3,8128,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
46,5167142,3,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
47,5169174,3,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
48,5165110,3,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
49,5171206,3,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
50,5163078,3,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
51,5167142,3,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
52,5169174,3,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
53,5165110,3,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
54,5198234,3,31092,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
55,5196202,3,29060,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
56,5194172,3,27030,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
57,5228601,3,61459,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
58,5230634,3,63492,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
59,5232665,3,65523,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
60,5220198,3,53056,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
61,5299398,4,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
62,5431654,5,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
63,5563910,6,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
64,5696166,7,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
65,5828422,8,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
66,5960678,9,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
67,5958646,9,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
68,5962710,9,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
69,5956614,9,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
70,5954582,9,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
71,5952550,9,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
72,5960678,9,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
73,5958646,9,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
74,5962710,9,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
75,5956614,9,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
76,5960678,9,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_CHECKSUM_MISMATCH
|
||||
77,5958646,9,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
78,5962710,9,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
79,5964742,9,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
80,5966774,9,6096,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
81,5968806,9,8128,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
82,5964742,9,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
83,6017761,9,57083,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
84,6015727,9,55049,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
85,6092934,10,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
86,6225190,11,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
87,6355414,12,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
88,6353382,12,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
89,6351350,12,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
90,6349317,12,-8129,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
91,6355414,12,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
92,6353382,12,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
93,6351350,12,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
94,6355414,12,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
95,6357446,12,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
96,6487670,13,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
97,6485638,13,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
98,6483606,13,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
99,6481574,13,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
100,6487670,13,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
101,6485638,13,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
102,6483606,13,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
103,6487670,13,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
104,6485638,13,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
105,6489702,13,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
106,6617894,14,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
107,6615862,14,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
108,6613830,14,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
109,6617894,14,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
110,6621958,14,0,TRUE_PREAMBLE_POSITION,CRC_PASS
|
||||
111,6750150,15,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
112,6748118,15,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
113,6746086,15,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
114,6750150,15,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
115,6748118,15,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
116,6750150,15,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
117,6754214,15,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
118,6752182,15,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
119,6756246,15,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
120,6758278,15,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
121,6760309,15,6095,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
122,6762341,15,8127,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
123,6754214,15,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
124,6752182,15,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
125,6756246,15,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
126,6758278,15,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
127,6754214,15,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_MAGIC_MISMATCH
|
||||
128,6752182,15,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
129,6756246,15,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
130,6880374,16,-6096,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
131,6878342,16,-8128,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
132,6886470,16,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_VERSION_MISMATCH
|
||||
133,6884438,16,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
134,6888502,16,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
135,6882406,16,-4064,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
136,6890534,16,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
137,6892566,16,6096,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
138,6894597,16,8127,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
139,6888502,16,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
140,6886470,16,0,TRUE_PREAMBLE_POSITION,PHY_HEADER_VERSION_MISMATCH
|
||||
141,6892566,16,6096,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
142,6890534,16,4064,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
143,6884438,16,-2032,ZERO_GAP,PHY_HEADER_MAGIC_MISMATCH
|
||||
144,6888502,16,2032,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
145,6902724,16,16254,REPEATED_PRBS_LOCAL_MAXIMUM,PHY_HEADER_MAGIC_MISMATCH
|
||||
|
BIN
data/processed/lab067/candidate_checks_by_packet.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
data/processed/lab067/gap_background_by_packet.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
2305
data/processed/lab067/header_bit_errors.csv
Normal file
BIN
data/processed/lab067/header_error_position_map.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
data/processed/lab067/header_errors_by_packet.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
17
data/processed/lab067/header_packet_comparison.csv
Normal file
@@ -0,0 +1,17 @@
|
||||
packet,strict_result,candidate_position,expected_packet_position,candidate_difference_samples,preamble_errors,preamble_error_rate,preamble_correlation,header_raw_errors,header_voted_errors,header_copy_1_errors,header_copy_2_errors,header_copy_3_errors,magic_errors,version_errors,length_errors,checksum_errors,expected_header_bits,received_raw_header_bits,voted_header_bits,header_accepted,header_reason,legacy_header_bits,t2_header_bits,legacy_initial_frequency_hz,t2_initial_frequency_hz,reference_frequency_hz,legacy_frequency_error_vs_reference_hz,initial_phase_error_rad,timing_offset_samples,fractional_timing_offset,reference_timing_offset_samples,reference_fractional_timing_offset,local_matched_power,local_residual_power,local_matched_residual_db,rms_symbol_deviation,legacy_t2_header_differences,classification
|
||||
1,HEADER_FAIL,4902630,4902630,0,15,0.01171875,0.8981139142983712,3,1,1,2,0,0,0,1,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000101000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000110000000000000111000000000,110100111001000100000001000000010000110100001000,False,CHECKSUM_MISMATCH,111111000101000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000110000000000000111000000000,111111000101000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000110000000000000111000000000,-2.662272181646709,-5.3879680108224575,-2.6542601970280364,-0.008011984618672585,1.8341164291785854,0,0.0,0,0.0,8.982336070665001,2.7583799759789063,5.127352085281537,0.5541564541425134,0,HEADER_SPECIFIC_FAILURE
|
||||
2,HEADER_FAIL,5034886,5034886,0,10,0.0078125,0.892574719565451,6,1,3,2,1,0,0,1,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000100000111000000000000000000000111000000000000000000000001000001110000111111000000000000000000111000000000,110100111001000100000001000000010010110000001000,False,CHECKSUM_MISMATCH,111111000111000000111111111000000111000100000111000000000000000000000111000000000000000000000001000001110000111111000000000000000000111000000000,111111000111000000111111111000000111000100000111000000000000000000000111000000000000000000000011000001110000111111000000000000000000111000000000,-11.085402212024045,-5.423727145761877,2.664169158774504,-13.749571370798549,1.8865220093820032,0,0.0,0,0.0,8.566883580374462,3.867819679092585,3.4535664637802532,0.6719263565992358,1,HEADER_SPECIFIC_FAILURE
|
||||
3,HEADER_FAIL,5167142,5167142,0,11,0.00859375,0.8832445602414427,3,1,1,1,1,1,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111110001000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100110001000100000001000000010000110000001000,False,MAGIC_MISMATCH,111111000111000000111110001000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111110001000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,-2.14390103272374,2.717358680061899,4.3756668288999,-6.51956786162364,1.837976468233186,0,0.0,0,0.0,8.391114739306301,2.917171371178907,4.5885771684300085,0.589618554298201,0,HEADER_SPECIFIC_FAILURE
|
||||
4,PASS,5299398,5299398,0,12,0.009375,0.8702559090330103,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,-4.969843852460903,0.5011874511379912,3.928841196279306,-8.898685048740209,1.823004794704275,0,0.0,0,0.0,8.221366239802954,2.9179318124892393,4.498688563977461,0.5957520861601466,0,PASSED
|
||||
5,PASS,5431654,5431654,0,15,0.01171875,0.882298526549992,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,3.163024030078619,-1.2710648335872299,1.1365290837564324,2.026494946322187,1.8355890323417587,0,0.0,0,0.0,8.188755217773341,1.2708156134102264,8.091353471792932,0.39394203715952975,0,PASSED
|
||||
6,PASS,5563910,5563910,0,17,0.01328125,0.8834263035683648,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,5.595453900367469,1.2623874159193273,-4.567512776067651,10.162966676435119,1.7721238280090583,0,0.0,0,0.0,8.667865149110309,1.6073728447094802,7.317955191422993,0.43062797582960244,0,PASSED
|
||||
7,PASS,5696166,5696166,0,7,0.00546875,0.8957036694801145,1,0,0,0,1,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000001,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000001,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,-1.2654548420073324,-1.236432022604831,-6.203901481459032,4.9384466394516995,1.8155098517430892,0,0.0,0,0.0,8.65014337171221,2.0185187625245264,6.319905151171046,0.4830640770103008,1,PASSED
|
||||
8,PASS,5828422,5828422,0,10,0.0078125,0.8935154947988603,3,0,1,2,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111001000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000010000111000000000,110100111001000100000001000000010000110000001000,True,,111001000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000010000111000000000,111001000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000010000111000000000,9.131379183391202,-0.4710188398180419,4.02876530540373,5.102613877987472,1.6973544391851634,0,0.0,0,0.0,8.476859162285322,2.688967210304384,4.986494612866651,0.5632163702340075,0,PASSED
|
||||
9,HEADER_FAIL,5960678,5960678,0,13,0.01015625,0.8831012072365596,4,1,1,2,1,0,0,1,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000011010000000000000000000111000000000000000000000111011000000000111111000000000000000000111000000000,110100111001000100000001000000011000110000001000,False,CHECKSUM_MISMATCH,111111000111000000111111111000000111000000000011010000000000000000000111000000000000000000000111011000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000011010000000000000000000111000000000000000000000111011000000000111111000000000000000000111000000000,6.363378164483905,-1.9815016298670118,8.696281322717777,-2.332903158233872,1.7240388753166553,0,0.0,0,0.0,8.379835253157635,3.513421286396085,3.7750525233897125,0.6475113327264761,0,HEADER_SPECIFIC_FAILURE
|
||||
10,PASS,6092934,6092934,0,14,0.0109375,0.8756981622368548,1,0,0,0,1,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111110111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111110111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111110111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,10.521050631968299,4.784422094967777,8.894608707236657,1.6264419247316422,1.6172834220436263,0,0.0,0,0.0,7.7745725932128815,2.478953865410118,4.964080786327893,0.5646716196549459,0,PASSED
|
||||
11,PASS,6225190,6225190,0,14,0.0109375,0.8739448578846127,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,-4.3377788034794795,5.576222280700207,6.2889992427567645,-10.626778046236243,1.8042319285279784,0,0.0,0,0.0,8.281301835417366,2.613504672826743,5.008753330856255,0.5617749030480063,0,PASSED
|
||||
12,PASS,6357446,6357446,0,15,0.01171875,0.8691086828109845,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,-14.662329907046818,-12.429475985972452,-7.481009787452366,-7.181320119594452,1.9299825595605988,0,0.0,0,0.0,8.105161843192903,1.785984725783043,6.568839508606049,0.4694161471804715,0,PASSED
|
||||
13,PASS,6489702,6489702,0,19,0.01484375,0.8870876180609537,0,0,0,0,0,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,2.005711398433546,-3.8713317585287843,-9.48236864846436,11.488080046897906,1.7755837460769197,0,0.0,0,0.0,8.379598937532618,1.5074086416620165,7.44992232360151,0.42413477685697976,0,PASSED
|
||||
14,PASS,6621958,6621958,0,13,0.01015625,0.8979602974476922,1,0,0,0,1,0,0,0,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000110000000000000111111000000000000000000111000000000,110100111001000100000001000000010000110000001000,True,,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000110000000000000111111000000000000000000111000000000,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,7.210729633183354,3.326192426336044,-3.8425458753995345,11.053275508582889,1.6778789052916618,0,0.0,0,0.0,8.575750726813316,2.4203963781579967,5.493856544116208,0.5312600671059894,1,PASSED
|
||||
15,HEADER_FAIL,6754214,6754214,0,12,0.009375,0.8887378180983065,7,2,2,2,3,1,0,1,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,111111000111000011111111111000000111000000000111000000000000000000000111001000110000000000000111000000000000111111000000000000000000111001100000,110101111001000100000001001000010000110000001000,False,MAGIC_MISMATCH,111111000111000011111111111000000111000000000111000000000000000000000111001000110000000000000111000000000000111111000000000000000000111001100000,111111000111000011111111111000000111000000000111000000000000000000000111001000110000000000000111000000000000111111000000000000000000111001100000,-5.7315977256001345,-3.5200403113930196,0.10139388237069231,-5.832991607970826,1.7811965898235738,0,0.0,0,0.0,7.968245167205011,3.1321489434833123,4.055202820682332,0.6269600363881329,0,HEADER_SPECIFIC_FAILURE
|
||||
16,HEADER_FAIL,6886470,6886470,0,16,0.0125,0.8805097957519517,5,2,1,2,2,0,1,1,0,111111000111000000111111111000000111000000000111000000000000000000000111000000000000000000000111000000000000111111000000000000000000111000000000,101111000111000000111111111000000111000000000111000000000011000000000111000000000000001100000111000000000000111111000000000000000000111000000000,110100111001000100010001000001010000110000001000,False,VERSION_MISMATCH,101111000111000000111111111000000111000000000111000000000011000000000111000000000000001100000111000000000000111111000000000000000000111000000000,000111000000111111100000000101011110110001101000011101111000001111000110111101111011100000111001010010111111000000111111111111111011000111111111,4.7344866536019055,228.10491338016192,10.381403191597384,-5.646916537995478,1.6829380426891485,0,0.0,0,0.0,8.084601213892752,3.04749463363358,4.237156529516785,0.613962963260252,107,HEADER_SPECIFIC_FAILURE
|
||||
|
BIN
data/processed/lab067/local_signal_quality_by_packet.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
data/processed/lab067/preamble_errors_by_packet.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
data/processed/lab067/preamble_header_transition.png
Normal file
|
After Width: | Height: | Size: 838 KiB |
1724
data/processed/lab067/summary.json
Normal file
BIN
data/processed/lab067/timing_and_cfo_by_packet.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
49
data/processed/lab067/transition_metrics.csv
Normal file
@@ -0,0 +1,49 @@
|
||||
packet,region,symbols,decision_errors,magnitude_median,magnitude_minimum,phase_error_rms_rad,imaginary_rms,timing_offset_samples
|
||||
1,preamble_tail,128,0,1.0988442307313235,0.43466351049701235,0.36545432588063453,0.3677449037710503,0
|
||||
1,header,144,3,1.0815752733984212,0.5778378379912865,0.6187439503436525,0.551502400297575,0
|
||||
1,postheader,128,2,1.1114278978759744,0.4714742841146855,0.5693534448896019,0.49591231338100494,0
|
||||
2,preamble_tail,128,0,1.074345037947809,0.5715381413183729,0.5169701760998907,0.4918784327994302,0
|
||||
2,header,144,6,1.0888449278310801,0.4753465266196904,0.7542889272180515,0.6186207192551644,0
|
||||
2,postheader,128,0,1.1317475637994039,0.6993167478293558,0.28345944315453764,0.2904885233790264,0
|
||||
3,preamble_tail,128,0,1.0919580830559625,0.4931835747157359,0.577421183092019,0.5161252295458062,0
|
||||
3,header,144,4,1.152609811941216,0.6541218073505881,0.5437712455230955,0.433109513511742,0
|
||||
3,postheader,128,0,1.0857706086674181,0.3396593308737327,0.2976136824815135,0.3003548051915534,0
|
||||
4,preamble_tail,128,0,1.1087294828126495,0.5024192881228802,0.6792582574914069,0.6647511818530001,0
|
||||
4,header,144,0,1.1507766975854852,0.42562892480326897,0.3156584316410723,0.33839321792646554,0
|
||||
4,postheader,128,0,1.1449814637127051,0.5937437256113064,0.29460252451816493,0.31949283720140587,0
|
||||
5,preamble_tail,128,0,1.1434636342884739,0.6047967729049536,0.35172140313369066,0.3535407042323131,0
|
||||
5,header,144,0,1.1363877434085625,0.6007022877000862,0.2522418948656545,0.27555358339271957,0
|
||||
5,postheader,128,5,1.0966485328526976,0.41749527315450785,0.6464901825183261,0.5318845761533794,0
|
||||
6,preamble_tail,128,0,1.1318097006554524,0.6221411610236426,0.2794053247219388,0.30212984078304644,0
|
||||
6,header,144,0,1.118944116022563,0.604506255970942,0.4000248179092374,0.40617877592291496,0
|
||||
6,postheader,128,3,1.1203373379450718,0.5733983281027794,0.6433269203677763,0.5585770337413266,0
|
||||
7,preamble_tail,128,0,1.098335506453933,0.685298350349321,0.31267846128306165,0.3400209740040822,0
|
||||
7,header,144,1,1.0859515485682385,0.4918795883455653,0.49770271058274784,0.48404835853198735,0
|
||||
7,postheader,128,3,1.078419996569039,0.44698965664144213,0.5990105590866479,0.5112821191437632,0
|
||||
8,preamble_tail,128,0,1.0950824513758386,0.5415446078858628,0.37054349208551474,0.38513707581929985,0
|
||||
8,header,144,3,1.0904644019616199,0.43076194377790394,0.6094384014423195,0.5588536614362375,0
|
||||
8,postheader,128,2,1.0935087479377619,0.63642176553416,0.4463244127331142,0.43911459515487505,0
|
||||
9,preamble_tail,128,2,1.093901338967227,0.4388973792742324,0.5923392165896195,0.5322335060592657,0
|
||||
9,header,144,4,1.1195324105485278,0.49998913621143454,0.611185029044535,0.5659527404173302,0
|
||||
9,postheader,128,0,1.1053805607840526,0.640256963735712,0.28052596266122526,0.30630431218636367,0
|
||||
10,preamble_tail,128,3,1.1419841905626784,0.5060536577715014,0.5971065417005673,0.5330612346503504,0
|
||||
10,header,144,1,1.1147443336597755,0.6501217642416475,0.39358947394490806,0.40010760065192014,0
|
||||
10,postheader,128,0,1.1787629415552199,0.42900718390148934,0.2926514547716184,0.3306690896219249,0
|
||||
11,preamble_tail,128,3,1.1704570892176922,0.6573740193329803,0.6151666707460389,0.6107317885152251,0
|
||||
11,header,144,0,1.12981219997058,0.5282686497447672,0.324658042677611,0.3384614869201178,0
|
||||
11,postheader,128,0,1.1070044356208588,0.5046571226194821,0.33630657442006734,0.36220237044356646,0
|
||||
12,preamble_tail,128,4,1.1293212243971127,0.5975355368621857,0.5056848722345468,0.41499438743931705,0
|
||||
12,header,144,0,1.1346606346022847,0.37281496808234904,0.2862848068844218,0.3172306512537894,0
|
||||
12,postheader,128,1,1.1452864327334882,0.4013942634977783,0.4893751135204076,0.4862028130220328,0
|
||||
13,preamble_tail,128,0,1.1020405947443312,0.5476951608093663,0.26825405646139866,0.299908757841907,0
|
||||
13,header,144,0,1.1246852022329477,0.5448659176501547,0.3819108989758959,0.3972670513029744,0
|
||||
13,postheader,128,2,1.0925865889420296,0.5942311935828164,0.5526657411269684,0.47037502313805846,0
|
||||
14,preamble_tail,128,0,1.0986885517578937,0.4240058786452209,0.307170345418273,0.30833136259021016,0
|
||||
14,header,144,1,1.1117645619461933,0.5698477702591676,0.5701021956757241,0.5678681526072823,0
|
||||
14,postheader,128,3,1.106509873733971,0.5370530426198264,0.594466571272217,0.5534673376824929,0
|
||||
15,preamble_tail,128,1,1.0605799496659238,0.5096523999436898,0.4395937997414911,0.41506255958111626,0
|
||||
15,header,144,7,1.0998344881084918,0.562650314086593,0.6887166853647746,0.5814901000470972,0
|
||||
15,postheader,128,2,1.1227074410764701,0.5161407533490777,0.5946319825247028,0.5701686238399535,0
|
||||
16,preamble_tail,128,2,1.1150803467611397,0.5398340206811254,0.5646446219736847,0.48272300998033535,0
|
||||
16,header,144,5,1.1024025293008193,0.41214915514462663,0.6355591984079474,0.49483023185734204,0
|
||||
16,postheader,128,0,1.1777412846771014,0.3344663773895672,0.3015102248779219,0.3386110581722476,0
|
||||
|
38730
data/processed/lab067/transition_symbols.json
Normal file
@@ -663,3 +663,81 @@
|
||||
- **Независимая проверка:** прямой пересчёт из TX/RX CS8 подтвердил все SHA,
|
||||
счётчики, фоны, matched/residual/`h`, ступени и спектральные полосы;
|
||||
расхождений нет. Quick gate и pytest после RF:986 passed,0 failed.
|
||||
|
||||
## Lab065: измерение уровня при TX VGA 20 дБ
|
||||
|
||||
- **База:** Lab064 зафиксирована коммитом
|
||||
`cff226e0bf9e36a627829ea11a4ca3292ae20953`; push не выполнялся.
|
||||
- **Единственное изменение параметра:** TX VGA 10 → 20 дБ. Частота 1400 МГц,
|
||||
RX LNA/VGA 24/24 дБ, усилители, геометрия, антенны и кабели сохранены.
|
||||
- **Waveform:** точная копия Lab064, 6000000 отсчётов, 0,75 с; SHA-256
|
||||
`fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9`.
|
||||
- **Подготовлено:** RX-first runner, максимальные счётчики потерь, сравнение
|
||||
линейности внутри Lab065 и по одинаковым уровням Lab064/Lab065, численные
|
||||
правила классификации. Software gate PASS.
|
||||
- **RX-only preflight:** обе HackRF и роли подтверждены, reg29=0x26 у MASTER и
|
||||
SLAVE, CLKIN обнаружен на третьем опросе через201,986мс. Фон:800000 отсчётов,
|
||||
AC RMS1,056565 кода, max|I|/|Q|=6/8, clipping0, overruns0. Cleanup PASS.
|
||||
- **Граница:** `RF TX=0`. Lab065 не коммитировалась. Нужны рассмотрение
|
||||
подготовки и отдельное прямое разрешение на один TX; Lab066 не начинать.
|
||||
- [Текущее состояние](lab065_current_state.md),
|
||||
[RX-only отчёт](lab065_rx_preflight.md),
|
||||
[подготовка](../data/processed/lab065/preparation.json),
|
||||
[software gate](../data/processed/lab065/software_gate.json),
|
||||
[preflight](../data/processed/lab065/preflight_result.json).
|
||||
- **Единственный RF-сеанс:** TX1, автоматических повторов0; underrun/overrun/
|
||||
clipping0, cleanup PASS, процессы CLEAR. RX16000000 отсчётов, SHA-256
|
||||
`845d7b7dcaef70e179ab69e6ba0041601859630786d2f4d51c16a8c1c7869d7e`.
|
||||
- **Линейность:** ошибки ступеней −0,077363/+0,034934/−0,013858дБ, компрессии
|
||||
L2/L3 нет. Медианный прирост matched power+11,233489дБ, изменение остатка
|
||||
+0,517004дБ, улучшение matched/residual+10,734495дБ.
|
||||
- **Результат:** `LINEAR_GAIN_IMPROVEMENT`. Фон стабилен; диагностические полосы
|
||||
остатка растут слабее matched power. [RF-отчёт](lab065_rf_result.md),
|
||||
[RF JSON](../data/processed/lab065/rf_result.json),
|
||||
[capture JSON](../data/processed/lab065/capture_result.json).
|
||||
- **STOP:** `RF TX=0`; повторов и изменения усилений не было. Lab065 не
|
||||
коммитировалась, push отсутствует, Lab066 не создана.
|
||||
- **Независимая проверка:** прямой пересчёт CS8 подтвердил SHA, счётчики,
|
||||
локализацию, фон, matched/residual, `h`, CFO и спектральные полосы. Расхождений
|
||||
нет. Quick gate1000/38/13 PASS, pytest1000 passed, diff check PASS.
|
||||
- **Фиксация:** Lab065 зафиксирована локальным коммитом
|
||||
`dacf04f2d0a15c0c1b56a7fb527db61fc2e38462`; push не выполнялся.
|
||||
|
||||
## Lab066 — исправленный повтор полного 500K PHY-сеанса
|
||||
|
||||
- **Основание:** Lab062 остаётся `RF_SESSION_INVALID_TX_UNDERRUN`, legacy/T2
|
||||
0/16. Lab066 исправляет отсутствовавшие межпакетные паузы и использует
|
||||
максимальный наблюдавшийся счётчик потерь.
|
||||
- **TX:** 16 полных PHY-пакетов по256 байт, packet waveform92256 отсчётов;
|
||||
15 нулевых пауз по40000 отсчётов/5мс и guards по800000 отсчётов/100мс.
|
||||
Всего3676096 отсчётов,0,459512с; SHA-256
|
||||
`621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199`.
|
||||
- **Параметры:** 1400МГц, Fs8Мвыб/с,500ксимв/с, несущая+50кГц,
|
||||
TX VGA20дБ, RX LNA/VGA24/24дБ, amplifier/Bias-Tee OFF; прежняя геометрия.
|
||||
- **Software gate:** legacy и T2 независимо дали16/16 HEADER/FEC/CRC,
|
||||
packet output и точных упорядоченных payload. Fallback отсутствует; T2
|
||||
инициализируется по собственной преамбуле каждого пакета.
|
||||
- **RX-only preflight:** MASTER/SLAVE reg29=0x26, CLKIN DETECTED через132,113мс;
|
||||
AC RMS0,827637, max|I|/|Q|=4/5, clipping0, maximum overruns0. Cleanup PASS,
|
||||
процессы CLEAR.
|
||||
- **Проверки:** quick gate1013/39/13 PASS, pytest1013 passed, diff check PASS;
|
||||
verifier и adversary не оставили открытых замечаний.
|
||||
- [Состояние](lab066_current_state.md), [RX-only отчёт](lab066_rx_preflight.md),
|
||||
[подготовка](../data/processed/lab066/preparation.json),
|
||||
[software gate](../data/processed/lab066/software_gate.json),
|
||||
[preflight](../data/processed/lab066/preflight_result.json).
|
||||
- **STOP:** RF TX=0; требуется отдельное прямое разрешение на один TX. Lab066
|
||||
не коммитировалась, push не выполнялся, Lab067 не создавалась.
|
||||
- **Единственный RF-сеанс:** один TX, повторов0; maximum underrun/overrun0/0,
|
||||
clipping0, cleanup PASS, процессы CLEAR. RX16000000 отсчётов, SHA-256
|
||||
`72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0`.
|
||||
- **Строгий результат:** legacy и T2 совпали — по10/16 точных пакетов;
|
||||
приняты4–8 и10–14, отказали на header пакеты1–3,9,15,16. Выигрыша T2 нет.
|
||||
- **Физическое качество:** SER0,011047, RMS0,528374, matched/residual
|
||||
+5,542117дБ, медианная неравномерность2,870070дБ. Все четыре показателя лучше
|
||||
Lab056 B и невалидной Lab062.
|
||||
- **Итог:** `PARTIAL_PACKET_RECOVERY_NO_T2_GAIN`. [RF-отчёт](lab066_rf_result.md),
|
||||
[RF summary](../data/processed/lab066/rf_summary.json),
|
||||
[packet CSV](../data/processed/lab066/packet_comparison.csv).
|
||||
- **После сеанса:** RF TX=0, второй TX не выполнялся. Lab066 не
|
||||
коммитировалась, push не выполнялся, Lab067 не создавалась.
|
||||
|
||||
70
docs/lab065_current_state.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Lab065: подготовка измерения при TX VGA 20 дБ
|
||||
|
||||
Lab065 повторяет измерение Lab064 с одним изменением аппаратного параметра:
|
||||
TX VGA повышен с 10 до 20 дБ. Частота 1400000000 Гц, RX LNA/VGA 24/24 дБ,
|
||||
геометрия, антенны, кабели, общая опора и цифровой waveform сохранены.
|
||||
|
||||
TX-файл Lab065 является побайтовой копией Lab064. Его SHA-256 должен быть равен
|
||||
`fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9`.
|
||||
Длина остаётся равной 6000000 комплексных отсчётов и 0,75 с. Ступени
|
||||
−18/−12/−6/0 дБ, PRBS7, BPSK, RRC, 20000 символов/с, Fs 8000000 отсчётов/с и
|
||||
цифровая несущая +50000 Гц не меняются.
|
||||
|
||||
## Сравнение с Lab064
|
||||
|
||||
Для L0, L1, L2 и L3 анализатор выдаёт изменение согласованной мощности,
|
||||
`|h|²`, остаточной мощности и matched/residual. Номинальное изменение TX VGA
|
||||
равно +10 дБ и приводится как справочная величина, а не как условие PASS.
|
||||
|
||||
До RF зафиксированы численные правила:
|
||||
|
||||
- линейность внутри Lab065: максимальная абсолютная ошибка шага не более 1 дБ;
|
||||
- компрессия: ошибка верхнего шага не более −1,5 дБ;
|
||||
- заметный прирост: медиана прироста matched power не менее 3 дБ;
|
||||
- заметное улучшение matched/residual: медиана не менее 3 дБ;
|
||||
- согласованность прироста четырёх уровней: разброс не более 3 дБ;
|
||||
- остаток считается растущим почти как сигнал, если его медианный прирост
|
||||
отстаёт от медианного прироста matched power не более чем на 3 дБ.
|
||||
|
||||
Эти пороги нужны для воспроизводимой классификации конкретного опыта. Они не
|
||||
заменяют измерение абсолютной RF-мощности.
|
||||
|
||||
## Аппаратная граница
|
||||
|
||||
Подготовка допускает программные проверки, common-clock preflight и RX-only
|
||||
запись фона. Любая команда с `-t` внутри preflight запрещена. Передача Lab065
|
||||
требует нового прямого разрешения пользователя и в текущий этап не входит.
|
||||
Автоматических повторов и изменения усилений после ошибки нет.
|
||||
|
||||
`RF TX = 0`.
|
||||
|
||||
## Результат подготовки
|
||||
|
||||
Подготовка выполнена от HEAD
|
||||
`cff226e0bf9e36a627829ea11a4ca3292ae20953`. Software gate прошёл. Побайтовая
|
||||
копия waveform имеет требуемый SHA-256, подготовленная TX-команда содержит
|
||||
TX VGA 20 дБ, а RX-команда сохраняет LNA/VGA 24/24 дБ.
|
||||
|
||||
Common-clock и RX-only preflight прошёл. Обе HackRF One r9 обнаружены с
|
||||
прежними ролями. MASTER и SLAVE имеют reg29 `0x26`; SLAVE CLKIN обнаружен на
|
||||
третьем опросе через 201,986 мс. В RX-фоне из 800000 отсчётов AC RMS равен
|
||||
1,056565 кода, max |I|/|Q| равны 6/8, clipping и overruns отсутствуют.
|
||||
Процессы до и после проверки имеют состояние CLEAR, ошибок очистки нет.
|
||||
|
||||
Lab065 готова к рассмотрению перед одним RF-сеансом. Текущая подготовка такого
|
||||
разрешения не содержит. `RF TX = 0`.
|
||||
|
||||
## Единственный RF-сеанс
|
||||
|
||||
После отдельного прямого разрешения выполнен один TX. Общая опора прошла:
|
||||
MASTER/SLAVE reg29 `0x26`, SLAVE CLKIN обнаружен на втором опросе через
|
||||
136,897 мс. TX underrun, RX overrun, clipping и ошибки очистки отсутствуют.
|
||||
Повторов не было. RX содержит 16000000 отсчётов, SHA-256
|
||||
`845d7b7dcaef70e179ab69e6ba0041601859630786d2f4d51c16a8c1c7869d7e`.
|
||||
|
||||
Медианный прирост matched power относительно Lab064 равен +11,233489 дБ,
|
||||
медианное изменение остатка +0,517004 дБ, улучшение matched/residual
|
||||
+10,734495 дБ. Ошибки ступеней равны −0,077363/+0,034934/−0,013858 дБ.
|
||||
Признаков компрессии нет. Классификация: `LINEAR_GAIN_IMPROVEMENT`.
|
||||
|
||||
[Полный RF-отчёт](lab065_rf_result.md). `RF TX = 0`.
|
||||
114
docs/lab065_rf_result.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Lab065: один OTA-сеанс при TX VGA 20 дБ
|
||||
|
||||
15 сентября 2026 года выполнен один разрешённый OTA-сеанс Lab065 на частоте
|
||||
1400000000 Гц. Передача использовала точную копию waveform Lab064 с SHA-256
|
||||
`fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9`.
|
||||
Автоматических и ручных повторов не было.
|
||||
|
||||
## Аппаратная валидность
|
||||
|
||||
Перед TX обе HackRF One r9 и их роли подтверждены. MASTER и SLAVE имели reg29
|
||||
`0x26`; SLAVE CLKIN обнаружен на втором опросе через 136,897 мс. RX стартовал
|
||||
раньше TX. Результат сеанса:
|
||||
|
||||
```text
|
||||
COMPLETED TX SESSIONS = 1
|
||||
AUTOMATIC TX REPEATS = 0
|
||||
TX UNDERRUN MAXIMUM = 0
|
||||
RX OVERRUN MAXIMUM = 0
|
||||
RX CLIPPING = NO
|
||||
CLEANUP ERRORS = []
|
||||
SDR PROCESSES = CLEAR
|
||||
```
|
||||
|
||||
RX содержит 16000000 комплексных отсчётов, 32000000 байт и 2,0 с. SHA-256:
|
||||
`845d7b7dcaef70e179ab69e6ba0041601859630786d2f4d51c16a8c1c7869d7e`.
|
||||
Максимумы всей записи равны `|I|=10` и `|Q|=10`; rail-кодов нет.
|
||||
|
||||
## Линейность внутри Lab065
|
||||
|
||||
| Переход | TX, дБ | RX matched, дБ | Ошибка, дБ |
|
||||
|---|---:|---:|---:|
|
||||
| L0 → L1 | +5,977270 | +5,899908 | −0,077363 |
|
||||
| L1 → L2 | +5,998157 | +6,033091 | +0,034934 |
|
||||
| L2 → L3 | +5,998257 | +5,984400 | −0,013858 |
|
||||
|
||||
Максимальная абсолютная ошибка равна 0,077363 дБ при границе 1 дБ. Ошибка
|
||||
верхней ступени равна −0,013858 дБ при пороге компрессии −1,5 дБ. Согласованная
|
||||
мощность L3 выросла относительно L2 на 5,984400 дБ. Признаков компрессии на
|
||||
L2/L3 нет.
|
||||
|
||||
## Сравнение Lab064 и Lab065
|
||||
|
||||
| Уровень | Matched, дБ | Остаток, дБ | Улучшение matched/residual, дБ | Изменение \|h\|², дБ |
|
||||
|---|---:|---:|---:|---:|
|
||||
| L0 | +11,179328 | −0,243276 | +11,422604 | +11,179328 |
|
||||
| L1 | +11,119466 | −2,366286 | +13,485752 | +11,119466 |
|
||||
| L2 | +11,323670 | +1,277284 | +10,046386 | +11,323670 |
|
||||
| L3 | +11,287650 | +3,907581 | +7,380069 | +11,287650 |
|
||||
|
||||
Медианный прирост согласованной мощности равен +11,233489 дБ. Его разброс по
|
||||
четырём уровням составляет 0,204204 дБ. Медианное изменение остатка равно
|
||||
+0,517004 дБ, а медианное улучшение matched/residual равно +10,734495 дБ.
|
||||
|
||||
При TX VGA 20 дБ matched/residual для L0/L1/L2/L3 равен
|
||||
−20,663889/−12,091711/−6,645283/−4,753267 дБ. Отношение улучшилось на каждом
|
||||
уровне, хотя даже на L3 согласованная составляющая остаётся слабее остатка.
|
||||
|
||||
## Комплексный коэффициент передачи
|
||||
|
||||
| Уровень | h Lab064 | h Lab065 | \|h\| Lab064 | \|h\| Lab065 | Изменение фазы, градусы |
|
||||
|---|---|---|---:|---:|---:|
|
||||
| L0 | 0,001876 − j0,004508 | −0,006915 + j0,016279 | 0,004883 | 0,017686 | −179,581 |
|
||||
| L1 | 0,004471 − j0,001938 | −0,010180 + j0,014271 | 0,004873 | 0,017529 | +148,932 |
|
||||
| L2 | 0,003277 − j0,003478 | −0,009134 + j0,015045 | 0,004779 | 0,017600 | +167,966 |
|
||||
| L3 | 0,003384 − j0,003391 | −0,009107 + j0,015028 | 0,004791 | 0,017572 | +166,276 |
|
||||
|
||||
Модуль `h` внутри Lab065 почти постоянен: 0,017529…0,017686. Его мощность
|
||||
относительно Lab064 выросла на 11,119…11,324 дБ, что согласуется с matched
|
||||
power. Фаза между отдельными сеансами не имеет непрерывной временной привязки,
|
||||
поэтому приведённый поворот является диагностикой конкретных записей, а не
|
||||
калиброванной фазовой характеристикой тракта.
|
||||
|
||||
## Фон и остаточные полосы
|
||||
|
||||
Пять нулевых окон Lab065 имеют AC RMS 0,763722…0,857191 кода. Разброс фоновой
|
||||
AC-мощности равен 1,002840 дБ при границе 3 дБ, поэтому фон классифицирован как
|
||||
стабильный. В Lab064 разброс составлял 0,119530 дБ; фон Lab065 стабильный, но
|
||||
межоконный разброс стал больше.
|
||||
|
||||
Изменение диагностических полос остатка относительно Lab064:
|
||||
|
||||
| Уровень | 50 кГц, дБ | 100 кГц, дБ | 150 кГц, дБ |
|
||||
|---|---:|---:|---:|
|
||||
| L0 | +2,869745 | +2,654569 | +2,258651 |
|
||||
| L1 | +3,032680 | +2,378561 | +1,300716 |
|
||||
| L2 | +5,594944 | +4,091263 | +2,888397 |
|
||||
| L3 | +7,304593 | +6,432281 | +4,996232 |
|
||||
|
||||
На верхних уровнях остаточные полосы выросли, особенно около 50 кГц. На L3 их
|
||||
рост остаётся ниже прироста matched power +11,287650 дБ, а matched/residual
|
||||
улучшился на 7,380069 дБ. Эти цифровые полосы не являются калиброванным
|
||||
измерением RF-гармоник.
|
||||
|
||||
## Итог
|
||||
|
||||
```text
|
||||
CLASSIFICATION = LINEAR_GAIN_IMPROVEMENT
|
||||
```
|
||||
|
||||
Повышение TX VGA с 10 до 20 дБ дало устойчивый прирост согласованной принятой
|
||||
мощности примерно на 11,2 дБ без потери линейности. Остаток вырос существенно
|
||||
меньше, поэтому отношение полезной составляющей к остатку улучшилось на всех
|
||||
четырёх уровнях. Верхняя ступень не показывает компрессии.
|
||||
|
||||
Результат остаётся относительным измерением всего OTA-тракта. Он не определяет
|
||||
абсолютную RF-мощность и не доказывает прохождение полного PHY-пакета, FEC, CRC,
|
||||
T2 или JPEG.
|
||||
|
||||
Независимая проверка пересчитала сохранённый CS8, локализацию, фон, matched,
|
||||
residual, `h`, CFO, внутреннюю линейность и сравнение с Lab064. Расхождений нет.
|
||||
Рекомендуемый следующий одиночный шаг: рассмотреть scope Lab065 и отдельно
|
||||
разрешить локальный коммит. Новую RF-передачу для этого выполнять не требуется.
|
||||
|
||||
`RF TX = 0`. Lab065 не коммитировалась, push не выполнялся, Lab066 не создана.
|
||||
83
docs/lab065_rx_preflight.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Lab065: подготовка и RX-only preflight
|
||||
|
||||
15 сентября 2026 года подготовлена Lab065 с TX VGA 20 дБ. Пользователь
|
||||
подтвердил неизменность расстояния около 1,2 м, прямой видимости, положения TX
|
||||
возле окна и RX на столе, антенн, поляризации, USB, общей опоры и RF-кабелей.
|
||||
|
||||
## Неизменность сигнала
|
||||
|
||||
TX-файл Lab065 является побайтовой копией файла Lab064:
|
||||
|
||||
```text
|
||||
TX samples = 6000000
|
||||
TX bytes = 12000000
|
||||
TX duration = 0.750000 s
|
||||
TX SHA-256 = fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9
|
||||
WAVEFORM UNCHANGED FROM LAB064 = YES
|
||||
```
|
||||
|
||||
Сохранены PRBS7, BPSK, RRC, ступени −18/−12/−6/0 дБ, интервалы и нулевые
|
||||
защитные участки, 20000 символов/с, Fs 8000000 комплексных отсчётов/с и
|
||||
цифровая несущая +50000 Гц. Из аппаратных параметров изменён только TX VGA:
|
||||
10 → 20 дБ. RX LNA/VGA остаются равными 24/24 дБ; RF amplifier и Bias-Tee
|
||||
выключены.
|
||||
|
||||
Software gate прошёл. Он подтвердил SHA, пригодность результата Lab064 как
|
||||
базы сравнения, синтетическую проверку линейности и наличие численных правил
|
||||
классификации, зафиксированных до RF.
|
||||
|
||||
## Common clock
|
||||
|
||||
Перед аппаратными командами проверка процессов через psutil осмотрела 289
|
||||
процессов и получила CLEAR. Обнаружены обе HackRF One r9 с firmware и
|
||||
libhackrf 2024.02.1. Роли совпали с Lab064:
|
||||
|
||||
- MASTER `000000000000000097c063dc323b97d3`;
|
||||
- SLAVE `000000000000000097c063dc2e5327d3`.
|
||||
|
||||
Результат общей опоры:
|
||||
|
||||
```text
|
||||
MASTER reg29 = 0x26
|
||||
SLAVE reg29 = 0x26
|
||||
SLAVE CLKIN = DETECTED
|
||||
CLKIN query = 3
|
||||
time to detect = 201.986 ms
|
||||
```
|
||||
|
||||
Первые два опроса не обнаружили CLKIN, третий опрос прошёл в пределах штатного
|
||||
окна ожидания. Итоговый common-clock gate имеет статус PASS.
|
||||
|
||||
## RX-only фон
|
||||
|
||||
SLAVE записал 800000 комплексных отсчётов при 1400000000 Гц и RX LNA/VGA
|
||||
24/24 дБ.
|
||||
|
||||
| Метрика | Значение |
|
||||
|---|---:|
|
||||
| Полный RMS IQ | 2,333068 кода |
|
||||
| AC RMS после удаления среднего | 1,056565 кода |
|
||||
| Средние I / Q | 0,948896 / 1,851074 кода |
|
||||
| max \|I\| / \|Q\| | 6 / 8 кодов |
|
||||
| Максимальная комплексная амплитуда | 8,062258 кода |
|
||||
| Clipping | 0 / 800000 отсчётов |
|
||||
| RX overruns, максимум / финал | 0 / 0 |
|
||||
| Неожиданно сильный сигнал | нет |
|
||||
|
||||
SHA-256 RX-фона:
|
||||
`be3c61dfa1bc40d1a9f6777689026804a1ccfc340b82680d3ec5304ddd68f78c`.
|
||||
|
||||
Оба CLKOUT выключены без ошибок. Повторная проверка процессов получила CLEAR.
|
||||
Команд передачи не было.
|
||||
|
||||
```text
|
||||
LAB065 PREPARATION = PASS
|
||||
COMMON CLOCK = PASS
|
||||
RX PREFLIGHT = PASS
|
||||
RF TX = 0
|
||||
READY FOR SINGLE LAB065 TX = YES
|
||||
WAITING FOR EXPLICIT RF AUTHORIZATION = YES
|
||||
```
|
||||
|
||||
Готовность означает, что программные и аппаратные входные проверки пройдены.
|
||||
Она не разрешает передачу и не предсказывает результат измерения.
|
||||
92
docs/lab066_current_state.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Lab066 — исправленный повтор полного 500K PHY-сеанса
|
||||
|
||||
## Цель и границы
|
||||
|
||||
Lab066 повторяет исходную цель Lab062 после двух установленных дефектов того
|
||||
сеанса: пакеты Lab062 шли вплотную, без заявленных пауз 5 мс, а в TX-журнале
|
||||
был промежуточный underrun. Исторический результат Lab062 сохраняется без
|
||||
изменений: `RF_SESSION_INVALID_TX_UNDERRUN`, legacy 0/16, T2 0/16.
|
||||
|
||||
На этапе подготовки Lab066 была ограничена программным декодированием,
|
||||
common-clock и RX-only preflight; передача тогда не выполнялась. После этого
|
||||
пользователь отдельно разрешил один TX. Автоматический повтор оставался
|
||||
запрещён и не выполнялся.
|
||||
|
||||
## Радиопараметры
|
||||
|
||||
- RF: 1400000000 Гц;
|
||||
- Fs: 8000000 комплексных отсчётов/с;
|
||||
- символьная скорость: 500000 символов/с, 16 отсчётов/символ;
|
||||
- цифровая несущая: +50000 Гц;
|
||||
- TX VGA: 20 дБ;
|
||||
- RX LNA/VGA: 24/24 дБ;
|
||||
- RF amplifier и Bias-Tee: OFF;
|
||||
- 16 штатных PHY-пакетов с PRBS7-преамбулой, заголовком, FEC и CRC;
|
||||
- payload: 256 байт, детерминированный и различимый для каждого пакета;
|
||||
- геометрия: около 1,2 м, LOS в помещении, прежние антенны и поляризация;
|
||||
MASTER TX возле окна, SLAVE RX на столе.
|
||||
|
||||
## Итоговый TX-файл
|
||||
|
||||
Каждый packet waveform занимает 92256 отсчётов. Между соседними пакетами
|
||||
записаны 15 нулевых пауз по 40000 отсчётов, то есть ровно по 5 мс. Перед первым
|
||||
и после последнего пакета добавлены нулевые guards по 800000 отсчётов, или по
|
||||
100 мс.
|
||||
|
||||
Расчёт длины:
|
||||
|
||||
```text
|
||||
16 × 92256 + 15 × 40000 = 2076096 отсчётов без внешних guards
|
||||
2076096 + 2 × 800000 = 3676096 отсчётов всего
|
||||
3676096 / 8000000 = 0,459512 с
|
||||
```
|
||||
|
||||
Фактическая длина совпала с расчётом: 3676096 комплексных отсчётов,
|
||||
7352192 байта. SHA-256:
|
||||
`621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199`.
|
||||
Повторная сборка дала те же байты. Нулевые guards и каждая из 15 пауз проверены
|
||||
по точным границам из сохранённой разметки.
|
||||
|
||||
## Программное декодирование
|
||||
|
||||
Весь конечный TX-файл дважды прошёл один и тот же строгий приёмный тракт. Режимы
|
||||
legacy и T2 запускались отдельно, автоматического fallback между ними нет.
|
||||
|
||||
| Режим | Кандидаты | Header | FEC | CRC | Выход | Payload и порядок |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| legacy | 16 | 16/16 | 16/16 | 16/16 | 16/16 | 16/16 |
|
||||
| T2 | 16 | 16/16 | 16/16 | 16/16 | 16/16 | 16/16 |
|
||||
|
||||
Позиции принятых пакетов равны ожидаемым позициям в разметке. Для каждого
|
||||
кандидата T2 заново оценивает начальные параметры по собственной преамбуле.
|
||||
Наблюдение дало по два локальных запуска слежения на пакет: для заголовка и
|
||||
полного кадра. Контекст обработки создаётся заново, состояния между пакетами
|
||||
не переносятся.
|
||||
|
||||
Аппаратный анализ применил legacy и T2 к одному сохранённому RX-файлу. До
|
||||
строгого вердикта декодеру не передавались payload или ожидаемые биты. Известный
|
||||
TX использован после вердикта только для физических диагностик и сравнения с
|
||||
Lab056 B, Lab062 и Lab066.
|
||||
|
||||
## Проверки перед preflight
|
||||
|
||||
- `python tools/quick_gate.py`: PASS, 1013 проверок ядра, 39 функциональных,
|
||||
13 аппаратных пропусков;
|
||||
- `python -m pytest tests/ -q`: 1013 passed;
|
||||
- `git diff --check`: PASS.
|
||||
|
||||
Независимый verifier пересчитал длину, SHA, все нулевые диапазоны, пакетные
|
||||
срезы, программное декодирование и RX-only метрики; расхождений нет. Read-only
|
||||
adversary проверил запрет TX, одноразовость, привязку capture к конфигурации,
|
||||
счётчики, live-проверку фона и границу oracle-диагностики; открытых замечаний
|
||||
нет.
|
||||
|
||||
## Текущее состояние
|
||||
|
||||
Common-clock и RX-only preflight прошли; измерения приведены в
|
||||
[отдельном отчёте](lab066_rx_preflight.md). `RF TX=0`. Lab066 не
|
||||
коммитировалась, push не выполнялся, Lab067 не создавалась.
|
||||
|
||||
После отдельного разрешения выполнен один аппаратно валидный RF-сеанс без
|
||||
повторов. Legacy и T2 дали одинаковые 10/16 точных пакетов. Полные результаты
|
||||
и физическое сравнение приведены в [RF-отчёте](lab066_rf_result.md).
|
||||
118
docs/lab066_rf_result.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Lab066 — один OTA RF-сеанс полного 500K PHY-потока
|
||||
|
||||
## Аппаратная валидность
|
||||
|
||||
15 сентября 2026 года выполнен один разрешённый TX всего подготовленного
|
||||
потока. Передача запускалась один раз; автоматических повторов не было. RX
|
||||
стартовал раньше TX и сохранил 16000000 комплексных отсчётов за 2,000000 с.
|
||||
|
||||
- TX SHA-256:
|
||||
`621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199`;
|
||||
- RX SHA-256:
|
||||
`72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0`;
|
||||
- TX underrun observations: 0, 0; maximum 0;
|
||||
- RX overrun observations: 0, 0, 0, 0; maximum 0;
|
||||
- clipping: нет;
|
||||
- cleanup errors: нет;
|
||||
- SDR-процессы до и после: `CLEAR`.
|
||||
|
||||
Непосредственно перед TX обе платы имели reg29=`0x26`; SLAVE CLKIN обнаружен
|
||||
на втором опросе через 131,291 мс. Live RX-проверка дала AC RMS 1,173567 кода,
|
||||
max |I|/|Q|=4/5 и не обнаружила clipping или сильный фон. После сеанса оба
|
||||
CLKOUT отключены.
|
||||
|
||||
## Строгое декодирование одной RX-записи
|
||||
|
||||
Legacy и T2 получили одну и ту же непрерывную IQ-запись. Поиск кандидатов,
|
||||
временная синхронизация, RRC, пороги, PHY header, FEC, CRC и критерии принятия
|
||||
не менялись. Автоматического fallback между режимами нет.
|
||||
|
||||
| Метрика | legacy | T2 |
|
||||
|---|---:|---:|
|
||||
| Кандидаты | 145 | 145 |
|
||||
| Header PASS | 10 | 10 |
|
||||
| FEC started | 10 | 10 |
|
||||
| FEC PASS | 10 | 10 |
|
||||
| CRC started | 10 | 10 |
|
||||
| CRC PASS | 10 | 10 |
|
||||
| Packet output | 10 | 10 |
|
||||
| Exact payload match | 10 | 10 |
|
||||
|
||||
Оба режима приняли пакеты 4, 5, 6, 7, 8, 10, 11, 12, 13 и 14. Пакеты 1, 2,
|
||||
3, 9, 15 и 16 не прошли PHY header у обоих режимов. Выигрыша одного режима
|
||||
над другим нет.
|
||||
|
||||
| Пакет | Legacy H/F/C/O | T2 H/F/C/O |
|
||||
|---:|:---:|:---:|
|
||||
| 1 | 0/0/0/0 | 0/0/0/0 |
|
||||
| 2 | 0/0/0/0 | 0/0/0/0 |
|
||||
| 3 | 0/0/0/0 | 0/0/0/0 |
|
||||
| 4 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 5 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 6 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 7 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 8 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 9 | 0/0/0/0 | 0/0/0/0 |
|
||||
| 10 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 11 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 12 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 13 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 14 | 1/1/1/1 | 1/1/1/1 |
|
||||
| 15 | 0/0/0/0 | 0/0/0/0 |
|
||||
| 16 | 0/0/0/0 | 0/0/0/0 |
|
||||
|
||||
H/F/C/O означает header/FEC/CRC/output. Для каждого из 16 ожидаемых пакетов
|
||||
детектор имел кандидат точно на диагностической границе пакета. У шести
|
||||
непринятых пакетов FEC и CRC не запускались из-за отказа PHY header.
|
||||
|
||||
## Физическая диагностика после строгого результата
|
||||
|
||||
Сопоставимые показатели рассчитаны отдельно по всем 16 пакетам методикой
|
||||
Lab058. Известный TX использован после завершения строгих прогонов и не влиял
|
||||
на принятие пакетов.
|
||||
|
||||
| Показатель | Lab066 |
|
||||
|---|---:|
|
||||
| Доля ошибочных символов | 0,0110466, или 1,104664% |
|
||||
| Нормированное RMS-отклонение символов | 0,528374 |
|
||||
| Согласованная мощность | 8,359398 код² |
|
||||
| Остаточная мощность | 2,333261 код² |
|
||||
| Matched/residual | +5,542117 дБ |
|
||||
| Неравномерность усиления p95–p05 | медиана 2,870070 дБ |
|
||||
| Диапазон неравномерности по пакетам | 2,724906…3,111679 дБ |
|
||||
|
||||
Остаток включает шум и немоделированные искажения; это не калиброванная
|
||||
мощность шума. Физические показатели являются диагностическими и не превращают
|
||||
непринятый пакет в успешный.
|
||||
|
||||
## Сравнение
|
||||
|
||||
| Показатель | Lab056 B | Lab062 | Lab066 |
|
||||
|---|---:|---:|---:|
|
||||
| Доля ошибочных символов | 0,034959 | 0,099287 | 0,011047 |
|
||||
| RMS-отклонение | 0,845350 | 1,220401 | 0,528374 |
|
||||
| Matched/residual | +1,464846 дБ | −1,730051 дБ | +5,542117 дБ |
|
||||
| Неравномерность полосы | 5,471382 дБ | 6,660341 дБ | 2,870070 дБ |
|
||||
|
||||
Относительно Lab056 B доля ошибочных символов ниже на 0,0239123, или на
|
||||
68,40%; RMS-отклонение ниже на 0,316976, matched/residual выше на 4,077271 дБ,
|
||||
а неравномерность меньше на 2,601312 дБ.
|
||||
|
||||
Относительно Lab062 доля ошибочных символов ниже на 0,0882400, или на 88,87%;
|
||||
RMS-отклонение ниже на 0,692027, matched/residual выше на 7,272168 дБ, а
|
||||
неравномерность меньше на 3,790271 дБ. Сравнение с Lab062 контекстное: тот
|
||||
сеанс остаётся аппаратно невалидным из-за TX underrun.
|
||||
|
||||
## Итог
|
||||
|
||||
Классификация: `PARTIAL_PACKET_RECOVERY_NO_T2_GAIN`. Аппаратно чистая запись
|
||||
дала одинаковые 10/16 точных пакетов в legacy и T2. Физическое качество заметно
|
||||
лучше Lab056 B и невалидной Lab062, однако шесть пакетов остановились на PHY
|
||||
header, поэтому результат не является полным 16/16 успехом.
|
||||
|
||||
Следующий отдельный шаг, если он будет разрешён: только офлайн сравнить ошибки
|
||||
PHY header, timing и CFO пакетов 1–3, 9, 15–16 с принятыми пакетами по уже
|
||||
сохранённому RX. Новый TX для этого не нужен.
|
||||
|
||||
Сейчас `RF TX=0`, процессы `CLEAR`. Lab066 не коммитировалась, push не
|
||||
выполнялся, Lab067 не создавалась.
|
||||
35
docs/lab066_rx_preflight.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Lab066 — common-clock и RX-only preflight
|
||||
|
||||
## Результат
|
||||
|
||||
15 сентября 2026 года выполнен один preflight без передачи. До и после него
|
||||
SDR-процессы имели состояние `CLEAR`; обе HackRF One r9 и назначение ролей
|
||||
подтверждены.
|
||||
|
||||
Общая опора прошла проверку:
|
||||
|
||||
- MASTER reg29 = `0x26`;
|
||||
- SLAVE reg29 = `0x26`;
|
||||
- SLAVE CLKIN = `DETECTED`;
|
||||
- обнаружение произошло на втором опросе через 132,113 мс.
|
||||
|
||||
RX-only команда записала 800000 комплексных отсчётов на частоте 1400 МГц с
|
||||
RX LNA/VGA 24/24 дБ. Максимум всех наблюдавшихся счётчиков overrun равен нулю.
|
||||
После проверки оба CLKOUT отключены; ошибок очистки нет.
|
||||
|
||||
## Уровень фона
|
||||
|
||||
- полный RMS: 2,225641 кода;
|
||||
- AC RMS: 0,827637 кода;
|
||||
- средние I/Q: 0,947530 / 1,835941 кода;
|
||||
- max |I| / |Q|: 4 / 5;
|
||||
- доля clipped components и samples: 0 / 0;
|
||||
- неожиданный сильный сигнал: нет;
|
||||
- SHA-256 RX-only записи:
|
||||
`ff817582e6a8fc03b489bf89f46322a9db8a9493335a4360a1b0f9bedad7c4d5`.
|
||||
|
||||
## Граница разрешения
|
||||
|
||||
Preflight не запускал TX и не расходует право на будущий единственный сеанс.
|
||||
Сейчас `RF TX=0`. Для одного Lab066 TX требуется отдельное прямое разрешение.
|
||||
Автоматические повторы, изменение усилений и переход к Lab067 запрещены.
|
||||
49
docs/lab067_current_state.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Lab067: текущее состояние
|
||||
|
||||
Дата: 15 сентября 2026 года.
|
||||
|
||||
## Граница работы
|
||||
|
||||
Lab067 выполнена полностью офлайн по сохранённой аппаратно валидной записи
|
||||
Lab066. HackRF Plus не опрашивался, аппаратные команды не запускались, RF TX
|
||||
оставался равным 0. Алгоритмы приёмника, PHY-заголовок, пороги, FEC, CRC и
|
||||
критерии принятия пакета не менялись.
|
||||
|
||||
Исторический строгий результат Lab066 сохранён:
|
||||
|
||||
- legacy: 10/16 точных пакетов;
|
||||
- T2: 10/16 точных пакетов;
|
||||
- приняты пакеты 4–8 и 10–14;
|
||||
- пакеты 1–3, 9, 15 и 16 остановились на PHY-заголовке.
|
||||
|
||||
## Проверенные исходники
|
||||
|
||||
- TX: 7 352 192 байта, 3 676 096 комплексных отсчётов, SHA-256
|
||||
`621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199`;
|
||||
- RX: 32 000 000 байт, 16 000 000 комплексных отсчётов, 2,0 с при
|
||||
8 Мвыб/с, SHA-256
|
||||
`72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0`;
|
||||
- на расчётной позиции каждого из 16 пакетов найден строгий кандидат с
|
||||
нулевым смещением уточнённой позиции.
|
||||
|
||||
## Текущий вывод
|
||||
|
||||
Все шесть отказов локализованы в PHY-заголовке: преамбулы этих пакетов лежат
|
||||
внутри диапазона принятых пакетов, но в коротком окне от последних 128 символов
|
||||
преамбулы до конца заголовка растёт остаток. Медиана `matched/residual` снижается
|
||||
с 5,906881 dB у принятых пакетов до 4,146180 dB у отказавших.
|
||||
|
||||
28 сырых ошибок заголовка занимают разные позиции; ни одна позиция не
|
||||
повторяется между отказавшими пакетами. TX-заголовки всех 16 пакетов идентичны,
|
||||
корректны и дают текущий программный круговой тест 16/16 в legacy и T2.
|
||||
Систематическая ошибка обработки заголовка не найдена.
|
||||
|
||||
`HEADER_FAILURE_ROOT_CAUSE = LOCAL_RF_QUALITY`
|
||||
|
||||
`ONE SMALL FIX JUSTIFIED = NO`
|
||||
|
||||
Рекомендация: закрыть текущую диагностическую ветку на достигнутом результате
|
||||
10/16 и перейти к ветке управления и телеметрии CRSF/ELRS. QPSK с меньшей
|
||||
символьной скоростью можно рассматривать позднее для видеоканала.
|
||||
|
||||
Lab067 не коммитировалась. Push не выполнялся.
|
||||
151
docs/lab067_offline_result.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Lab067: офлайн-анализ отказов PHY-заголовка Lab066
|
||||
|
||||
## Результат
|
||||
|
||||
Причина шести отказов совместима с кратковременным ростом несогласованного
|
||||
остатка именно в области заголовка. Данных за небольшую ошибку логики приёмника,
|
||||
временной привязки, сопровождения несущей или состояния кандидатов нет.
|
||||
|
||||
`HEADER_FAILURE_ROOT_CAUSE = LOCAL_RF_QUALITY`
|
||||
|
||||
`ONE SMALL FIX JUSTIFIED = NO`
|
||||
|
||||
`BRANCH DECISION = NO_SMALL_FIX_JUSTIFIED`
|
||||
|
||||
Исторический результат Lab066 остаётся 10/16 для legacy и 10/16 для T2.
|
||||
Oracle-расчёты ниже диагностические и не меняют строгий результат.
|
||||
|
||||
## Целостность и геометрия данных
|
||||
|
||||
SHA-256 TX и RX совпали с зафиксированными значениями Lab066. RX имеет
|
||||
16 000 000 комплексных отсчётов, 32 000 000 байт и длительность 2,0 с при
|
||||
8 Мвыб/с. Все 16 ожидаемых позиций подтверждены строгими кандидатами;
|
||||
уточнённое смещение для каждого пакета равно 0 отсчётов.
|
||||
|
||||
## Сводная таблица
|
||||
|
||||
`P err` означает ошибки 1280-символьной преамбулы. `H raw` означает ошибки
|
||||
среди 144 переданных символов заголовка. `H vote` означает ошибки после штатной
|
||||
мягкой суммы трёх повторений. Локальная метрика рассчитана по фиксированному
|
||||
окну: последние 128 символов преамбулы и весь заголовок.
|
||||
|
||||
| Пакет | Строгий результат | P err | P corr | H raw | Копии 1/2/3 | H vote | Поля с ошибкой | CFO legacy, Hz | CFO T2/P3, Hz | Timing, отсч. | Локальный M/R, dB | RMS | Legacy/T2 diff | Классификация |
|
||||
|---:|---|---:|---:|---:|---:|---:|---|---:|---:|---:|---:|---:|---:|---|
|
||||
| 1 | HEADER FAIL | 15 | 0,898114 | 3 | 1/2/0 | 1 | length | -2,662 | -5,388 | 0 | 5,127352 | 0,554156 | 0 | HEADER_SPECIFIC_FAILURE |
|
||||
| 2 | HEADER FAIL | 10 | 0,892575 | 6 | 3/2/1 | 1 | length | -11,085 | -5,424 | 0 | 3,453566 | 0,671926 | 1 | HEADER_SPECIFIC_FAILURE |
|
||||
| 3 | HEADER FAIL | 11 | 0,883245 | 3 | 1/1/1 | 1 | magic | -2,144 | 2,717 | 0 | 4,588577 | 0,589619 | 0 | HEADER_SPECIFIC_FAILURE |
|
||||
| 4 | PASS | 12 | 0,870256 | 0 | 0/0/0 | 0 | none | -4,970 | 0,501 | 0 | 4,498689 | 0,595752 | 0 | PASSED |
|
||||
| 5 | PASS | 15 | 0,882299 | 0 | 0/0/0 | 0 | none | 3,163 | -1,271 | 0 | 8,091353 | 0,393942 | 0 | PASSED |
|
||||
| 6 | PASS | 17 | 0,883426 | 0 | 0/0/0 | 0 | none | 5,595 | 1,262 | 0 | 7,317955 | 0,430628 | 0 | PASSED |
|
||||
| 7 | PASS | 7 | 0,895704 | 1 | 0/0/1 | 0 | none | -1,265 | -1,236 | 0 | 6,319905 | 0,483064 | 1 | PASSED |
|
||||
| 8 | PASS | 10 | 0,893515 | 3 | 1/2/0 | 0 | none | 9,131 | -0,471 | 0 | 4,986495 | 0,563216 | 0 | PASSED |
|
||||
| 9 | HEADER FAIL | 13 | 0,883101 | 4 | 1/2/1 | 1 | length | 6,363 | -1,982 | 0 | 3,775053 | 0,647511 | 0 | HEADER_SPECIFIC_FAILURE |
|
||||
| 10 | PASS | 14 | 0,875698 | 1 | 0/0/1 | 0 | none | 10,521 | 4,784 | 0 | 4,964081 | 0,564672 | 0 | PASSED |
|
||||
| 11 | PASS | 14 | 0,873945 | 0 | 0/0/0 | 0 | none | -4,338 | 5,576 | 0 | 5,008753 | 0,561775 | 0 | PASSED |
|
||||
| 12 | PASS | 15 | 0,869109 | 0 | 0/0/0 | 0 | none | -14,662 | -12,429 | 0 | 6,568840 | 0,469416 | 0 | PASSED |
|
||||
| 13 | PASS | 19 | 0,887088 | 0 | 0/0/0 | 0 | none | 2,006 | -3,871 | 0 | 7,449922 | 0,424135 | 0 | PASSED |
|
||||
| 14 | PASS | 13 | 0,897960 | 1 | 0/0/1 | 0 | none | 7,211 | 3,326 | 0 | 5,493857 | 0,531260 | 1 | PASSED |
|
||||
| 15 | HEADER FAIL | 12 | 0,888738 | 7 | 2/2/3 | 2 | magic, length | -5,732 | -3,520 | 0 | 4,055203 | 0,626960 | 0 | HEADER_SPECIFIC_FAILURE |
|
||||
| 16 | HEADER FAIL | 16 | 0,880510 | 5 | 1/2/2 | 2 | version, length | 4,734 | 228,105 | 0 | 4,237157 | 0,613963 | 107 | HEADER_SPECIFIC_FAILURE |
|
||||
|
||||
Полные строки битов `EXPECTED HEADER BITS`, `RECEIVED RAW HEADER BITS`,
|
||||
`VOTED HEADER BITS`, причины строгого отказа и позиции ошибок находятся в
|
||||
`summary.json` и `header_packet_comparison.csv`.
|
||||
|
||||
## Преамбула: распределения
|
||||
|
||||
| Метрика | Passed min / median / max | Failed min / median / max |
|
||||
|---|---:|---:|
|
||||
| Ошибки, бит | 7 / 14 / 19 | 10 / 12,5 / 16 |
|
||||
| Доля ошибок | 0,005469 / 0,010938 / 0,014844 | 0,007813 / 0,009766 / 0,012500 |
|
||||
| Корреляция | 0,869109 / 0,882862 / 0,897960 | 0,880510 / 0,885991 / 0,898114 |
|
||||
| RMS отклонение | 0,489327 / 0,532530 / 0,567221 | 0,489090 / 0,523373 / 0,538197 |
|
||||
| Matched power | 7,774573 / 8,330450 / 8,667865 | 7,968245 / 8,385475 / 8,982336 |
|
||||
| Residual power | 2,053382 / 2,346123 / 2,645143 | 2,118219 / 2,258980 / 2,379173 |
|
||||
| Matched/residual, dB | 4,924949 / 5,473142 / 6,208025 | 5,381167 / 5,624735 / 6,212224 |
|
||||
|
||||
Преамбула не отделяет отказавшие пакеты от принятых: медианные физические
|
||||
показатели failed-группы не хуже. Ухудшение появляется после последних
|
||||
128 символов преамбулы и внутри заголовка. В этом окне медиана residual power
|
||||
равна 3,089822 у failed и 2,219458 у passed; медиана `matched/residual` равна
|
||||
4,146180 и 5,906881 dB соответственно.
|
||||
|
||||
## Карта ошибок заголовка
|
||||
|
||||
У отказавших пакетов найдено 28 сырых ошибочных решений. Ни одна позиция
|
||||
не повторяется в двух failed-пакетах. Ошибки встречаются во всех трёх копиях:
|
||||
9, 11 и 8 ошибок соответственно. После мягкой суммы остаётся 1 ошибка у
|
||||
пакетов 1, 2, 3 и 9 и 2 ошибки у пакетов 15 и 16.
|
||||
|
||||
Общей позиции, стабильно плохой копии, концентрации только в начале или только
|
||||
в конце заголовка нет. Это не поддерживает гипотезу о систематической ошибке
|
||||
формата или индексации заголовка.
|
||||
|
||||
## Переход преамбула → заголовок
|
||||
|
||||
На последних 128 символах преамбулы медиана фазовой RMS-ошибки составляет
|
||||
0,361 rad у passed и 0,541 rad у failed. На заголовке значения составляют
|
||||
0,388 и 0,627 rad. У failed-пакетов ошибки заголовка лежат в диапазоне 3–7
|
||||
сырых решений, а в первых 128 символах после заголовка медиана ошибок равна 0.
|
||||
|
||||
Медианная амплитуда остаётся близкой к единице. Общего скачка амплитуды,
|
||||
смещения точки выборки или одинакового фазового скачка не найдено. Наблюдается
|
||||
короткое ухудшение фазового остатка, различное по позиции в каждом пакете.
|
||||
|
||||
## Timing, CFO и T2
|
||||
|
||||
Штатное timing-смещение и диагностическое oracle-смещение равны 0 отсчётов для
|
||||
всех 16 пакетов. Расхождение legacy CFO с контрольной оценкой создаёт за весь
|
||||
144-символьный заголовок фазовую ошибку от -0,02488 до +0,02079 rad. Этот
|
||||
масштаб недостаточен для объяснения 1–2 ошибок после мягкого голосования.
|
||||
|
||||
T2 начинает влиять с символа 1280, то есть с начала заголовка. Для пакетов
|
||||
1, 3, 4–6, 8–13 и 15 его жёсткие решения заголовка побитно совпадают с legacy.
|
||||
Различия равны одному биту для пакетов 2, 7 и 14. У пакета 16 оценка P3 стала
|
||||
выбросом 228,105 Hz и изменила 107 решений. Это ухудшение одного пакета не
|
||||
объясняет шесть legacy-отказов и не даёт основания менять T2 в этой ветке.
|
||||
|
||||
## Паузы и положение в сеансе
|
||||
|
||||
Для пакета 1 исследован внешний guard длиной 800 000 отсчётов. Для пакетов
|
||||
2–16 исследованы реальные паузы по 40 000 отсчётов. AC RMS перед пакетами
|
||||
лежит в диапазоне 0,744879–1,302893. Признаков хвоста предыдущей формы по
|
||||
критерию превышения первых 8000 отсчётов над серединой паузы на 3 dB нет.
|
||||
После пакета 16 отдельно проверен конечный guard.
|
||||
|
||||
Группы START и END имеют больше ошибок заголовка, чем MIDDLE 1, однако пакет 9
|
||||
отказывает в середине, пакеты 4, 13 и 14 проходят, а timing, CFO и фон не дают
|
||||
монотонного временного тренда. Расположение пакетов не доказывает
|
||||
`START_END_TRANSIENT`.
|
||||
|
||||
## 145 кандидатов
|
||||
|
||||
Строгий журнал содержит 145 проверок:
|
||||
|
||||
- 27 проверок на точных позициях преамбулы, 16 уникальных позиций и 11
|
||||
повторных проверок той же позиции;
|
||||
- 54 локальных максимума повторяющейся PRBS-структуры внутри формы пакета;
|
||||
- 56 кандидатов в нулевых паузах;
|
||||
- 8 кандидатов вне этих областей.
|
||||
|
||||
Каждый кандидат получает новый `PacketMeasure` и полностью независимый строгий
|
||||
проход. Состояние после отказа не переносится. Граница `accepted_until`
|
||||
изменяется только после CRC PASS. Большое число кандидатов увеличивает объём
|
||||
работы, но не объясняет выборочные ошибки настоящих заголовков.
|
||||
|
||||
## TX и решение по ветке
|
||||
|
||||
Все 16 TX-заголовков побитно идентичны. Magic `0xD391`, версия 1, длина
|
||||
268 байт, checksum и тройное повторение корректны. Текущий программный прогон
|
||||
дал 16/16 точных пакетов и для legacy, и для T2.
|
||||
|
||||
Конкретная небольшая ошибка приёмника, которая объяснила бы значимую часть
|
||||
шести отказов, не найдена. Зафиксирован достигнутый результат: 10/16 точных
|
||||
полных пакетов при 500 ксимв/с и TX VGA 20 dB в аппаратно валидной OTA-записи.
|
||||
Текущую диагностическую ветку следует закрыть. Следующая проектная ветка:
|
||||
CRSF/ELRS для управления и телеметрии.
|
||||
|
||||
QPSK с меньшей скоростью символов остаётся будущим кандидатом для видеоканала.
|
||||
В Lab067 QPSK, новый PHY и изменения BPSK не выполнялись.
|
||||
|
||||
RF TX = 0. Lab067 не коммитировалась. Push не выполнялся.
|
||||
623
experiments/lab065_rf_level_linearity.py
Normal file
@@ -0,0 +1,623 @@
|
||||
"""Lab065: та же лестница уровней при TX VGA 20 дБ.
|
||||
|
||||
Подготовка, программная проверка и preflight не передают RF. Единственный
|
||||
аппаратный сеанс доступен только отдельной командой с явным разрешением.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import numpy as np
|
||||
|
||||
from experiments import lab054_hardware_control as lab054
|
||||
from experiments import lab056_capture_runner as hardware
|
||||
from experiments import lab064_rf_level_linearity as lab064
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RAW = ROOT / "data/raw/lab065"
|
||||
OUTPUT = ROOT / "data/processed/lab065"
|
||||
LAB064_RAW = ROOT / "data/raw/lab064"
|
||||
LAB064_RESULT = ROOT / "data/processed/lab064/rf_result.json"
|
||||
EXPECTED_TX_SHA256 = "fa260719659b2984fe29588238415622cd75c6daefec6cc9130ba1bf851bd3f9"
|
||||
TX_VGA_DB = 20
|
||||
TX_VGA_DIFFERENCE_DB = 10.0
|
||||
MEANINGFUL_GAIN_DB = 3.0
|
||||
MEANINGFUL_RATIO_IMPROVEMENT_DB = 3.0
|
||||
MAX_LEVEL_TO_LEVEL_GAIN_SPREAD_DB = 3.0
|
||||
RESIDUAL_TRACKING_MARGIN_DB = 3.0
|
||||
|
||||
POINT = hardware.OperatingPoint(**asdict(lab064.POINT))
|
||||
|
||||
|
||||
def save_new(path: Path, value: dict) -> None:
|
||||
"""Записать новое свидетельство и не перезаписывать результат опыта."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
stream.write("\n")
|
||||
|
||||
|
||||
def save_text_new(path: Path, value: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
stream.write(value)
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while block := stream.read(1024 * 1024):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def parameters() -> dict:
|
||||
result = lab064.parameters()
|
||||
result["tx_vga_db"] = TX_VGA_DB
|
||||
result["geometry_unchanged_from_lab064"] = True
|
||||
result["waveform_source"] = "Lab064 exact CS8 copy"
|
||||
return result
|
||||
|
||||
|
||||
def transfer_command(path: Path | str, samples: int, *, transmit: bool = False) -> list[str]:
|
||||
command = lab064.transfer_command(path, samples, transmit=transmit)
|
||||
if transmit:
|
||||
command[command.index("-x") + 1] = str(TX_VGA_DB)
|
||||
return command
|
||||
|
||||
|
||||
def validate_lab064_waveform() -> dict:
|
||||
source = LAB064_RAW / "tx_level_staircase.cs8"
|
||||
metadata = json.loads((LAB064_RAW / "tx_metadata.json").read_text(encoding="utf-8"))
|
||||
digest = sha256(source)
|
||||
if (
|
||||
digest != EXPECTED_TX_SHA256
|
||||
or metadata.get("tx_sha256") != EXPECTED_TX_SHA256
|
||||
or source.stat().st_size != 2 * lab064.TX_SAMPLES
|
||||
or metadata.get("segments") != lab064.prepared_segments()[0]
|
||||
):
|
||||
raise RuntimeError("Waveform Lab064 не совпадает с зафиксированным образцом")
|
||||
return metadata
|
||||
|
||||
|
||||
def validate_prepared() -> dict:
|
||||
metadata = json.loads((RAW / "tx_metadata.json").read_text(encoding="utf-8"))
|
||||
path = RAW / "tx_level_staircase.cs8"
|
||||
if (
|
||||
metadata.get("lab") != "Lab065"
|
||||
or metadata.get("stage") != "PREPARATION_ONLY"
|
||||
or metadata.get("rf_tx_count") != 0
|
||||
or metadata.get("parameters") != parameters()
|
||||
or metadata.get("tx_sha256") != EXPECTED_TX_SHA256
|
||||
or metadata.get("source_tx_sha256") != EXPECTED_TX_SHA256
|
||||
or path.stat().st_size != 2 * lab064.TX_SAMPLES
|
||||
or sha256(path) != EXPECTED_TX_SHA256
|
||||
):
|
||||
raise RuntimeError("Подготовленный Lab065 TX или параметры изменились")
|
||||
return metadata
|
||||
|
||||
|
||||
def prepare() -> dict:
|
||||
"""Скопировать точные байты Lab064 в отдельный каталог Lab065."""
|
||||
source_metadata = validate_lab064_waveform()
|
||||
RAW.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
target = RAW / "tx_level_staircase.cs8"
|
||||
if target.exists():
|
||||
raise FileExistsError("TX-файл Lab065 уже существует")
|
||||
shutil.copyfile(LAB064_RAW / "tx_level_staircase.cs8", target)
|
||||
if sha256(target) != EXPECTED_TX_SHA256:
|
||||
target.unlink()
|
||||
raise RuntimeError("Копия waveform Lab065 не прошла SHA-256")
|
||||
result = {
|
||||
"lab": "Lab065",
|
||||
"stage": "PREPARATION_ONLY",
|
||||
"rf_tx_count": 0,
|
||||
"head": subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip(),
|
||||
"parameters": parameters(),
|
||||
"segments": source_metadata["segments"],
|
||||
"tx_samples": lab064.TX_SAMPLES,
|
||||
"tx_bytes": 2 * lab064.TX_SAMPLES,
|
||||
"tx_duration_seconds": lab064.TX_SAMPLES / lab064.SAMPLE_RATE,
|
||||
"tx_sha256": sha256(target),
|
||||
"source_tx_sha256": source_metadata["tx_sha256"],
|
||||
"waveform_unchanged_from_lab064": True,
|
||||
"automatic_retry": False,
|
||||
"tx_command_prepared_not_run": transfer_command(target, lab064.TX_SAMPLES, transmit=True),
|
||||
"rx_command_prepared_not_run": transfer_command(RAW / "rx_stream.cs8", lab064.RX_SAMPLES),
|
||||
}
|
||||
save_new(RAW / "tx_metadata.json", result)
|
||||
save_new(OUTPUT / "preparation.json", result)
|
||||
return result
|
||||
|
||||
|
||||
def compare_sessions(old_result: dict, new_rows: list[dict]) -> dict:
|
||||
"""Сопоставить одинаковые цифровые уровни Lab064 и Lab065."""
|
||||
old_by_label = {row["label"]: row for row in old_result["rows"]}
|
||||
rows = []
|
||||
for new in new_rows:
|
||||
old = old_by_label[new["label"]]
|
||||
comparison = {
|
||||
"label": new["label"],
|
||||
"tx_vga_nominal_difference_db": TX_VGA_DIFFERENCE_DB,
|
||||
"rx_matched_power_difference_db": lab064.difference_db(
|
||||
new["matched_signal_power"], old["matched_signal_power"]
|
||||
),
|
||||
"gain_power_abs_h_squared_difference_db": lab064.difference_db(
|
||||
new["gain_power_abs_h_squared"], old["gain_power_abs_h_squared"]
|
||||
),
|
||||
"residual_power_difference_db": lab064.difference_db(
|
||||
new["residual_power"], old["residual_power"]
|
||||
),
|
||||
"matched_to_residual_improvement_db": (
|
||||
new["matched_to_residual_db"] - old["matched_to_residual_db"]
|
||||
),
|
||||
}
|
||||
if "complex_gain_h" in new and "residual_spectrum" in new:
|
||||
old_gain = complex(
|
||||
old["complex_gain_h"]["real"], old["complex_gain_h"]["imag"]
|
||||
)
|
||||
new_gain = complex(
|
||||
new["complex_gain_h"]["real"], new["complex_gain_h"]["imag"]
|
||||
)
|
||||
comparison["complex_gain_h_old"] = old["complex_gain_h"]
|
||||
comparison["complex_gain_h_new"] = new["complex_gain_h"]
|
||||
comparison["complex_gain_phase_change_degrees"] = float(
|
||||
np.degrees(np.angle(new_gain / old_gain))
|
||||
)
|
||||
comparison["residual_spectral_band_difference_db"] = {
|
||||
f"h{harmonic}_{harmonic * lab064.CARRIER_HZ}_hz": lab064.difference_db(
|
||||
new["residual_spectrum"][f"residual_band_h{harmonic}_power_code2"],
|
||||
old["residual_spectrum"][f"residual_band_h{harmonic}_power_code2"],
|
||||
)
|
||||
for harmonic in (1, 2, 3)
|
||||
}
|
||||
rows.append(comparison)
|
||||
return {"rows": rows, "rules_fixed_before_rf": classification_rules()}
|
||||
|
||||
|
||||
def classification_rules() -> dict:
|
||||
return {
|
||||
"internal_linear_max_abs_error_db": 1.0,
|
||||
"compression_upper_step_error_at_most_db": -1.5,
|
||||
"meaningful_matched_gain_median_at_least_db": MEANINGFUL_GAIN_DB,
|
||||
"meaningful_ratio_improvement_median_at_least_db": MEANINGFUL_RATIO_IMPROVEMENT_DB,
|
||||
"maximum_level_to_level_gain_spread_db": MAX_LEVEL_TO_LEVEL_GAIN_SPREAD_DB,
|
||||
"residual_tracks_if_within_matched_gain_db": RESIDUAL_TRACKING_MARGIN_DB,
|
||||
}
|
||||
|
||||
|
||||
def classify_result(new_result: dict, cross_comparison: dict) -> dict:
|
||||
"""Выбрать один из пяти заранее заданных результатов Lab065."""
|
||||
if not new_result.get("capture_valid"):
|
||||
return {"case": "INVALID_CAPTURE", "rules_fixed_before_rf": classification_rules()}
|
||||
internal_errors = [row["linearity_error_db"] for row in new_result["comparisons"]]
|
||||
upper_error = internal_errors[-1]
|
||||
cross_rows = cross_comparison["rows"]
|
||||
matched = np.asarray([row["rx_matched_power_difference_db"] for row in cross_rows])
|
||||
residual = np.asarray([row["residual_power_difference_db"] for row in cross_rows])
|
||||
improvements = np.asarray([row["matched_to_residual_improvement_db"] for row in cross_rows])
|
||||
median_matched = float(np.median(matched))
|
||||
median_residual = float(np.median(residual))
|
||||
median_improvement = float(np.median(improvements))
|
||||
spread = float(np.ptp(matched))
|
||||
linear = max(abs(value) for value in internal_errors) <= 1.0
|
||||
if upper_error <= -1.5:
|
||||
case = "COMPRESSION_AT_HIGHER_TX_GAIN"
|
||||
elif median_matched < MEANINGFUL_GAIN_DB:
|
||||
case = "NO_MEANINGFUL_GAIN"
|
||||
elif (
|
||||
linear
|
||||
and spread <= MAX_LEVEL_TO_LEVEL_GAIN_SPREAD_DB
|
||||
and median_improvement >= MEANINGFUL_RATIO_IMPROVEMENT_DB
|
||||
):
|
||||
case = "LINEAR_GAIN_IMPROVEMENT"
|
||||
elif (
|
||||
median_residual >= median_matched - RESIDUAL_TRACKING_MARGIN_DB
|
||||
and median_improvement < MEANINGFUL_RATIO_IMPROVEMENT_DB
|
||||
):
|
||||
case = "RESIDUAL_RISES_WITH_SIGNAL"
|
||||
else:
|
||||
case = "NOT_LOCALIZED"
|
||||
return {
|
||||
"case": case,
|
||||
"summary": {
|
||||
"median_matched_gain_db": median_matched,
|
||||
"median_residual_gain_db": median_residual,
|
||||
"median_matched_to_residual_improvement_db": median_improvement,
|
||||
"matched_gain_level_spread_db": spread,
|
||||
"maximum_internal_linearity_error_db": max(abs(value) for value in internal_errors),
|
||||
"upper_step_linearity_error_db": upper_error,
|
||||
},
|
||||
"rules_fixed_before_rf": classification_rules(),
|
||||
}
|
||||
|
||||
|
||||
def software_gate() -> dict:
|
||||
metadata = validate_prepared()
|
||||
old_result = json.loads(LAB064_RESULT.read_text(encoding="utf-8"))
|
||||
synthetic = lab064.synthetic_linearity_gate()
|
||||
result = {
|
||||
"lab": "Lab065",
|
||||
"stage": "SOFTWARE_GATE",
|
||||
"rf_tx_count": 0,
|
||||
"passed": bool(synthetic["passed"] and old_result["capture_valid"]),
|
||||
"tx_sha256": metadata["tx_sha256"],
|
||||
"waveform_unchanged_from_lab064": metadata["waveform_unchanged_from_lab064"],
|
||||
"lab064_baseline_classification": old_result["classification"]["case"],
|
||||
"classification_rules": classification_rules(),
|
||||
}
|
||||
save_new(OUTPUT / "software_gate.json", result)
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab065 software gate FAIL")
|
||||
return result
|
||||
|
||||
|
||||
def preflight(*, runner=None, process_checker=None, clock_checker=None, sleep_function=time.sleep) -> dict:
|
||||
"""Проверить общую опору и 0,1 с RX-фона при полном запрете TX."""
|
||||
metadata = validate_prepared()
|
||||
software = json.loads((OUTPUT / "software_gate.json").read_text(encoding="utf-8"))
|
||||
if not software.get("passed") or software.get("tx_sha256") != metadata["tx_sha256"]:
|
||||
raise RuntimeError("Нет действующего Lab065 software gate")
|
||||
save_new(RAW / "preflight_attempt.json", {
|
||||
"lab": "Lab065", "rf_tx_count": 0, "point": asdict(POINT),
|
||||
"parameters": parameters(), "tx_sha256": metadata["tx_sha256"],
|
||||
})
|
||||
actual_runner = runner or lab054.clock._default_command_runner
|
||||
process_checker = process_checker or lab054.check_processes
|
||||
clock_checker = clock_checker or hardware.common_clock
|
||||
|
||||
def rx_only(command: list[str]):
|
||||
if "-t" in command:
|
||||
raise PermissionError("TX запрещён в Lab065 preflight")
|
||||
return actual_runner(command)
|
||||
|
||||
result = {
|
||||
"lab": "Lab065", "stage": "COMMON_CLOCK_AND_RX_ONLY_PREFLIGHT",
|
||||
"passed": False, "rf_tx_count": 0, "stop_before_tx": True,
|
||||
"point": asdict(POINT), "parameters": parameters(),
|
||||
"tx_sha256": metadata["tx_sha256"],
|
||||
}
|
||||
clock_started = False
|
||||
try:
|
||||
before = process_checker()
|
||||
result["process_before"] = before.to_dict()
|
||||
lab054.require_clear(before)
|
||||
result["inventory"] = lab054.phy.lab045._verify_device_inventory(rx_only)
|
||||
clock_started = True
|
||||
result["clock"] = clock_checker(
|
||||
POINT, rx_only, lab054.clock.TransmitGuard(), sleep_function=sleep_function
|
||||
)
|
||||
hardware.require_clock_pass(result["clock"])
|
||||
path = RAW / "preflight_rx.cs8"
|
||||
captured = rx_only(transfer_command(path, lab064.PREFLIGHT_SAMPLES))
|
||||
stdout, stderr = captured.stdout or "", captured.stderr or ""
|
||||
result["rx_process"] = {
|
||||
"returncode": captured.returncode, "stdout": stdout, "stderr": stderr,
|
||||
}
|
||||
result["rx_counter_summary"] = lab064.counter_summary(stdout + stderr, "overrun")
|
||||
if captured.returncode or not result["rx_counter_summary"]["clean"]:
|
||||
raise RuntimeError("RX preflight: процесс или счётчик overruns не прошёл")
|
||||
if path.stat().st_size != 2 * lab064.PREFLIGHT_SAMPLES:
|
||||
raise RuntimeError("Неверная длина Lab065 preflight RX")
|
||||
level = hardware.receive_level(path)
|
||||
level.pop("symbol_energy_code2_seconds", None)
|
||||
level["symbol_energy"] = "NOT_APPLICABLE_TO_BACKGROUND"
|
||||
level["ac_rms_code"] = float(np.sqrt(max(0.0, level["ac_power_code2"])))
|
||||
level["unexpected_strong_signal"] = bool(
|
||||
level["ac_rms_code"] >= lab064.STRONG_AC_RMS_CODE
|
||||
or level["max_abs_i"] >= lab064.STRONG_COMPONENT_CODE
|
||||
or level["max_abs_q"] >= lab064.STRONG_COMPONENT_CODE
|
||||
)
|
||||
result["rx_level"] = level
|
||||
result["rx_sha256"] = sha256(path)
|
||||
result["rx_overruns"] = result["rx_counter_summary"]["final_count"]
|
||||
if level["saturation_detected"] or level["unexpected_strong_signal"]:
|
||||
raise RuntimeError("RX-only уровень не прошёл gate; STOP без изменения gain")
|
||||
result["passed"] = True
|
||||
except Exception as error:
|
||||
result["error"] = str(error)
|
||||
raise
|
||||
finally:
|
||||
errors = lab054.disable_clock_outputs(rx_only) if clock_started else []
|
||||
try:
|
||||
after = process_checker()
|
||||
result["process_after"] = after.to_dict()
|
||||
lab054.require_clear(after)
|
||||
except Exception as error:
|
||||
errors.append(str(error))
|
||||
result["cleanup_errors"] = errors
|
||||
result["passed"] = result["passed"] and not errors
|
||||
save_new(RAW / "preflight_result.json", result)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUTPUT / "preflight_result.json").write_text(
|
||||
json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab065 preflight FAIL")
|
||||
return result
|
||||
|
||||
|
||||
def require_preflight(metadata: dict) -> dict:
|
||||
record = json.loads((RAW / "preflight_result.json").read_text(encoding="utf-8"))
|
||||
if (
|
||||
record.get("passed") is not True
|
||||
or record.get("rf_tx_count") != 0
|
||||
or record.get("point") != asdict(POINT)
|
||||
or record.get("parameters") != parameters()
|
||||
or record.get("tx_sha256") != metadata["tx_sha256"]
|
||||
):
|
||||
raise PermissionError("Нет успешного RX-only preflight для конфигурации Lab065")
|
||||
hardware.require_clock_pass(record.get("clock", {}))
|
||||
level = record.get("rx_level", {})
|
||||
if (
|
||||
level.get("saturation_detected") is not False
|
||||
or level.get("unexpected_strong_signal") is not False
|
||||
or record.get("rx_overruns") != 0
|
||||
):
|
||||
raise PermissionError("RX-only preflight не разрешает передачу")
|
||||
return record
|
||||
|
||||
|
||||
def capture_once(
|
||||
*, authorization: bool = False, runner=None, process_starter=None,
|
||||
process_checker=None, clock_checker=None, sleep_function=time.sleep,
|
||||
) -> dict:
|
||||
"""Выполнить один RX-first сеанс после отдельного прямого разрешения."""
|
||||
if authorization is not True:
|
||||
raise PermissionError("Нужно отдельное разрешение на единственный RF-сеанс Lab065")
|
||||
metadata = validate_prepared()
|
||||
require_preflight(metadata)
|
||||
receive_path = RAW / "rx_stream.cs8"
|
||||
if receive_path.exists() or (RAW / "capture_result.json").exists():
|
||||
raise FileExistsError("Аппаратный результат Lab065 уже существует")
|
||||
actual_runner = runner or lab054.clock._default_command_runner
|
||||
process_starter = process_starter or lab054.clock._default_process_starter
|
||||
process_checker = process_checker or lab054.check_processes
|
||||
clock_checker = clock_checker or hardware.common_clock
|
||||
save_new(RAW / "hardware_attempt.json", {
|
||||
"lab": "Lab065", "authorized_once": True, "automatic_retry": False,
|
||||
"point": asdict(POINT), "parameters": parameters(),
|
||||
"tx_sha256": metadata["tx_sha256"],
|
||||
})
|
||||
result = {
|
||||
"lab": "Lab065", "stage": "SINGLE_RF_LEVEL_CAPTURE", "passed": False,
|
||||
"rf_tx_count": 0, "automatic_tx_repeats": 0, "point": asdict(POINT),
|
||||
"parameters": parameters(), "tx_sha256": metadata["tx_sha256"],
|
||||
}
|
||||
guard = lab054.clock.TransmitGuard()
|
||||
process = None
|
||||
clock_started = False
|
||||
try:
|
||||
before = process_checker()
|
||||
result["process_before"] = before.to_dict()
|
||||
lab054.require_clear(before)
|
||||
result["inventory"] = lab054.phy.lab045._verify_device_inventory(actual_runner)
|
||||
clock_started = True
|
||||
result["clock"] = clock_checker(
|
||||
POINT, actual_runner, guard, sleep_function=sleep_function
|
||||
)
|
||||
hardware.require_clock_pass(result["clock"])
|
||||
process = process_starter(
|
||||
transfer_command(receive_path, lab064.RX_SAMPLES),
|
||||
RAW / "rx_stdout.txt", RAW / "rx_stderr.txt",
|
||||
)
|
||||
sleep_function(lab064.RX_LEAD_SECONDS)
|
||||
if process.poll() is not None:
|
||||
raise RuntimeError("RX завершился до TX")
|
||||
before_tx = hardware.receive_level(receive_path, tail_samples=65_536)
|
||||
before_tx.pop("symbol_energy_code2_seconds", None)
|
||||
result["rx_level_before_tx"] = before_tx
|
||||
if before_tx["saturation_detected"]:
|
||||
raise RuntimeError("RX clipping до TX; RF TX = 0")
|
||||
guard.register_transmit()
|
||||
result["rf_tx_count"] = guard.transmit_calls
|
||||
transmitted = actual_runner(
|
||||
transfer_command(RAW / "tx_level_staircase.cs8", lab064.TX_SAMPLES, transmit=True)
|
||||
)
|
||||
tx_stdout, tx_stderr = transmitted.stdout or "", transmitted.stderr or ""
|
||||
save_text_new(RAW / "tx_stdout.txt", tx_stdout)
|
||||
save_text_new(RAW / "tx_stderr.txt", tx_stderr)
|
||||
result["tx_process"] = {
|
||||
"returncode": transmitted.returncode,
|
||||
"stdout_file": "tx_stdout.txt", "stderr_file": "tx_stderr.txt",
|
||||
}
|
||||
result["tx_counter_summary"] = lab064.counter_summary(
|
||||
tx_stdout + tx_stderr, "underrun"
|
||||
)
|
||||
if transmitted.returncode:
|
||||
raise RuntimeError("Единственный TX завершился ошибкой; повтор запрещён")
|
||||
if process.wait(lab064.RX_SECONDS + 10):
|
||||
raise RuntimeError("RX завершился ошибкой; повтор запрещён")
|
||||
process.close_logs()
|
||||
result["rx_samples"] = receive_path.stat().st_size // 2
|
||||
result["rx_bytes"] = receive_path.stat().st_size
|
||||
result["rx_duration_seconds"] = result["rx_samples"] / lab064.SAMPLE_RATE
|
||||
if result["rx_bytes"] != 2 * lab064.RX_SAMPLES:
|
||||
raise RuntimeError("Повреждённая длина RX; повтор запрещён")
|
||||
rx_stdout = (RAW / "rx_stdout.txt").read_text(encoding="utf-8")
|
||||
rx_stderr = (RAW / "rx_stderr.txt").read_text(encoding="utf-8")
|
||||
result["rx_counter_summary"] = lab064.counter_summary(
|
||||
rx_stdout + rx_stderr, "overrun"
|
||||
)
|
||||
result["rx_sha256"] = sha256(receive_path)
|
||||
level = hardware.receive_level(receive_path)
|
||||
level.pop("symbol_energy_code2_seconds", None)
|
||||
result["rx_level"] = level
|
||||
result["rx_clipping"] = bool(level["saturation_detected"])
|
||||
result["passed"] = True
|
||||
validity = lab064.capture_validity(result)
|
||||
if not validity["valid"]:
|
||||
result["passed"] = False
|
||||
raise RuntimeError("Запись невалидна; результат сохранён, повтор запрещён")
|
||||
except Exception as error:
|
||||
result["error"] = str(error)
|
||||
raise
|
||||
finally:
|
||||
errors = []
|
||||
if process is not None:
|
||||
try:
|
||||
process.terminate()
|
||||
except Exception as error:
|
||||
errors.append(str(error))
|
||||
if clock_started:
|
||||
errors.extend(lab054.disable_clock_outputs(actual_runner))
|
||||
try:
|
||||
after = process_checker()
|
||||
result["process_after"] = after.to_dict()
|
||||
lab054.require_clear(after)
|
||||
except Exception as error:
|
||||
errors.append(str(error))
|
||||
result["cleanup_errors"] = errors
|
||||
result["passed"] = result["passed"] and not errors
|
||||
save_new(RAW / "capture_result.json", result)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUTPUT / "capture_result.json").write_text(
|
||||
json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab065 RF-сеанс завершён с ошибкой; повтор запрещён")
|
||||
return result
|
||||
|
||||
|
||||
def analyze_capture() -> dict:
|
||||
"""Рассчитать метрики Lab065 и прямое сравнение с Lab064."""
|
||||
metadata = validate_prepared()
|
||||
rx_path = RAW / "rx_stream.cs8"
|
||||
capture = json.loads((RAW / "capture_result.json").read_text(encoding="utf-8"))
|
||||
if capture.get("rf_tx_count") != 1 or capture.get("rx_sha256") != sha256(rx_path):
|
||||
raise RuntimeError("Нет подтверждённой единственной RX-записи Lab065")
|
||||
tx = lab064.energy.Cs8File(RAW / "tx_level_staircase.cs8")
|
||||
rx = lab064.energy.Cs8File(rx_path)
|
||||
location = lab064.locate_waveform(tx, rx)
|
||||
background_rows = []
|
||||
for window in lab064.background_windows():
|
||||
rx_start = location["start_sample"] + window["tx_relative_start_sample"]
|
||||
background_rows.append({
|
||||
**window, "rx_start_sample": rx_start,
|
||||
"metrics": lab064._window_metrics(rx.read(rx_start, window["samples"])),
|
||||
})
|
||||
background_by_label = {row["label"]: row for row in background_rows}
|
||||
preceding = {"L0": "INITIAL_GUARD", "L1": "GAP_L0_L1",
|
||||
"L2": "GAP_L1_L2", "L3": "GAP_L2_L3"}
|
||||
rows = []
|
||||
for segment in metadata["segments"]:
|
||||
tx_start = segment["start_sample"]
|
||||
nominal_rx_start = location["start_sample"] + tx_start
|
||||
fitted = lab064.energy.analyze_rx(
|
||||
lab064.lab056.OffsetSource(tx, tx_start), rx, [nominal_rx_start],
|
||||
lab064.SEGMENT_SAMPLES, lab064.SAMPLES_PER_SYMBOL,
|
||||
)["frames"][0]
|
||||
actual_start = fitted["start"]
|
||||
active_reference = tx.read(
|
||||
tx_start + lab064.TRIM_SAMPLES,
|
||||
lab064.SEGMENT_SAMPLES - 2 * lab064.TRIM_SAMPLES,
|
||||
)
|
||||
active_received = rx.read(
|
||||
actual_start + lab064.TRIM_SAMPLES,
|
||||
lab064.SEGMENT_SAMPLES - 2 * lab064.TRIM_SAMPLES,
|
||||
)
|
||||
complex_gain = lab064.lab056.complex_gain(
|
||||
active_reference, active_received, fitted["cfo_hz"]
|
||||
)
|
||||
matched = fitted["heldout_signal_power"]
|
||||
residual = fitted["heldout_residual_power"]
|
||||
background = background_by_label[preceding[segment["label"]]]
|
||||
rows.append({
|
||||
"label": segment["label"], "level_db": segment["level_db"],
|
||||
"rx_start_sample": actual_start,
|
||||
"alignment_adjustment_samples": fitted["alignment_adjustment"],
|
||||
"full_iq": lab064._window_metrics(rx.read(actual_start, lab064.SEGMENT_SAMPLES)),
|
||||
"background_label": background["label"], "background": background["metrics"],
|
||||
"matched_signal_power": matched, "residual_power": residual,
|
||||
"matched_to_residual_db": lab064.difference_db(matched, residual),
|
||||
"complex_gain_h": complex_gain, "gain_abs": fitted["gain_abs"],
|
||||
"gain_power_abs_h_squared": fitted["gain_abs"] ** 2,
|
||||
"cfo_hz": fitted["cfo_hz"],
|
||||
"residual_spectrum": lab064.residual_spectral_diagnostics(
|
||||
active_reference, active_received, fitted["cfo_hz"], complex_gain
|
||||
),
|
||||
})
|
||||
comparisons = []
|
||||
for index in range(1, len(rows)):
|
||||
tx_difference = metadata["segments"][index]["tx_input_difference_from_previous_db"]
|
||||
rx_difference = lab064.difference_db(
|
||||
rows[index]["matched_signal_power"], rows[index - 1]["matched_signal_power"]
|
||||
)
|
||||
comparisons.append({
|
||||
"comparison": f"{rows[index]['label']}/{rows[index - 1]['label']}",
|
||||
"tx_input_difference_db": tx_difference,
|
||||
"rx_matched_power_difference_db": rx_difference,
|
||||
"linearity_error_db": rx_difference - tx_difference,
|
||||
})
|
||||
validity = lab064.capture_validity(capture)
|
||||
result = {
|
||||
"lab": "Lab065", "stage": "OFFLINE_RF_LEVEL_LINEARITY_ANALYSIS",
|
||||
"packet_receiver_used": False, "location": location, "rows": rows,
|
||||
"background_windows": background_rows,
|
||||
"background_summary": lab064.summarize_background(background_rows),
|
||||
"comparisons": comparisons, "capture_valid": validity["valid"],
|
||||
"capture_invalid_reasons": validity["invalid_reasons"],
|
||||
"tx_underruns_maximum": capture.get("tx_counter_summary", {}).get("maximum_count"),
|
||||
"rx_overruns_maximum": capture.get("rx_counter_summary", {}).get("maximum_count"),
|
||||
"rx_clipping": any(row["full_iq"]["clipped_samples_fraction"] > 0 for row in rows),
|
||||
}
|
||||
old_result = json.loads(LAB064_RESULT.read_text(encoding="utf-8"))
|
||||
result["lab064_to_lab065"] = compare_sessions(old_result, rows)
|
||||
result["classification"] = classify_result(result, result["lab064_to_lab065"])
|
||||
save_new(OUTPUT / "rf_result.json", result)
|
||||
return result
|
||||
|
||||
|
||||
def run_functional_tests() -> None:
|
||||
assert transfer_command("NUL", 100, transmit=True)[-7:-5] == ["-x", "20"]
|
||||
assert "-t" not in transfer_command("NUL", 100)
|
||||
assert validate_lab064_waveform()["tx_sha256"] == EXPECTED_TX_SHA256
|
||||
assert lab064.synthetic_linearity_gate()["passed"]
|
||||
try:
|
||||
capture_once(authorization=False)
|
||||
except PermissionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("RF без отдельного разрешения")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--stage", choices=("prepare", "software", "preflight", "capture", "analyze"),
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument("--authorize-one-rf", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.stage == "prepare":
|
||||
result = prepare()
|
||||
elif arguments.stage == "software":
|
||||
result = software_gate()
|
||||
elif arguments.stage == "preflight":
|
||||
result = preflight()
|
||||
elif arguments.stage == "capture":
|
||||
result = capture_once(authorization=arguments.authorize_one_rf)
|
||||
else:
|
||||
result = analyze_capture()
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False))
|
||||
print("RF TX NOW = 0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
786
experiments/lab066_full_packet_rf_retest.py
Normal file
@@ -0,0 +1,786 @@
|
||||
"""Lab066: исправленный повтор полного 500K PHY-сеанса.
|
||||
|
||||
Подготовка, программная проверка и preflight не передают RF. Единственный
|
||||
аппаратный сеанс доступен только отдельной командой с явным разрешением.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
import argparse
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import numpy as np
|
||||
from scipy import signal
|
||||
|
||||
from experiments import lab049_bpsk_symbol_rate as phy
|
||||
from experiments import lab053_packet_processing as packet_processing
|
||||
from experiments import lab053_streaming_receiver as streaming
|
||||
from experiments import lab054_hardware_control as lab054
|
||||
from experiments import lab056_capture_runner as hardware
|
||||
from experiments import lab058_abab_symbol_quality as lab058
|
||||
from experiments import lab064_rf_level_linearity as lab064
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RAW = ROOT / "data/raw/lab066"
|
||||
OUTPUT = ROOT / "data/processed/lab066"
|
||||
POINT = hardware.OperatingPoint(
|
||||
frequency_hz=1_400_000_000,
|
||||
physical_path="MASTER TX near window -> LOS indoor -> SLAVE RX on desk",
|
||||
path_confirmed=True,
|
||||
tx_antenna="existing 1400 MHz antenna near window",
|
||||
rx_antenna="existing 1400 MHz antenna on desk",
|
||||
distance_m=1.2,
|
||||
orientation="same polarization; fixed during preflight and RF session",
|
||||
visibility="LOS indoor",
|
||||
)
|
||||
MODE = phy.PhyMode(500_000)
|
||||
PACKETS = 16
|
||||
PAYLOAD_BYTES = 256
|
||||
PACKET_SAMPLES = 92_256
|
||||
INTER_PACKET_GAP_SECONDS = 0.005
|
||||
INTER_PACKET_GAP_SAMPLES = 40_000
|
||||
NUMBER_OF_GAPS = PACKETS - 1
|
||||
GUARD_SECONDS = 0.1
|
||||
GUARD_SAMPLES = 800_000
|
||||
BASE_TX_SAMPLES = PACKETS * PACKET_SAMPLES + NUMBER_OF_GAPS * INTER_PACKET_GAP_SAMPLES
|
||||
EXPECTED_TX_SAMPLES = BASE_TX_SAMPLES + 2 * GUARD_SAMPLES
|
||||
PREFLIGHT_SAMPLES = 800_000
|
||||
RX_SECONDS = 2
|
||||
RX_SAMPLES = RX_SECONDS * MODE.sample_rate_hz
|
||||
RX_LEAD_SECONDS = 0.5
|
||||
TX_VGA_DB = 20
|
||||
STRONG_AC_RMS_CODE = 20.0
|
||||
STRONG_COMPONENT_CODE = 100.0
|
||||
|
||||
|
||||
def save_new(path: Path, value: dict) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
stream.write("\n")
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while block := stream.read(1024 * 1024):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def plans():
|
||||
result = lab054.plans()
|
||||
payloads = [frame.plan.payload for frame in result]
|
||||
if len(result) != PACKETS or any(len(payload) != PAYLOAD_BYTES for payload in payloads):
|
||||
raise RuntimeError("Штатный генератор не дал 16 payload по 256 байт")
|
||||
if len(set(payloads)) != PACKETS:
|
||||
raise RuntimeError("Payload пакетов должны однозначно различаться")
|
||||
return result
|
||||
|
||||
|
||||
def parameters() -> dict:
|
||||
return {
|
||||
"rf_frequency_hz": POINT.frequency_hz,
|
||||
"sample_rate_hz": MODE.sample_rate_hz,
|
||||
"symbol_rate": MODE.symbol_rate,
|
||||
"samples_per_symbol": MODE.samples_per_symbol,
|
||||
"digital_carrier_hz": lab054.phy.lab045.CARRIER_OFFSET_HZ,
|
||||
"packets": PACKETS,
|
||||
"payload_bytes": PAYLOAD_BYTES,
|
||||
"tx_vga_db": TX_VGA_DB,
|
||||
"rx_lna_db": 24,
|
||||
"rx_vga_db": 24,
|
||||
"rf_amplifier": False,
|
||||
"bias_tee": False,
|
||||
"geometry": asdict(POINT),
|
||||
"geometry_unchanged": True,
|
||||
"clock_path": "10 MHz TCXO -> MASTER -> MASTER CLKOUT -> SLAVE CLKIN",
|
||||
}
|
||||
|
||||
|
||||
def transfer_command(path: Path | str, samples: int, *, transmit: bool = False) -> list[str]:
|
||||
command = hardware.command(POINT, path, samples, transmit=transmit)
|
||||
if transmit:
|
||||
command[command.index("-x") + 1] = str(TX_VGA_DB)
|
||||
return command
|
||||
|
||||
|
||||
def build_waveform() -> tuple[bytes, list[dict]]:
|
||||
"""Собрать финальный поток с нулевыми guards и паузами."""
|
||||
blocks: list[bytes] = [bytes(2 * GUARD_SAMPLES)]
|
||||
layout: list[dict] = []
|
||||
cursor = GUARD_SAMPLES
|
||||
for index, frame in enumerate(plans(), start=1):
|
||||
packet = phy.build_parameterized_tx_cs8(frame.plan, MODE)
|
||||
if len(packet) != 2 * PACKET_SAMPLES:
|
||||
raise RuntimeError(f"Пакет {index}: ожидалось {PACKET_SAMPLES} отсчётов")
|
||||
layout.append({
|
||||
"packet_index": index,
|
||||
"start_sample": cursor,
|
||||
"samples": PACKET_SAMPLES,
|
||||
"payload_sha256": hashlib.sha256(frame.plan.payload).hexdigest(),
|
||||
})
|
||||
blocks.append(packet)
|
||||
cursor += PACKET_SAMPLES
|
||||
if index != PACKETS:
|
||||
blocks.append(bytes(2 * INTER_PACKET_GAP_SAMPLES))
|
||||
layout[-1]["following_gap_start_sample"] = cursor
|
||||
layout[-1]["following_gap_samples"] = INTER_PACKET_GAP_SAMPLES
|
||||
cursor += INTER_PACKET_GAP_SAMPLES
|
||||
blocks.append(bytes(2 * GUARD_SAMPLES))
|
||||
waveform = b"".join(blocks)
|
||||
if len(waveform) != 2 * EXPECTED_TX_SAMPLES or cursor + GUARD_SAMPLES != EXPECTED_TX_SAMPLES:
|
||||
raise RuntimeError("Неверная длина итогового TX-потока Lab066")
|
||||
return waveform, layout
|
||||
|
||||
|
||||
def prepare() -> dict:
|
||||
RAW.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
waveform_path = RAW / "tx_stream.cs8"
|
||||
if waveform_path.exists():
|
||||
raise FileExistsError("Lab066 TX уже существует; перезапись запрещена")
|
||||
waveform, layout = build_waveform()
|
||||
waveform_path.write_bytes(waveform)
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "PREPARATION_ONLY", "rf_tx_count": 0,
|
||||
"head": subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip(),
|
||||
"parameters": parameters(), "packet_samples": PACKET_SAMPLES,
|
||||
"inter_packet_gap_seconds": INTER_PACKET_GAP_SECONDS,
|
||||
"inter_packet_gap_samples": INTER_PACKET_GAP_SAMPLES,
|
||||
"number_of_gaps": NUMBER_OF_GAPS,
|
||||
"zero_guard_before_samples": GUARD_SAMPLES,
|
||||
"zero_guard_after_samples": GUARD_SAMPLES,
|
||||
"base_tx_samples": BASE_TX_SAMPLES,
|
||||
"expected_tx_samples": EXPECTED_TX_SAMPLES,
|
||||
"tx_samples": EXPECTED_TX_SAMPLES, "tx_bytes": len(waveform),
|
||||
"tx_duration_seconds": EXPECTED_TX_SAMPLES / MODE.sample_rate_hz,
|
||||
"tx_sha256": sha256(waveform_path), "layout": layout,
|
||||
"rx_samples": RX_SAMPLES, "rx_duration_seconds": RX_SECONDS,
|
||||
"automatic_retry": False,
|
||||
"tx_command_prepared_not_run": transfer_command(waveform_path, EXPECTED_TX_SAMPLES, transmit=True),
|
||||
"rx_command_prepared_not_run": transfer_command(RAW / "rx_stream.cs8", RX_SAMPLES),
|
||||
}
|
||||
save_new(RAW / "tx_metadata.json", result)
|
||||
save_new(OUTPUT / "preparation.json", result)
|
||||
return result
|
||||
|
||||
|
||||
def validate_prepared() -> dict:
|
||||
metadata = json.loads((RAW / "tx_metadata.json").read_text(encoding="utf-8"))
|
||||
path = RAW / "tx_stream.cs8"
|
||||
expected, layout = build_waveform()
|
||||
expected_digest = hashlib.sha256(expected).hexdigest()
|
||||
actual = path.read_bytes()
|
||||
if (
|
||||
metadata.get("lab") != "Lab066"
|
||||
or metadata.get("stage") != "PREPARATION_ONLY"
|
||||
or metadata.get("rf_tx_count") != 0
|
||||
or metadata.get("parameters") != parameters()
|
||||
or metadata.get("layout") != layout
|
||||
or metadata.get("expected_tx_samples") != EXPECTED_TX_SAMPLES
|
||||
or metadata.get("tx_samples") != EXPECTED_TX_SAMPLES
|
||||
or len(actual) != 2 * EXPECTED_TX_SAMPLES
|
||||
or metadata.get("tx_sha256") != expected_digest
|
||||
or hashlib.sha256(actual).hexdigest() != expected_digest
|
||||
or actual != expected
|
||||
):
|
||||
raise RuntimeError("Подготовленный Lab066 TX или его разметка изменились")
|
||||
return metadata
|
||||
|
||||
|
||||
def decode(mode: str, source_path: Path | None = None, *, diagnostics: bool = False) -> dict:
|
||||
"""Строго декодировать весь поток; legacy и T2 запускаются отдельно."""
|
||||
observations = []
|
||||
receiver = streaming.StreamingReceiver(
|
||||
processor=packet_processing.PacketProcessor(carrier_tracking_mode=mode),
|
||||
packet_observer=lambda row, outcome: observations.append((row, outcome)),
|
||||
)
|
||||
received = []
|
||||
with (source_path or (RAW / "tx_stream.cs8")).open("rb") as stream:
|
||||
while block := stream.read(2 * receiver.block_samples):
|
||||
values = np.frombuffer(block, np.int8)
|
||||
samples = values[::2].astype(np.float32) + 1j * values[1::2].astype(np.float32)
|
||||
received.extend(receiver.push_iq_block(samples.astype(np.complex64)))
|
||||
received.extend(receiver.finish())
|
||||
expected = [frame.plan.payload for frame in plans()]
|
||||
actual = [item.payload for item in received]
|
||||
payload_to_index = {payload: index for index, payload in enumerate(expected, start=1)}
|
||||
result = {
|
||||
"mode": mode, "candidates": len(observations),
|
||||
"header_pass": sum(outcome.phy_header_pass for _, outcome in observations),
|
||||
"fec_started": sum(outcome.fec_attempted for _, outcome in observations),
|
||||
"fec_pass": sum(outcome.fec_pass for _, outcome in observations),
|
||||
"crc_started": sum(outcome.crc != "NOT_REACHED" for _, outcome in observations),
|
||||
"crc_pass": sum(outcome.crc == "PASS" for _, outcome in observations),
|
||||
"packet_output": len(actual),
|
||||
"exact_payload_match": sum(payload in payload_to_index for payload in actual),
|
||||
"packet_indexes": [payload_to_index.get(payload) for payload in actual],
|
||||
"exact_ordered_payloads": actual == expected,
|
||||
"t2_state_scope": "NEW_PREAMBLE_ESTIMATE_PER_PACKET" if mode == "t2" else "NOT_APPLICABLE",
|
||||
"fallback_between_modes": False,
|
||||
"passed": len(actual) == PACKETS and actual == expected,
|
||||
}
|
||||
if diagnostics:
|
||||
result["rows"] = [{
|
||||
"absolute_refined": int(row["absolute_refined"]),
|
||||
"reason": outcome.reason,
|
||||
"header": bool(outcome.phy_header_pass),
|
||||
"fec_started": bool(outcome.fec_attempted), "fec": bool(outcome.fec_pass),
|
||||
"crc_started": outcome.crc != "NOT_REACHED", "crc": outcome.crc == "PASS",
|
||||
"packet_output": bool(outcome.accepted and outcome.crc == "PASS"),
|
||||
"packet_index_after_verdict": payload_to_index.get(
|
||||
bytes(outcome.frame.reception.payload_bytes)
|
||||
if outcome.frame is not None and outcome.frame.reception.payload_bytes is not None else b""
|
||||
),
|
||||
"t2_tracking_runs_for_candidate": len(row.get("carrier_tracking_traces", ())),
|
||||
} for row, outcome in observations]
|
||||
result["detector_statistics"] = dict(receiver.detection_statistics)
|
||||
result["candidate_overflow"] = receiver.candidate_overflow
|
||||
if mode == "t2":
|
||||
result["t2_state_reset_evidence"] = {
|
||||
"estimate_source": "each candidate's own preamble",
|
||||
"context_scope": "new PacketMeasure context for every candidate",
|
||||
"tracking_runs_per_accepted_packet": [
|
||||
row["t2_tracking_runs_for_candidate"]
|
||||
for row in result["rows"] if row["packet_output"]
|
||||
],
|
||||
"all_accepted_packets_have_fresh_tracking_runs": all(
|
||||
row["t2_tracking_runs_for_candidate"] > 0
|
||||
for row in result["rows"] if row["packet_output"]
|
||||
),
|
||||
"persistent_tracking_state_in_packet_processor": False,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def software_gate() -> dict:
|
||||
metadata = validate_prepared()
|
||||
legacy = decode("legacy", diagnostics=True)
|
||||
t2 = decode("t2", diagnostics=True)
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "SOFTWARE_GATE", "rf_tx_count": 0,
|
||||
"tx_sha256": metadata["tx_sha256"], "legacy": legacy, "t2": t2,
|
||||
"gaps_verified_zero": True,
|
||||
"t2_state_reset_verified_by_fresh_preamble_per_packet": bool(
|
||||
t2.get("t2_state_reset_evidence", {}).get(
|
||||
"all_accepted_packets_have_fresh_tracking_runs"
|
||||
)
|
||||
),
|
||||
"passed": (
|
||||
legacy["passed"] and t2["passed"]
|
||||
and t2.get("t2_state_reset_evidence", {}).get(
|
||||
"all_accepted_packets_have_fresh_tracking_runs"
|
||||
) is True
|
||||
),
|
||||
}
|
||||
save_new(OUTPUT / "software_gate.json", result)
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab066 software gate FAIL")
|
||||
return result
|
||||
|
||||
|
||||
def preflight(*, runner=None, process_checker=None, clock_checker=None,
|
||||
sleep_function=time.sleep) -> dict:
|
||||
metadata = validate_prepared()
|
||||
software = json.loads((OUTPUT / "software_gate.json").read_text(encoding="utf-8"))
|
||||
if not software.get("passed") or software.get("tx_sha256") != metadata["tx_sha256"]:
|
||||
raise RuntimeError("Нет действующего software gate Lab066")
|
||||
save_new(RAW / "preflight_attempt.json", {
|
||||
"lab": "Lab066", "rf_tx_count": 0, "point": asdict(POINT),
|
||||
"tx_sha256": metadata["tx_sha256"],
|
||||
})
|
||||
actual_runner = runner or lab054.clock._default_command_runner
|
||||
process_checker = process_checker or lab054.check_processes
|
||||
clock_checker = clock_checker or hardware.common_clock
|
||||
|
||||
def rx_only(command):
|
||||
if "-t" in command:
|
||||
raise PermissionError("TX запрещён в Lab066 preflight")
|
||||
return actual_runner(command)
|
||||
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "RX_ONLY_PREFLIGHT", "passed": False,
|
||||
"rf_tx_count": 0, "point": asdict(POINT), "parameters": parameters(),
|
||||
"tx_sha256": metadata["tx_sha256"], "stop_before_tx": True,
|
||||
}
|
||||
clock_started = False
|
||||
try:
|
||||
before = process_checker()
|
||||
result["process_before"] = before.to_dict()
|
||||
lab054.require_clear(before)
|
||||
result["inventory"] = lab054.phy.lab045._verify_device_inventory(rx_only)
|
||||
clock_started = True
|
||||
result["clock"] = clock_checker(
|
||||
POINT, rx_only, lab054.clock.TransmitGuard(), sleep_function=sleep_function
|
||||
)
|
||||
hardware.require_clock_pass(result["clock"])
|
||||
path = RAW / "preflight_rx.cs8"
|
||||
if path.exists():
|
||||
raise FileExistsError("Lab066 preflight RX уже существует")
|
||||
captured = rx_only(transfer_command(path, PREFLIGHT_SAMPLES))
|
||||
stdout, stderr = captured.stdout or "", captured.stderr or ""
|
||||
result["rx_process"] = {"returncode": captured.returncode, "stdout": stdout, "stderr": stderr}
|
||||
result["rx_counter_summary"] = lab064.counter_summary(stdout + stderr, "overrun")
|
||||
if captured.returncode or not result["rx_counter_summary"]["clean"]:
|
||||
raise RuntimeError("RX preflight: процесс или максимум overruns не прошёл")
|
||||
if path.stat().st_size != 2 * PREFLIGHT_SAMPLES:
|
||||
raise RuntimeError("Неверная длина Lab066 preflight RX")
|
||||
level = hardware.receive_level(path)
|
||||
level.pop("symbol_energy_code2_seconds", None)
|
||||
level["symbol_energy"] = "NOT_APPLICABLE_TO_BACKGROUND"
|
||||
ac_rms = float(np.sqrt(max(0.0, level["ac_power_code2"])))
|
||||
strong = bool(ac_rms >= STRONG_AC_RMS_CODE or level["max_abs_i"] >= STRONG_COMPONENT_CODE
|
||||
or level["max_abs_q"] >= STRONG_COMPONENT_CODE)
|
||||
level.update({"ac_rms_code": ac_rms, "unexpected_strong_signal": strong})
|
||||
result["rx_level"] = level
|
||||
result["rx_sha256"] = sha256(path)
|
||||
result["rx_overruns"] = result["rx_counter_summary"]["maximum_count"]
|
||||
if level["saturation_detected"]:
|
||||
raise RuntimeError("RX clipping; STOP без изменения gain")
|
||||
if strong:
|
||||
raise RuntimeError("Неожиданно сильный RX-only фон; STOP без изменения gain")
|
||||
result["passed"] = True
|
||||
except Exception as error:
|
||||
result["error"] = str(error)
|
||||
raise
|
||||
finally:
|
||||
errors = lab054.disable_clock_outputs(rx_only) if clock_started else []
|
||||
try:
|
||||
after = process_checker()
|
||||
result["process_after"] = after.to_dict()
|
||||
lab054.require_clear(after)
|
||||
except Exception as error:
|
||||
errors.append(str(error))
|
||||
result["cleanup_errors"] = errors
|
||||
result["passed"] = result["passed"] and not errors
|
||||
save_new(RAW / "preflight_result.json", result)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUTPUT / "preflight_result.json").write_text(
|
||||
json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False) + "\n", encoding="utf-8"
|
||||
)
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab066 preflight FAIL")
|
||||
return result
|
||||
|
||||
|
||||
def require_preflight(metadata: dict) -> dict:
|
||||
record = json.loads((RAW / "preflight_result.json").read_text(encoding="utf-8"))
|
||||
if (record.get("passed") is not True or record.get("rf_tx_count") != 0
|
||||
or record.get("point") != asdict(POINT) or record.get("parameters") != parameters()
|
||||
or record.get("tx_sha256") != metadata["tx_sha256"]):
|
||||
raise PermissionError("Нет успешного RX-only preflight для конфигурации Lab066")
|
||||
hardware.require_clock_pass(record.get("clock", {}))
|
||||
if (record.get("rx_overruns") != 0
|
||||
or record.get("rx_level", {}).get("saturation_detected") is not False
|
||||
or record.get("rx_level", {}).get("unexpected_strong_signal") is not False):
|
||||
raise PermissionError("RX-only preflight не разрешает передачу")
|
||||
return record
|
||||
|
||||
|
||||
def capture_once(*, authorization: bool = False, runner=None, process_starter=None,
|
||||
process_checker=None, clock_checker=None, sleep_function=time.sleep) -> dict:
|
||||
"""Выполнить будущий единственный RX-first сеанс после отдельного разрешения."""
|
||||
if authorization is not True:
|
||||
raise PermissionError("Нужно отдельное разрешение на единственный RF-сеанс Lab066")
|
||||
metadata = validate_prepared()
|
||||
require_preflight(metadata)
|
||||
receive_path = RAW / "rx_stream.cs8"
|
||||
if receive_path.exists() or (RAW / "capture_result.json").exists():
|
||||
raise FileExistsError("Аппаратный результат Lab066 уже существует")
|
||||
actual_runner = runner or lab054.clock._default_command_runner
|
||||
process_starter = process_starter or lab054.clock._default_process_starter
|
||||
process_checker = process_checker or lab054.check_processes
|
||||
clock_checker = clock_checker or hardware.common_clock
|
||||
save_new(RAW / "hardware_attempt.json", {
|
||||
"lab": "Lab066", "authorized_once": True, "automatic_retry": False,
|
||||
"point": asdict(POINT), "parameters": parameters(), "tx_sha256": metadata["tx_sha256"],
|
||||
})
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "SINGLE_RF_CAPTURE", "passed": False,
|
||||
"rf_tx_count": 0, "automatic_tx_repeats": 0, "point": asdict(POINT),
|
||||
"parameters": parameters(), "tx_sha256": metadata["tx_sha256"],
|
||||
}
|
||||
guard, process, clock_started = lab054.clock.TransmitGuard(), None, False
|
||||
try:
|
||||
before = process_checker(); result["process_before"] = before.to_dict(); lab054.require_clear(before)
|
||||
result["inventory"] = lab054.phy.lab045._verify_device_inventory(actual_runner)
|
||||
clock_started = True
|
||||
result["clock"] = clock_checker(POINT, actual_runner, guard, sleep_function=sleep_function)
|
||||
hardware.require_clock_pass(result["clock"])
|
||||
process = process_starter(transfer_command(receive_path, RX_SAMPLES), RAW / "rx_stdout.txt", RAW / "rx_stderr.txt")
|
||||
sleep_function(RX_LEAD_SECONDS)
|
||||
if process.poll() is not None:
|
||||
raise RuntimeError("RX завершился до TX")
|
||||
before_tx = hardware.receive_level(receive_path, tail_samples=65_536)
|
||||
before_tx.pop("symbol_energy_code2_seconds", None)
|
||||
before_tx_ac_rms = float(np.sqrt(max(0.0, before_tx["ac_power_code2"])))
|
||||
before_tx_strong = bool(
|
||||
before_tx_ac_rms >= STRONG_AC_RMS_CODE
|
||||
or before_tx["max_abs_i"] >= STRONG_COMPONENT_CODE
|
||||
or before_tx["max_abs_q"] >= STRONG_COMPONENT_CODE
|
||||
)
|
||||
before_tx.update({
|
||||
"ac_rms_code": before_tx_ac_rms,
|
||||
"unexpected_strong_signal": before_tx_strong,
|
||||
})
|
||||
result["rx_level_before_tx"] = before_tx
|
||||
if before_tx["saturation_detected"]:
|
||||
raise RuntimeError("RX clipping до TX; RF TX = 0")
|
||||
if before_tx_strong:
|
||||
raise RuntimeError("Неожиданно сильный фон непосредственно перед TX; RF TX = 0")
|
||||
guard.register_transmit(); result["rf_tx_count"] = guard.transmit_calls
|
||||
transmitted = actual_runner(transfer_command(RAW / "tx_stream.cs8", EXPECTED_TX_SAMPLES, transmit=True))
|
||||
tx_stdout, tx_stderr = transmitted.stdout or "", transmitted.stderr or ""
|
||||
(RAW / "tx_stdout.txt").write_text(tx_stdout, encoding="utf-8")
|
||||
(RAW / "tx_stderr.txt").write_text(tx_stderr, encoding="utf-8")
|
||||
result["tx_process"] = {"returncode": transmitted.returncode,
|
||||
"stdout_file": "tx_stdout.txt", "stderr_file": "tx_stderr.txt"}
|
||||
result["tx_counter_summary"] = lab064.counter_summary(tx_stdout + tx_stderr, "underrun")
|
||||
if transmitted.returncode:
|
||||
raise RuntimeError("Единственный TX завершился ошибкой; повтор запрещён")
|
||||
if process.wait(RX_SECONDS + 10):
|
||||
raise RuntimeError("RX завершился ошибкой; повтор запрещён")
|
||||
process.close_logs()
|
||||
rx_log = (RAW / "rx_stdout.txt").read_text(encoding="utf-8") + (RAW / "rx_stderr.txt").read_text(encoding="utf-8")
|
||||
result["rx_counter_summary"] = lab064.counter_summary(rx_log, "overrun")
|
||||
result["rx_samples"] = receive_path.stat().st_size // 2
|
||||
result["rx_sha256"] = sha256(receive_path)
|
||||
result["rx_level"] = hardware.receive_level(receive_path)
|
||||
result["rx_clipping"] = bool(result["rx_level"]["saturation_detected"])
|
||||
result["passed"] = result["rx_samples"] == RX_SAMPLES
|
||||
validity = lab064.capture_validity(result)
|
||||
if not validity["valid"] or not result["passed"]:
|
||||
result["passed"] = False
|
||||
raise RuntimeError("INVALID_CAPTURE; запись сохранена, повтор запрещён")
|
||||
except Exception as error:
|
||||
result["error"] = str(error)
|
||||
raise
|
||||
finally:
|
||||
errors = []
|
||||
if process is not None:
|
||||
try: process.terminate()
|
||||
except Exception as error: errors.append(str(error))
|
||||
if clock_started: errors.extend(lab054.disable_clock_outputs(actual_runner))
|
||||
try:
|
||||
after = process_checker(); result["process_after"] = after.to_dict(); lab054.require_clear(after)
|
||||
except Exception as error: errors.append(str(error))
|
||||
result["cleanup_errors"] = errors
|
||||
result["passed"] = result["passed"] and not errors
|
||||
save_new(RAW / "capture_result.json", result)
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUTPUT / "capture_result.json").write_text(json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False) + "\n", encoding="utf-8")
|
||||
if not result["passed"]:
|
||||
raise RuntimeError("Lab066 RF-сеанс невалиден; повтор запрещён")
|
||||
return result
|
||||
|
||||
|
||||
def locate_first_packet(tx: lab058.Cs8File, rx: lab058.Cs8File) -> dict:
|
||||
"""Найти первый пакет после строгого verdict, не раскрывая границы декодеру."""
|
||||
stride = MODE.samples_per_symbol
|
||||
reference = tx.read(GUARD_SAMPLES, PACKET_SAMPLES).astype(np.complex64)
|
||||
received = rx.read(0, rx.size).astype(np.complex64)
|
||||
reference = reference[::stride] - np.mean(reference[::stride])
|
||||
received = received[::stride] - np.mean(received[::stride])
|
||||
correlation = signal.correlate(received, reference, mode="valid", method="fft")
|
||||
coarse = int(np.argmax(abs(correlation))) * stride
|
||||
full_reference = tx.read(GUARD_SAMPLES, PACKET_SAMPLES).astype(np.complex128)
|
||||
full_reference -= np.mean(full_reference)
|
||||
starts = range(max(0, coarse - 2 * stride), min(rx.size - PACKET_SAMPLES, coarse + 2 * stride) + 1)
|
||||
scores = []
|
||||
for start in starts:
|
||||
values = rx.read(start, PACKET_SAMPLES).astype(np.complex128)
|
||||
values -= np.mean(values)
|
||||
scores.append(abs(np.vdot(full_reference, values)))
|
||||
first_start = starts.start + int(np.argmax(scores))
|
||||
return {
|
||||
"first_packet_rx_start_sample": first_start,
|
||||
"stream_rx_start_sample": first_start - GUARD_SAMPLES,
|
||||
"coarse_first_packet_start_sample": coarse,
|
||||
"method": "first known packet correlation after strict receiver verdict",
|
||||
}
|
||||
|
||||
|
||||
def physical_packet_diagnostics(tx: lab058.Cs8File, rx: lab058.Cs8File,
|
||||
location: dict) -> dict:
|
||||
"""Оценить 16 пакетов известным TX только после строгого решения."""
|
||||
rows = []
|
||||
stream_start = location["stream_rx_start_sample"]
|
||||
for layout, frame in zip(validate_prepared()["layout"], plans()):
|
||||
packet_index = layout["packet_index"]
|
||||
tx_start = layout["start_sample"]
|
||||
rx_start = stream_start + tx_start
|
||||
transmitted = tx.read(tx_start, PACKET_SAMPLES)
|
||||
received = rx.read(rx_start, PACKET_SAMPLES)
|
||||
expected = 2.0 * frame.plan.frame_bits.astype(np.float64) - 1.0
|
||||
metrics, _working = lab058.analyze_values(received, expected, MODE.symbol_rate)
|
||||
channel, _arrays = lab058.channel_metrics(
|
||||
transmitted, received, MODE.symbol_rate, metrics["oracle"]["cfo_hz"]
|
||||
)
|
||||
oracle = metrics["oracle"]["metrics"]
|
||||
rows.append({
|
||||
"packet_index": packet_index, "tx_start_sample": tx_start,
|
||||
"rx_start_sample": rx_start,
|
||||
"symbol_error_rate": oracle["errors"] / oracle["symbols"],
|
||||
"symbol_rms_deviation": float(np.sqrt(
|
||||
oracle["residual_power"] / oracle["matched_component_power"]
|
||||
)),
|
||||
"matched_component_power": oracle["matched_component_power"],
|
||||
"residual_power": oracle["residual_power"],
|
||||
"matched_residual_db": float(10 * np.log10(
|
||||
oracle["matched_component_power"] / oracle["residual_power"]
|
||||
)),
|
||||
"band_gain_nonuniformity_p95_p05_db": channel["amplitude_ripple_p95_p05_db"],
|
||||
})
|
||||
return {
|
||||
"method": "Lab058 oracle diagnostics per packet after strict verdict",
|
||||
"rows": rows,
|
||||
"comparison": {
|
||||
"lab056_B_oracle_symbol_error_rate": 0.03495896,
|
||||
"lab056_B_oracle_evm_rms": 0.845350,
|
||||
"lab062_source": "data/processed/lab062/rf_analysis.json",
|
||||
"scope": "Lab056 B and Lab062 are contextual comparisons, not identical estimands",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def summarize_analysis(result: dict) -> dict:
|
||||
"""Свести строгие и физические результаты без повторного декодирования."""
|
||||
legacy, t2 = result["run_a_legacy"], result["run_b_t2"]
|
||||
legacy_set, t2_set = set(legacy["packet_indexes"]), set(t2["packet_indexes"])
|
||||
layout = validate_prepared()["layout"]
|
||||
stream_start = result["location_after_strict_runs"]["stream_rx_start_sample"]
|
||||
packet_rows = []
|
||||
for layout_row in layout:
|
||||
index = layout_row["packet_index"]
|
||||
nominal = stream_start + layout_row["start_sample"]
|
||||
modes = {}
|
||||
for label, strict, accepted in (
|
||||
("legacy", legacy, index in legacy_set), ("t2", t2, index in t2_set)
|
||||
):
|
||||
candidates = [
|
||||
row for row in strict["rows"]
|
||||
if abs(row["absolute_refined"] - nominal) <= 64
|
||||
]
|
||||
best = max(candidates, key=lambda row: (
|
||||
row["packet_output"], row["crc"], row["fec"], row["header"],
|
||||
-abs(row["absolute_refined"] - nominal),
|
||||
)) if candidates else None
|
||||
modes[label] = {
|
||||
"candidate_at_expected_packet": best is not None,
|
||||
"header": accepted or bool(best and best["header"]),
|
||||
"fec": accepted or bool(best and best["fec"]),
|
||||
"crc": accepted or bool(best and best["crc"]),
|
||||
"packet_output": accepted,
|
||||
}
|
||||
packet_rows.append({"packet_index": index, **modes})
|
||||
|
||||
physical_rows = result["physical_diagnostics"]["rows"]
|
||||
matched = float(np.mean([row["matched_component_power"] for row in physical_rows]))
|
||||
residual = float(np.mean([row["residual_power"] for row in physical_rows]))
|
||||
physical = {
|
||||
"symbol_error_rate": float(np.mean([row["symbol_error_rate"] for row in physical_rows])),
|
||||
"rms_symbol_deviation": float(np.sqrt(np.mean([
|
||||
row["symbol_rms_deviation"] ** 2 for row in physical_rows
|
||||
]))),
|
||||
"matched_component_power": matched,
|
||||
"residual_power": residual,
|
||||
"matched_residual_db": float(10 * np.log10(matched / residual)),
|
||||
"band_gain_nonuniformity_p95_p05_db_median": float(np.median([
|
||||
row["band_gain_nonuniformity_p95_p05_db"] for row in physical_rows
|
||||
])),
|
||||
"band_gain_nonuniformity_p95_p05_db_min": float(np.min([
|
||||
row["band_gain_nonuniformity_p95_p05_db"] for row in physical_rows
|
||||
])),
|
||||
"band_gain_nonuniformity_p95_p05_db_max": float(np.max([
|
||||
row["band_gain_nonuniformity_p95_p05_db"] for row in physical_rows
|
||||
])),
|
||||
}
|
||||
lab056 = {
|
||||
"symbol_error_rate": 0.03495896464646465,
|
||||
"rms_symbol_deviation": 0.8453497751219741,
|
||||
"matched_residual_db": 1.4648455366051558,
|
||||
"band_gain_nonuniformity_db": 5.471381501203027,
|
||||
}
|
||||
lab062_result = json.loads(
|
||||
(ROOT / "data/processed/lab062/rf_analysis.json").read_text(encoding="utf-8")
|
||||
)["lab058_quality"]
|
||||
lab062 = {
|
||||
"symbol_error_rate": lab062_result["symbol_error_rate"],
|
||||
"rms_symbol_deviation": lab062_result["evm_rms"],
|
||||
"matched_residual_db": lab062_result["matched_residual_db"],
|
||||
"band_gain_nonuniformity_db": lab062_result[
|
||||
"band_gain_nonuniformity_p95_p05_db_median"
|
||||
],
|
||||
"capture_status": "RF_SESSION_INVALID_TX_UNDERRUN",
|
||||
}
|
||||
|
||||
def differences(reference: dict) -> dict:
|
||||
return {
|
||||
"symbol_error_rate_absolute": (
|
||||
physical["symbol_error_rate"] - reference["symbol_error_rate"]
|
||||
),
|
||||
"rms_symbol_deviation_absolute": (
|
||||
physical["rms_symbol_deviation"] - reference["rms_symbol_deviation"]
|
||||
),
|
||||
"matched_residual_db": (
|
||||
physical["matched_residual_db"] - reference["matched_residual_db"]
|
||||
),
|
||||
"band_gain_nonuniformity_db": (
|
||||
physical["band_gain_nonuniformity_p95_p05_db_median"]
|
||||
- reference["band_gain_nonuniformity_db"]
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
"lab": "Lab066", "capture_valid": result["capture_valid"],
|
||||
"legacy": {key: legacy[key] for key in (
|
||||
"candidates", "header_pass", "fec_started", "fec_pass", "crc_started",
|
||||
"crc_pass", "packet_output", "exact_payload_match", "packet_indexes",
|
||||
)},
|
||||
"t2": {key: t2[key] for key in (
|
||||
"candidates", "header_pass", "fec_started", "fec_pass", "crc_started",
|
||||
"crc_pass", "packet_output", "exact_payload_match", "packet_indexes",
|
||||
)},
|
||||
"passed_by_both": sorted(legacy_set & t2_set),
|
||||
"passed_only_by_legacy": sorted(legacy_set - t2_set),
|
||||
"passed_only_by_t2": sorted(t2_set - legacy_set),
|
||||
"failed_by_both": sorted(set(range(1, PACKETS + 1)) - legacy_set - t2_set),
|
||||
"packet_rows": packet_rows, "physical": physical,
|
||||
"lab056_B": lab056, "difference_from_lab056_B": differences(lab056),
|
||||
"lab062": lab062, "difference_from_lab062": differences(lab062),
|
||||
"classification": "PARTIAL_PACKET_RECOVERY_NO_T2_GAIN",
|
||||
"primary_result": (
|
||||
"Аппаратно валидный Lab066 дал одинаковые 10/16 точных пакетов "
|
||||
"в legacy и T2; физическое качество лучше Lab056 B и невалидной Lab062."
|
||||
),
|
||||
"recommended_next_single_step": (
|
||||
"Отдельно разрешить офлайн-сравнение header-ошибок пакетов "
|
||||
"1-3, 9, 15-16 с принятыми пакетами по сохранённому RX; новый TX не нужен."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def write_summary_outputs(result: dict) -> dict:
|
||||
summary = summarize_analysis(result)
|
||||
save_new(OUTPUT / "rf_summary.json", summary)
|
||||
with (OUTPUT / "packet_comparison.csv").open("x", newline="", encoding="utf-8-sig") as stream:
|
||||
writer = csv.writer(stream)
|
||||
writer.writerow([
|
||||
"packet_index", "legacy_header", "legacy_fec", "legacy_crc",
|
||||
"legacy_packet_output", "t2_header", "t2_fec", "t2_crc", "t2_packet_output",
|
||||
])
|
||||
for row in summary["packet_rows"]:
|
||||
writer.writerow([
|
||||
row["packet_index"], row["legacy"]["header"], row["legacy"]["fec"],
|
||||
row["legacy"]["crc"], row["legacy"]["packet_output"],
|
||||
row["t2"]["header"], row["t2"]["fec"], row["t2"]["crc"],
|
||||
row["t2"]["packet_output"],
|
||||
])
|
||||
return summary
|
||||
|
||||
|
||||
def analyze_saved_capture() -> dict:
|
||||
"""Строго обработать одну будущую RX-запись обоими режимами."""
|
||||
metadata = validate_prepared()
|
||||
capture = json.loads((RAW / "capture_result.json").read_text(encoding="utf-8"))
|
||||
rx_path = RAW / "rx_stream.cs8"
|
||||
if (
|
||||
capture.get("lab") != "Lab066"
|
||||
or capture.get("rf_tx_count") != 1
|
||||
or capture.get("automatic_tx_repeats") != 0
|
||||
or capture.get("point") != asdict(POINT)
|
||||
or capture.get("parameters") != parameters()
|
||||
or capture.get("tx_sha256") != metadata["tx_sha256"]
|
||||
or capture.get("rx_sha256") != sha256(rx_path)
|
||||
):
|
||||
raise RuntimeError("Нет подтверждённой единственной RX-записи Lab066")
|
||||
validity = lab064.capture_validity(capture)
|
||||
if not validity["valid"]:
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "STRICT_DUAL_OFFLINE_ANALYSIS",
|
||||
"classification": "INVALID_CAPTURE", "capture_valid": False,
|
||||
"capture_invalid_reasons": validity["invalid_reasons"],
|
||||
"rf_tx_count": capture.get("rf_tx_count"), "automatic_tx_repeats": 0,
|
||||
}
|
||||
save_new(OUTPUT / "rf_result.json", result)
|
||||
return result
|
||||
legacy = decode("legacy", rx_path, diagnostics=True)
|
||||
t2 = decode("t2", rx_path, diagnostics=True)
|
||||
strict_runs_complete = (
|
||||
legacy.get("candidate_overflow") == 0
|
||||
and t2.get("candidate_overflow") == 0
|
||||
and isinstance(legacy.get("rows"), list)
|
||||
and isinstance(t2.get("rows"), list)
|
||||
)
|
||||
if not strict_runs_complete:
|
||||
raise RuntimeError("Строгие legacy/T2 прогоны не завершены; oracle-диагностика запрещена")
|
||||
tx = lab058.Cs8File(RAW / "tx_stream.cs8")
|
||||
rx = lab058.Cs8File(rx_path)
|
||||
location = locate_first_packet(tx, rx)
|
||||
result = {
|
||||
"lab": "Lab066", "stage": "STRICT_DUAL_OFFLINE_ANALYSIS",
|
||||
"tx_sha256": metadata["tx_sha256"], "rx_sha256": capture["rx_sha256"],
|
||||
"capture_valid": validity["valid"], "capture_invalid_reasons": validity["invalid_reasons"],
|
||||
"run_a_legacy": legacy, "run_b_t2": t2,
|
||||
"same_rx_capture": True, "fallback_between_modes": False,
|
||||
"strict_runs_complete": True,
|
||||
"location_after_strict_runs": location,
|
||||
"physical_diagnostics_scope": "KNOWN_TX_ONLY_AFTER_COMPLETE_STRICT_RUNS",
|
||||
"physical_diagnostics": physical_packet_diagnostics(tx, rx, location),
|
||||
"comparison_targets": ["Lab056 B", "Lab062", "Lab066"],
|
||||
}
|
||||
save_new(OUTPUT / "rf_result.json", result)
|
||||
return result
|
||||
|
||||
|
||||
def summarize_saved_analysis() -> dict:
|
||||
return write_summary_outputs(json.loads(
|
||||
(OUTPUT / "rf_result.json").read_text(encoding="utf-8")
|
||||
))
|
||||
|
||||
|
||||
def run_functional_tests() -> None:
|
||||
assert EXPECTED_TX_SAMPLES == 3_676_096
|
||||
assert transfer_command("NUL", 100, transmit=True)[-7:-5] == ["-x", "20"]
|
||||
assert "-t" not in transfer_command("NUL", 100)
|
||||
try:
|
||||
capture_once(authorization=False)
|
||||
except PermissionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("RF без отдельного разрешения")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--stage",
|
||||
choices=("prepare", "software", "preflight", "capture", "analyze", "summarize"),
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument("--authorize-one-rf", action="store_true")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.stage == "prepare": result = prepare()
|
||||
elif arguments.stage == "software": result = software_gate()
|
||||
elif arguments.stage == "preflight": result = preflight()
|
||||
elif arguments.stage == "capture": result = capture_once(authorization=arguments.authorize_one_rf)
|
||||
elif arguments.stage == "analyze": result = analyze_saved_capture()
|
||||
else: result = summarize_saved_analysis()
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False))
|
||||
print("RF TX NOW = 0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
906
experiments/lab067_header_failure_analysis.py
Normal file
@@ -0,0 +1,906 @@
|
||||
"""Lab067: офлайн-анализ отказов PHY-заголовка в сохранённой записи Lab066.
|
||||
|
||||
Строгий результат Lab066 загружается без изменений. Известный TX используется
|
||||
только после этого результата для диагностического подсчёта ошибок.
|
||||
Аппаратные команды и передача RF в этой лаборатории отсутствуют.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
if __package__ in (None, ""):
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from experiments import lab053_packet_processing as packet_processing
|
||||
from experiments import lab058_abab_symbol_quality as symbol_quality
|
||||
from experiments import lab066_full_packet_rf_retest as lab066
|
||||
from protocol import bpsk_carrier_tracking as carrier_tracking
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RAW = ROOT / "data/raw/lab066"
|
||||
LAB066 = ROOT / "data/processed/lab066"
|
||||
OUTPUT = ROOT / "data/processed/lab067"
|
||||
TX_PATH = RAW / "tx_stream.cs8"
|
||||
RX_PATH = RAW / "rx_stream.cs8"
|
||||
TX_SHA256 = "621b0bb8ec72bba4f522c79a5c77669c850740ea481270c4dcc2a7317819f199"
|
||||
RX_SHA256 = "72ff7ddd8b30baa4a5c066361ca1f8dfa664f1da37f65c0c373b211ce6cf00f0"
|
||||
RX_BYTES = 32_000_000
|
||||
RX_SAMPLES = 16_000_000
|
||||
SAMPLE_RATE_HZ = 8_000_000
|
||||
SYMBOL_RATE = 500_000
|
||||
SAMPLES_PER_SYMBOL = 16
|
||||
PREAMBLE_SYMBOLS = 1280
|
||||
HEADER_SYMBOLS = 144
|
||||
HEADER_BITS = 48
|
||||
HEADER_REPETITION = 3
|
||||
POSTHEADER_SYMBOLS = 128
|
||||
FAILED_PACKETS = {1, 2, 3, 9, 15, 16}
|
||||
PASSED_PACKETS = set(range(1, 17)) - FAILED_PACKETS
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def save_json(path: Path, value: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(
|
||||
json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def bits_text(bits: np.ndarray) -> str:
|
||||
return "".join(str(int(value)) for value in np.asarray(bits).reshape(-1))
|
||||
|
||||
|
||||
def finite(value: float) -> float | None:
|
||||
return float(value) if np.isfinite(value) else None
|
||||
|
||||
|
||||
def source_integrity() -> tuple[dict, dict, dict]:
|
||||
"""Проверить неизменность Lab066 до любой oracle-диагностики."""
|
||||
capture = json.loads((LAB066 / "capture_result.json").read_text(encoding="utf-8"))
|
||||
strict = json.loads((LAB066 / "rf_result.json").read_text(encoding="utf-8"))
|
||||
metadata = json.loads((LAB066 / "preparation.json").read_text(encoding="utf-8"))
|
||||
tx_digest = sha256(TX_PATH)
|
||||
rx_digest = sha256(RX_PATH)
|
||||
integrity = {
|
||||
"tx_path": str(TX_PATH.relative_to(ROOT)),
|
||||
"tx_bytes": TX_PATH.stat().st_size,
|
||||
"tx_samples": TX_PATH.stat().st_size // 2,
|
||||
"tx_sha256": tx_digest,
|
||||
"rx_path": str(RX_PATH.relative_to(ROOT)),
|
||||
"rx_bytes": RX_PATH.stat().st_size,
|
||||
"rx_samples": RX_PATH.stat().st_size // 2,
|
||||
"rx_duration_seconds": (RX_PATH.stat().st_size // 2) / SAMPLE_RATE_HZ,
|
||||
"rx_sha256": rx_digest,
|
||||
"sample_rate_hz": SAMPLE_RATE_HZ,
|
||||
}
|
||||
if (
|
||||
tx_digest != TX_SHA256
|
||||
or rx_digest != RX_SHA256
|
||||
or integrity["rx_bytes"] != RX_BYTES
|
||||
or integrity["rx_samples"] != RX_SAMPLES
|
||||
or metadata.get("tx_sha256") != TX_SHA256
|
||||
or metadata.get("parameters", {}).get("sample_rate_hz") != SAMPLE_RATE_HZ
|
||||
or capture.get("rx_sha256") != RX_SHA256
|
||||
or capture.get("rf_tx_count") != 1
|
||||
or capture.get("automatic_tx_repeats") != 0
|
||||
or capture.get("passed") is not True
|
||||
or strict.get("capture_valid") is not True
|
||||
):
|
||||
raise RuntimeError("Исходники или аппаратная валидность Lab066 не подтверждены")
|
||||
for mode in ("run_a_legacy", "run_b_t2"):
|
||||
result = strict[mode]
|
||||
if (
|
||||
result.get("header_pass") != 10
|
||||
or result.get("packet_output") != 10
|
||||
or result.get("exact_payload_match") != 10
|
||||
or result.get("packet_indexes") != sorted(PASSED_PACKETS)
|
||||
):
|
||||
raise RuntimeError("Исторический строгий результат Lab066 изменился")
|
||||
return integrity, metadata, strict
|
||||
|
||||
|
||||
def extract_symbols(
|
||||
received: symbol_quality.Cs8File,
|
||||
frame_start: int,
|
||||
symbol_count: int,
|
||||
mode: str,
|
||||
) -> dict:
|
||||
"""Повторить штатные timing/carrier операции для известного окна пакета."""
|
||||
processor = packet_processing.PacketProcessor(carrier_tracking_mode=mode)
|
||||
length = (symbol_count + 42) * SAMPLES_PER_SYMBOL
|
||||
segment = received.read(frame_start, length).astype(np.complex64)
|
||||
matched = processor.filter(processor.shift(segment))
|
||||
nominal = 16 * SAMPLES_PER_SYMBOL + processor.taps.size - 1
|
||||
timing = int(processor.timing(matched, nominal, processor.expected))
|
||||
initial = processor.carrier(
|
||||
matched, nominal, timing, processor.bits, len(processor.bits)
|
||||
)
|
||||
symbols = matched[
|
||||
nominal + timing:
|
||||
nominal + timing + symbol_count * SAMPLES_PER_SYMBOL:
|
||||
SAMPLES_PER_SYMBOL
|
||||
].astype(np.complex128)
|
||||
if symbols.size != symbol_count or initial["abs_h"] == 0:
|
||||
raise RuntimeError("Не удалось извлечь диагностическое окно символов")
|
||||
times = (np.arange(symbol_count, dtype=np.float64) + 0.5) / SYMBOL_RATE
|
||||
if mode == "legacy":
|
||||
corrected = symbols * np.exp(-2j * np.pi * initial["cfo_hz"] * times)
|
||||
gain = initial["abs_h"] * np.exp(1j * initial["phase_rad"])
|
||||
corrected /= gain
|
||||
phase_trace = (
|
||||
initial["phase_rad"] + 2 * np.pi * initial["cfo_hz"] * times
|
||||
)
|
||||
frequency_trace = np.full(symbol_count, initial["cfo_hz"], dtype=float)
|
||||
detector_error = np.angle(corrected * np.where(corrected.real >= 0, 1, -1))
|
||||
estimate = {
|
||||
"frequency_hz": float(initial["cfo_hz"]),
|
||||
"gain": complex(gain),
|
||||
"phase_fit_rmse_rad": None,
|
||||
"squared_coherence": None,
|
||||
}
|
||||
else:
|
||||
estimate_value = carrier_tracking.estimate_p3(
|
||||
symbols[:PREAMBLE_SYMBOLS], processor.expected, SYMBOL_RATE
|
||||
)
|
||||
tracked = carrier_tracking.track_t2(
|
||||
symbols, SYMBOL_RATE, estimate_value, start_index=PREAMBLE_SYMBOLS
|
||||
)
|
||||
corrected = tracked.corrected.copy()
|
||||
phase_trace = tracked.phase_rad.copy()
|
||||
frequency_trace = tracked.frequency_hz.copy()
|
||||
detector_error = tracked.detector_error_rad.copy()
|
||||
estimate = {
|
||||
"frequency_hz": float(estimate_value.frequency_hz),
|
||||
"gain": complex(estimate_value.gain),
|
||||
"phase_fit_rmse_rad": float(estimate_value.phase_fit_rmse_rad),
|
||||
"squared_coherence": float(estimate_value.squared_coherence),
|
||||
}
|
||||
return {
|
||||
"corrected": corrected,
|
||||
"raw_symbols": symbols,
|
||||
"matched": matched,
|
||||
"hard_bits": (corrected.real >= 0).astype(np.uint8),
|
||||
"timing_samples": timing,
|
||||
"timing_fraction": timing / SAMPLES_PER_SYMBOL,
|
||||
"nominal": nominal,
|
||||
"sample_positions": frame_start + nominal + timing + np.arange(symbol_count) * SAMPLES_PER_SYMBOL,
|
||||
"initial": estimate,
|
||||
"phase_trace": phase_trace,
|
||||
"frequency_trace": frequency_trace,
|
||||
"detector_error": detector_error,
|
||||
}
|
||||
|
||||
|
||||
def reference_sync_diagnostic(extracted: dict, expected_bits: np.ndarray) -> dict:
|
||||
"""Oracle-проверка timing/CFO после штатного результата, без влияния на него."""
|
||||
expected = 2.0 * np.asarray(expected_bits, dtype=np.float64) - 1.0
|
||||
indexes = np.arange(len(expected), dtype=np.int64)
|
||||
candidates = []
|
||||
for offset in range(-SAMPLES_PER_SYMBOL // 2, SAMPLES_PER_SYMBOL // 2 + 1):
|
||||
start = extracted["nominal"] + offset
|
||||
symbols = extracted["matched"][
|
||||
start:start + len(expected) * SAMPLES_PER_SYMBOL:SAMPLES_PER_SYMBOL
|
||||
].astype(np.complex128)
|
||||
if len(symbols) != len(expected):
|
||||
continue
|
||||
frequency, gain, coherence = symbol_quality.fit_cfo_gain(
|
||||
symbols, expected, indexes, SYMBOL_RATE, len(expected)
|
||||
)
|
||||
candidates.append((coherence, offset, frequency, gain))
|
||||
coherence, timing, frequency, gain = max(candidates, key=lambda item: item[0])
|
||||
return {
|
||||
"scope": "KNOWN_BITS_AFTER_STRICT_VERDICT",
|
||||
"timing_offset_samples": int(timing),
|
||||
"timing_fraction": timing / SAMPLES_PER_SYMBOL,
|
||||
"frequency_hz": float(frequency),
|
||||
"gain_real": float(gain.real),
|
||||
"gain_imag": float(gain.imag),
|
||||
"coherence": float(coherence),
|
||||
}
|
||||
|
||||
|
||||
def physical_metrics(extracted: dict, expected_bits: np.ndarray, selected: slice) -> dict:
|
||||
expected = 2.0 * np.asarray(expected_bits[selected], dtype=np.float64) - 1.0
|
||||
raw = extracted["raw_symbols"][selected]
|
||||
indexes = np.arange(len(extracted["raw_symbols"]), dtype=np.float64)[selected]
|
||||
frequency = extracted["initial"]["frequency_hz"]
|
||||
times = (indexes + 0.5) / SYMBOL_RATE
|
||||
derotated = raw * np.exp(-2j * np.pi * frequency * times)
|
||||
gain = complex(extracted["initial"]["gain"])
|
||||
predicted = gain * expected
|
||||
residual = derotated - predicted
|
||||
matched_power = float(np.mean(abs(predicted) ** 2))
|
||||
residual_power = float(np.mean(abs(residual) ** 2))
|
||||
normalized = derotated / gain
|
||||
errors = int(np.count_nonzero((normalized.real >= 0) != (expected > 0)))
|
||||
correlation = float(
|
||||
abs(np.vdot(expected, raw))
|
||||
/ math.sqrt(float(np.vdot(expected, expected).real * np.vdot(raw, raw).real) + 1e-30)
|
||||
)
|
||||
return {
|
||||
"symbols": int(len(expected)),
|
||||
"errors": errors,
|
||||
"error_rate": errors / len(expected),
|
||||
"correlation": correlation,
|
||||
"matched_component_power": matched_power,
|
||||
"residual_power": residual_power,
|
||||
"matched_residual_db": finite(10 * np.log10(matched_power / residual_power)),
|
||||
"rms_symbol_deviation": float(np.sqrt(residual_power / matched_power)),
|
||||
}
|
||||
|
||||
|
||||
def header_diagnostics(received_soft: np.ndarray, expected_bits: np.ndarray) -> dict:
|
||||
soft = np.asarray(received_soft[:HEADER_SYMBOLS], dtype=np.float64)
|
||||
raw = (soft >= 0.0).astype(np.uint8)
|
||||
expected = np.asarray(expected_bits[:HEADER_SYMBOLS], dtype=np.uint8)
|
||||
raw_groups = raw.reshape(HEADER_BITS, HEADER_REPETITION)
|
||||
expected_groups = expected.reshape(HEADER_BITS, HEADER_REPETITION)
|
||||
voted = (np.sum(soft.reshape(HEADER_BITS, HEADER_REPETITION), axis=1) >= 0.0).astype(np.uint8)
|
||||
expected_voted = expected_groups[:, 0]
|
||||
fields = lab066.phy.lab045.lab044.bits_to_bytes(voted)
|
||||
magic, version, length, checksum = struct.unpack(
|
||||
lab066.phy.lab045.PHY_HEADER_FIELDS_FORMAT, fields
|
||||
)
|
||||
decoded = lab066.phy.lab045.decode_phy_header_soft(soft)
|
||||
field_errors = {
|
||||
"magic": int(np.count_nonzero(voted[:16] != expected_voted[:16])),
|
||||
"version": int(np.count_nonzero(voted[16:24] != expected_voted[16:24])),
|
||||
"length": int(np.count_nonzero(voted[24:40] != expected_voted[24:40])),
|
||||
"checksum": int(np.count_nonzero(voted[40:48] != expected_voted[40:48])),
|
||||
}
|
||||
return {
|
||||
"expected_header_bits": bits_text(expected),
|
||||
"received_raw_header_bits": bits_text(raw),
|
||||
"raw_header_errors": int(np.count_nonzero(raw != expected)),
|
||||
"copy_errors": [
|
||||
int(np.count_nonzero(raw_groups[:, index] != expected_groups[:, index]))
|
||||
for index in range(HEADER_REPETITION)
|
||||
],
|
||||
"voted_header_bits": bits_text(voted),
|
||||
"expected_voted_bits": bits_text(expected_voted),
|
||||
"voted_header_errors": int(np.count_nonzero(voted != expected_voted)),
|
||||
"magic_errors": field_errors["magic"],
|
||||
"version_errors": field_errors["version"],
|
||||
"length_errors": field_errors["length"],
|
||||
"checksum_errors": field_errors["checksum"],
|
||||
"decoded_magic": magic,
|
||||
"decoded_version": version,
|
||||
"decoded_length": length,
|
||||
"decoded_checksum": checksum,
|
||||
"accepted": bool(decoded["passed"]),
|
||||
"reason": decoded["reason"],
|
||||
"raw_error_positions": np.flatnonzero(raw != expected).astype(int).tolist(),
|
||||
"voted_error_positions": np.flatnonzero(voted != expected_voted).astype(int).tolist(),
|
||||
}
|
||||
|
||||
|
||||
def gap_metrics(received: symbol_quality.Cs8File, start: int, count: int) -> dict:
|
||||
values = received.read(start, count).astype(np.complex128)
|
||||
mean = np.mean(values)
|
||||
centered = values - mean
|
||||
edge = min(count // 4, 8000)
|
||||
first_power = float(np.mean(abs(centered[:edge]) ** 2))
|
||||
middle_start = max(edge, count // 2 - edge // 2)
|
||||
middle_power = float(np.mean(abs(centered[middle_start:middle_start + edge]) ** 2))
|
||||
tail_ratio_db = float(10 * np.log10(max(first_power, 1e-30) / max(middle_power, 1e-30)))
|
||||
return {
|
||||
"length_samples": count,
|
||||
"duration_seconds": count / SAMPLE_RATE_HZ,
|
||||
"background_ac_rms": float(np.sqrt(np.mean(abs(centered) ** 2))),
|
||||
"mean_i": float(mean.real),
|
||||
"mean_q": float(mean.imag),
|
||||
"residual_energy_mean": float(np.mean(abs(centered) ** 2)),
|
||||
"leading_edge_to_middle_db": tail_ratio_db,
|
||||
"previous_waveform_tail_detected": bool(tail_ratio_db > 3.0),
|
||||
}
|
||||
|
||||
|
||||
def strict_candidate_for_packet(rows: list[dict], expected: int) -> dict | None:
|
||||
candidates = [row for row in rows if abs(int(row["absolute_refined"]) - expected) <= 64]
|
||||
if not candidates:
|
||||
return None
|
||||
return min(
|
||||
candidates,
|
||||
key=lambda row: (
|
||||
abs(int(row["absolute_refined"]) - expected),
|
||||
int(row["absolute_refined"]),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def candidate_analysis(strict: dict, expected_starts: list[int]) -> tuple[dict, list[dict]]:
|
||||
rows = strict["run_a_legacy"]["rows"]
|
||||
packet_samples = lab066.PACKET_SAMPLES
|
||||
gap_intervals = []
|
||||
metadata = json.loads((LAB066 / "preparation.json").read_text(encoding="utf-8"))
|
||||
stream_start = strict["location_after_strict_runs"]["stream_rx_start_sample"]
|
||||
for layout in metadata["layout"][:-1]:
|
||||
low = stream_start + layout["following_gap_start_sample"]
|
||||
gap_intervals.append((low, low + layout["following_gap_samples"]))
|
||||
output_rows = []
|
||||
true = repeated = gaps = unrelated = 0
|
||||
true_positions = set()
|
||||
per_packet = {index: 0 for index in range(1, 17)}
|
||||
for number, row in enumerate(rows, start=1):
|
||||
position = int(row["absolute_refined"])
|
||||
distances = np.abs(np.asarray(expected_starts, dtype=np.int64) - position)
|
||||
nearest_index = int(np.argmin(distances)) + 1
|
||||
delta = position - expected_starts[nearest_index - 1]
|
||||
in_gap = any(low <= position < high for low, high in gap_intervals)
|
||||
if abs(delta) <= 64:
|
||||
category = "TRUE_PREAMBLE_POSITION"
|
||||
true += 1
|
||||
true_positions.add((nearest_index, position))
|
||||
elif -64 <= delta < packet_samples:
|
||||
category = "REPEATED_PRBS_LOCAL_MAXIMUM"
|
||||
repeated += 1
|
||||
elif in_gap:
|
||||
category = "ZERO_GAP"
|
||||
gaps += 1
|
||||
else:
|
||||
category = "UNRELATED"
|
||||
unrelated += 1
|
||||
if category in {"TRUE_PREAMBLE_POSITION", "REPEATED_PRBS_LOCAL_MAXIMUM"}:
|
||||
per_packet[nearest_index] += 1
|
||||
output_rows.append({
|
||||
"candidate_number": number,
|
||||
"position": position,
|
||||
"nearest_packet": nearest_index,
|
||||
"difference_samples": delta,
|
||||
"category": category,
|
||||
"strict_reason": row["reason"],
|
||||
})
|
||||
summary = {
|
||||
"total_strict_candidates": len(rows),
|
||||
"true_preamble_position_checks": true,
|
||||
"unique_true_preamble_positions": len(true_positions),
|
||||
"duplicate_true_position_checks": true - len(true_positions),
|
||||
"repeated_prbs_local_maxima": repeated,
|
||||
"zero_gap_candidates": gaps,
|
||||
"unrelated_candidates": unrelated,
|
||||
"checks_associated_with_each_real_preamble": per_packet,
|
||||
"detector_statistics": strict["run_a_legacy"]["detector_statistics"],
|
||||
"candidate_processing_state": "NEW_PACKET_MEASURE_AND_INDEPENDENT_DECODE_PER_CANDIDATE",
|
||||
"persistent_state_between_rejected_candidates": False,
|
||||
"accepted_until_changes_only_after_crc_pass": True,
|
||||
}
|
||||
return summary, output_rows
|
||||
|
||||
|
||||
def distribution(rows: list[dict], packets: set[int], key: str) -> dict:
|
||||
values = np.asarray([row[key] for row in rows if row["packet"] in packets], dtype=float)
|
||||
return {
|
||||
"minimum": float(np.min(values)),
|
||||
"median": float(np.median(values)),
|
||||
"maximum": float(np.max(values)),
|
||||
"values": {str(row["packet"]): row[key] for row in rows if row["packet"] in packets},
|
||||
}
|
||||
|
||||
|
||||
def assign_classifications(rows: list[dict]) -> None:
|
||||
passed = [row for row in rows if row["packet"] in PASSED_PACKETS]
|
||||
limits = {
|
||||
"preamble_errors_max": max(row["preamble_errors"] for row in passed),
|
||||
"preamble_mr_p10": float(np.percentile([row["preamble_matched_residual_db"] for row in passed], 10)),
|
||||
"preamble_rms_p90": float(np.percentile([row["preamble_rms_symbol_deviation"] for row in passed], 90)),
|
||||
"correlation_p10": float(np.percentile([row["preamble_correlation"] for row in passed], 10)),
|
||||
}
|
||||
for row in rows:
|
||||
if row["packet"] in PASSED_PACKETS:
|
||||
row["classification"] = "PASSED"
|
||||
continue
|
||||
physical_bad = sum((
|
||||
row["preamble_errors"] > limits["preamble_errors_max"],
|
||||
row["preamble_matched_residual_db"] < limits["preamble_mr_p10"],
|
||||
row["preamble_rms_symbol_deviation"] > limits["preamble_rms_p90"],
|
||||
row["preamble_correlation"] < limits["correlation_p10"],
|
||||
))
|
||||
if row["preamble_errors"] > limits["preamble_errors_max"]:
|
||||
row["classification"] = "PREAMBLE_ALREADY_BAD"
|
||||
elif physical_bad >= 2:
|
||||
row["classification"] = "LOCAL_RF_DEGRADATION"
|
||||
elif row["header_voted_errors"] > 0:
|
||||
row["classification"] = "HEADER_SPECIFIC_FAILURE"
|
||||
else:
|
||||
row["classification"] = "NOT_LOCALIZED"
|
||||
|
||||
|
||||
def group_summary(rows: list[dict]) -> dict:
|
||||
groups = {"START": range(1, 5), "MIDDLE_1": range(5, 9), "MIDDLE_2": range(9, 13), "END": range(13, 17)}
|
||||
result = {}
|
||||
for name, indexes in groups.items():
|
||||
selected = [row for row in rows if row["packet"] in indexes]
|
||||
result[name] = {
|
||||
"packets": list(indexes),
|
||||
"preamble_error_rate": float(np.mean([row["preamble_error_rate"] for row in selected])),
|
||||
"header_error_rate": float(np.mean([row["header_raw_errors"] / HEADER_SYMBOLS for row in selected])),
|
||||
"matched_power": float(np.mean([row["local_matched_power"] for row in selected])),
|
||||
"residual_power": float(np.mean([row["local_residual_power"] for row in selected])),
|
||||
"matched_residual_db": float(10 * np.log10(
|
||||
np.mean([row["local_matched_power"] for row in selected])
|
||||
/ np.mean([row["local_residual_power"] for row in selected])
|
||||
)),
|
||||
"initial_frequency_offset_hz": float(np.mean([row["legacy_initial_frequency_hz"] for row in selected])),
|
||||
"timing_offset_samples": float(np.mean([row["timing_offset_samples"] for row in selected])),
|
||||
"background_ac_rms": float(np.mean([row["gap_background_ac_rms"] for row in selected])),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def make_plots(rows: list[dict], transitions: dict[int, dict], candidates: dict) -> list[str]:
|
||||
OUTPUT.mkdir(parents=True, exist_ok=True)
|
||||
packets = [row["packet"] for row in rows]
|
||||
colors = ["C3" if packet in FAILED_PACKETS else "C0" for packet in packets]
|
||||
plots = []
|
||||
|
||||
def save(name: str) -> None:
|
||||
plt.tight_layout()
|
||||
plt.savefig(OUTPUT / name, dpi=160)
|
||||
plt.close()
|
||||
plots.append(name)
|
||||
|
||||
plt.figure(figsize=(10, 4.5))
|
||||
plt.bar(packets, [row["preamble_errors"] for row in rows], color=colors)
|
||||
plt.xticks(packets); plt.xlabel("Пакет"); plt.ylabel("Ошибки преамбулы, бит")
|
||||
plt.grid(axis="y", alpha=.25); save("preamble_errors_by_packet.png")
|
||||
|
||||
plt.figure(figsize=(10, 4.5))
|
||||
x = np.arange(1, 17)
|
||||
plt.bar(x - .18, [row["header_raw_errors"] for row in rows], width=.36, label="raw")
|
||||
plt.bar(x + .18, [row["header_voted_errors"] for row in rows], width=.36, label="voted", color=colors)
|
||||
plt.xticks(x); plt.xlabel("Пакет"); plt.ylabel("Ошибки PHY header, бит"); plt.legend()
|
||||
plt.grid(axis="y", alpha=.25); save("header_errors_by_packet.png")
|
||||
|
||||
figure, axes = plt.subplots(3, 1, figsize=(10, 8), sharex=True)
|
||||
axes[0].bar(packets, [row["local_matched_power"] for row in rows], color=colors)
|
||||
axes[0].set_ylabel("Matched power")
|
||||
axes[1].bar(packets, [row["local_residual_power"] for row in rows], color=colors)
|
||||
axes[1].set_ylabel("Residual power")
|
||||
axes[2].bar(packets, [row["local_matched_residual_db"] for row in rows], color=colors)
|
||||
axes[2].set_ylabel("Matched/residual, dB"); axes[2].set_xlabel("Пакет"); axes[2].set_xticks(packets)
|
||||
for axis in axes: axis.grid(axis="y", alpha=.25)
|
||||
save("local_signal_quality_by_packet.png")
|
||||
|
||||
matrix = np.zeros((16, HEADER_SYMBOLS), dtype=np.uint8)
|
||||
for row in rows:
|
||||
for position in row["header_raw_error_positions"]:
|
||||
matrix[row["packet"] - 1, position] = 1
|
||||
plt.figure(figsize=(13, 5))
|
||||
plt.imshow(matrix, aspect="auto", interpolation="nearest", cmap="Reds")
|
||||
plt.xlabel("Позиция защищённого PHY header, 0...143"); plt.ylabel("Пакет")
|
||||
plt.yticks(np.arange(16), np.arange(1, 17)); plt.colorbar(label="Ошибка")
|
||||
save("header_error_position_map.png")
|
||||
|
||||
representative = [4, 1, 14, 16]
|
||||
figure, axes = plt.subplots(len(representative), 3, figsize=(13, 10), sharex=True)
|
||||
for line, packet in enumerate(representative):
|
||||
values = transitions[packet]
|
||||
x = np.arange(-128, HEADER_SYMBOLS + 128)
|
||||
axes[line, 0].plot(x, values["corrected_real"], lw=.7)
|
||||
axes[line, 0].scatter(x[values["errors"]], np.asarray(values["corrected_real"])[values["errors"]], s=12, color="red")
|
||||
axes[line, 1].plot(x, values["corrected_imag"], lw=.7)
|
||||
axes[line, 2].plot(x, values["phase_error_rad"], lw=.7)
|
||||
for axis in axes[line]:
|
||||
axis.axvline(0, color="black", lw=.8); axis.axvline(HEADER_SYMBOLS, color="black", lw=.8)
|
||||
axis.grid(alpha=.2)
|
||||
axes[line, 0].set_ylabel(f"P{packet}")
|
||||
axes[0, 0].set_title("Re, ошибки красным"); axes[0, 1].set_title("Im"); axes[0, 2].set_title("Фазовая ошибка, rad")
|
||||
for axis in axes[-1]: axis.set_xlabel("Символ относительно начала header")
|
||||
save("preamble_header_transition.png")
|
||||
|
||||
figure, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
|
||||
axes[0].bar(packets, [row["timing_offset_samples"] for row in rows], color=colors)
|
||||
axes[0].set_ylabel("Timing, samples")
|
||||
axes[1].plot(packets, [row["legacy_initial_frequency_hz"] for row in rows], "o-", label="legacy")
|
||||
axes[1].plot(packets, [row["t2_initial_frequency_hz"] for row in rows], "s-", label="T2/P3")
|
||||
axes[1].set_ylabel("CFO, Hz"); axes[1].set_xlabel("Пакет"); axes[1].legend()
|
||||
for axis in axes: axis.grid(alpha=.25)
|
||||
save("timing_and_cfo_by_packet.png")
|
||||
|
||||
plt.figure(figsize=(10, 4.5))
|
||||
plt.bar(packets, [row["gap_background_ac_rms"] for row in rows], color=colors)
|
||||
plt.xlabel("Пакет"); plt.ylabel("AC RMS перед пакетом"); plt.xticks(packets); plt.grid(axis="y", alpha=.25)
|
||||
save("gap_background_by_packet.png")
|
||||
|
||||
per_packet = candidates["checks_associated_with_each_real_preamble"]
|
||||
plt.figure(figsize=(10, 4.5))
|
||||
plt.bar(packets, [per_packet[str(packet)] if str(packet) in per_packet else per_packet[packet] for packet in packets], color=colors)
|
||||
plt.xlabel("Пакет"); plt.ylabel("Строгие проверки кандидатов"); plt.xticks(packets); plt.grid(axis="y", alpha=.25)
|
||||
save("candidate_checks_by_packet.png")
|
||||
return plots
|
||||
|
||||
|
||||
def write_csvs(rows: list[dict], candidate_rows: list[dict], bit_rows: list[dict], transition_rows: list[dict]) -> None:
|
||||
main_fields = [
|
||||
"packet", "strict_result", "candidate_position", "expected_packet_position", "candidate_difference_samples",
|
||||
"preamble_errors", "preamble_error_rate", "preamble_correlation", "header_raw_errors", "header_voted_errors",
|
||||
"header_copy_1_errors", "header_copy_2_errors", "header_copy_3_errors", "magic_errors", "version_errors",
|
||||
"length_errors", "checksum_errors", "expected_header_bits", "received_raw_header_bits", "voted_header_bits",
|
||||
"header_accepted", "header_reason", "legacy_header_bits", "t2_header_bits",
|
||||
"legacy_initial_frequency_hz", "t2_initial_frequency_hz", "reference_frequency_hz",
|
||||
"legacy_frequency_error_vs_reference_hz", "initial_phase_error_rad", "timing_offset_samples", "fractional_timing_offset",
|
||||
"reference_timing_offset_samples", "reference_fractional_timing_offset", "local_matched_power", "local_residual_power",
|
||||
"local_matched_residual_db", "rms_symbol_deviation", "legacy_t2_header_differences", "classification",
|
||||
]
|
||||
with (OUTPUT / "header_packet_comparison.csv").open("w", newline="", encoding="utf-8-sig") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=main_fields, extrasaction="ignore")
|
||||
writer.writeheader(); writer.writerows(rows)
|
||||
for name, values in (
|
||||
("header_bit_errors.csv", bit_rows),
|
||||
("candidate_analysis.csv", candidate_rows),
|
||||
("transition_metrics.csv", transition_rows),
|
||||
):
|
||||
with (OUTPUT / name).open("w", newline="", encoding="utf-8-sig") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=list(values[0]))
|
||||
writer.writeheader(); writer.writerows(values)
|
||||
|
||||
|
||||
def analyze() -> dict:
|
||||
integrity, metadata, strict = source_integrity()
|
||||
received = symbol_quality.Cs8File(RX_PATH)
|
||||
stream_start = int(strict["location_after_strict_runs"]["stream_rx_start_sample"])
|
||||
expected_starts = [stream_start + int(item["start_sample"]) for item in metadata["layout"]]
|
||||
plans = lab066.plans()
|
||||
packet_rows: list[dict] = []
|
||||
bit_rows: list[dict] = []
|
||||
transition_rows: list[dict] = []
|
||||
transitions: dict[int, dict] = {}
|
||||
tx_headers = []
|
||||
|
||||
for packet, (layout, frame, rx_start) in enumerate(zip(metadata["layout"], plans, expected_starts), start=1):
|
||||
expected_frame = frame.plan.frame_bits.astype(np.uint8)
|
||||
expected_header = frame.plan.phy_header_bits.astype(np.uint8)
|
||||
tx_headers.append(expected_header)
|
||||
legacy = extract_symbols(received, rx_start, PREAMBLE_SYMBOLS + HEADER_SYMBOLS, "legacy")
|
||||
t2 = extract_symbols(received, rx_start, PREAMBLE_SYMBOLS + HEADER_SYMBOLS, "t2")
|
||||
transition = extract_symbols(
|
||||
received, rx_start, PREAMBLE_SYMBOLS + HEADER_SYMBOLS + POSTHEADER_SYMBOLS, "legacy"
|
||||
)
|
||||
preamble = physical_metrics(legacy, expected_frame, slice(0, PREAMBLE_SYMBOLS))
|
||||
local = physical_metrics(legacy, expected_frame, slice(PREAMBLE_SYMBOLS - 128, PREAMBLE_SYMBOLS + HEADER_SYMBOLS))
|
||||
reference = reference_sync_diagnostic(
|
||||
legacy, expected_frame[:PREAMBLE_SYMBOLS + HEADER_SYMBOLS]
|
||||
)
|
||||
header_legacy = header_diagnostics(
|
||||
legacy["corrected"].real[PREAMBLE_SYMBOLS:], expected_header
|
||||
)
|
||||
header_t2 = header_diagnostics(
|
||||
t2["corrected"].real[PREAMBLE_SYMBOLS:], expected_header
|
||||
)
|
||||
legacy_t2_differences = int(np.count_nonzero(
|
||||
legacy["hard_bits"][PREAMBLE_SYMBOLS:] != t2["hard_bits"][PREAMBLE_SYMBOLS:]
|
||||
))
|
||||
strict_row = strict_candidate_for_packet(strict["run_a_legacy"]["rows"], rx_start)
|
||||
candidate_position = int(strict_row["absolute_refined"]) if strict_row else None
|
||||
if packet == 1:
|
||||
preceding_start = stream_start
|
||||
preceding_count = lab066.GUARD_SAMPLES
|
||||
else:
|
||||
preceding_count = lab066.INTER_PACKET_GAP_SAMPLES
|
||||
preceding_start = rx_start - preceding_count
|
||||
gap = gap_metrics(received, preceding_start, preceding_count)
|
||||
trailing = gap_metrics(received, rx_start + lab066.PACKET_SAMPLES, lab066.GUARD_SAMPLES) if packet == 16 else None
|
||||
row = {
|
||||
"packet": packet,
|
||||
"strict_result": "PASS" if packet in PASSED_PACKETS else "HEADER_FAIL",
|
||||
"candidate_position": candidate_position,
|
||||
"expected_packet_position": rx_start,
|
||||
"candidate_difference_samples": None if candidate_position is None else candidate_position - rx_start,
|
||||
"timing_offset_samples": legacy["timing_samples"],
|
||||
"fractional_timing_offset": legacy["timing_fraction"],
|
||||
"timing_metric_confidence": None,
|
||||
"preamble_symbols": preamble["symbols"],
|
||||
"preamble_errors": preamble["errors"],
|
||||
"preamble_error_rate": preamble["error_rate"],
|
||||
"preamble_correlation": preamble["correlation"],
|
||||
"preamble_rms_symbol_deviation": preamble["rms_symbol_deviation"],
|
||||
"preamble_matched_power": preamble["matched_component_power"],
|
||||
"preamble_residual_power": preamble["residual_power"],
|
||||
"preamble_matched_residual_db": preamble["matched_residual_db"],
|
||||
"legacy_initial_frequency_hz": legacy["initial"]["frequency_hz"],
|
||||
"t2_initial_frequency_hz": t2["initial"]["frequency_hz"],
|
||||
"initial_phase_error_rad": float(np.angle(legacy["initial"]["gain"])),
|
||||
"frequency_difference_legacy_t2_hz": legacy["initial"]["frequency_hz"] - t2["initial"]["frequency_hz"],
|
||||
"reference_frequency_hz": reference["frequency_hz"],
|
||||
"legacy_frequency_error_vs_reference_hz": legacy["initial"]["frequency_hz"] - reference["frequency_hz"],
|
||||
"reference_timing_offset_samples": reference["timing_offset_samples"],
|
||||
"reference_fractional_timing_offset": reference["timing_fraction"],
|
||||
"reference_coherence": reference["coherence"],
|
||||
"legacy_expected_phase_drift_preamble_rad": float(2 * np.pi * legacy["initial"]["frequency_hz"] * PREAMBLE_SYMBOLS / SYMBOL_RATE),
|
||||
"legacy_expected_phase_drift_header_rad": float(2 * np.pi * legacy["initial"]["frequency_hz"] * HEADER_SYMBOLS / SYMBOL_RATE),
|
||||
"legacy_expected_phase_drift_full_packet_rad": float(2 * np.pi * legacy["initial"]["frequency_hz"] * len(expected_frame) / SYMBOL_RATE),
|
||||
"phase_error_from_cfo_mismatch_over_header_rad": float(
|
||||
2 * np.pi * (legacy["initial"]["frequency_hz"] - reference["frequency_hz"])
|
||||
* HEADER_SYMBOLS / SYMBOL_RATE
|
||||
),
|
||||
"expected_header_bits": header_legacy["expected_header_bits"],
|
||||
"received_raw_header_bits": header_legacy["received_raw_header_bits"],
|
||||
"header_raw_errors": header_legacy["raw_header_errors"],
|
||||
"header_copy_1_errors": header_legacy["copy_errors"][0],
|
||||
"header_copy_2_errors": header_legacy["copy_errors"][1],
|
||||
"header_copy_3_errors": header_legacy["copy_errors"][2],
|
||||
"voted_header_bits": header_legacy["voted_header_bits"],
|
||||
"header_voted_errors": header_legacy["voted_header_errors"],
|
||||
"magic_errors": header_legacy["magic_errors"],
|
||||
"version_errors": header_legacy["version_errors"],
|
||||
"length_errors": header_legacy["length_errors"],
|
||||
"checksum_errors": header_legacy["checksum_errors"],
|
||||
"decoded_magic": header_legacy["decoded_magic"],
|
||||
"decoded_length": header_legacy["decoded_length"],
|
||||
"header_accepted": header_legacy["accepted"],
|
||||
"header_reason": header_legacy["reason"],
|
||||
"header_raw_error_positions": header_legacy["raw_error_positions"],
|
||||
"header_voted_error_positions": header_legacy["voted_error_positions"],
|
||||
"legacy_header_bits": header_legacy["received_raw_header_bits"],
|
||||
"t2_header_bits": header_t2["received_raw_header_bits"],
|
||||
"legacy_t2_header_differences": legacy_t2_differences,
|
||||
"local_window": "last 128 preamble symbols plus complete 144-symbol header",
|
||||
"local_matched_power": local["matched_component_power"],
|
||||
"local_residual_power": local["residual_power"],
|
||||
"local_matched_residual_db": local["matched_residual_db"],
|
||||
"rms_symbol_deviation": local["rms_symbol_deviation"],
|
||||
"header_error_rate": header_legacy["raw_header_errors"] / HEADER_SYMBOLS,
|
||||
"preceding_zero_gap_length": gap["length_samples"],
|
||||
"gap_background_ac_rms": gap["background_ac_rms"],
|
||||
"gap_mean_i": gap["mean_i"], "gap_mean_q": gap["mean_q"],
|
||||
"gap_residual_energy": gap["residual_energy_mean"],
|
||||
"previous_waveform_tail_detected": gap["previous_waveform_tail_detected"],
|
||||
"gap_leading_edge_to_middle_db": gap["leading_edge_to_middle_db"],
|
||||
"trailing_guard_packet_16": trailing,
|
||||
}
|
||||
packet_rows.append(row)
|
||||
for position in range(HEADER_SYMBOLS):
|
||||
bit_rows.append({
|
||||
"packet": packet, "protected_bit_position": position,
|
||||
"field_bit_position": position // HEADER_REPETITION,
|
||||
"copy": position % HEADER_REPETITION + 1,
|
||||
"expected": int(expected_header[position]),
|
||||
"legacy_received": int(legacy["hard_bits"][PREAMBLE_SYMBOLS + position]),
|
||||
"t2_received": int(t2["hard_bits"][PREAMBLE_SYMBOLS + position]),
|
||||
"legacy_error": int(position in header_legacy["raw_error_positions"]),
|
||||
"t2_error": int(position in header_t2["raw_error_positions"]),
|
||||
})
|
||||
start = PREAMBLE_SYMBOLS - 128
|
||||
stop = PREAMBLE_SYMBOLS + HEADER_SYMBOLS + 128
|
||||
corrected = transition["corrected"][start:stop]
|
||||
truth = 2.0 * expected_frame[start:stop] - 1.0
|
||||
errors = np.flatnonzero((corrected.real >= 0) != (truth > 0))
|
||||
phase_error = np.angle(corrected * truth)
|
||||
transitions[packet] = {
|
||||
"corrected_real": corrected.real.astype(float).tolist(),
|
||||
"corrected_imag": corrected.imag.astype(float).tolist(),
|
||||
"magnitude": abs(corrected).astype(float).tolist(),
|
||||
"phase_error_rad": phase_error.astype(float).tolist(),
|
||||
"hard_decisions": (corrected.real >= 0).astype(int).tolist(),
|
||||
"errors": errors.astype(int).tolist(),
|
||||
"sample_positions": transition["sample_positions"][start:stop].astype(int).tolist(),
|
||||
}
|
||||
regions = {"preamble_tail": slice(0, 128), "header": slice(128, 128 + HEADER_SYMBOLS), "postheader": slice(128 + HEADER_SYMBOLS, None)}
|
||||
for name, selected in regions.items():
|
||||
values = corrected[selected]
|
||||
local_truth = truth[selected]
|
||||
transition_rows.append({
|
||||
"packet": packet, "region": name,
|
||||
"symbols": len(values),
|
||||
"decision_errors": int(np.count_nonzero((values.real >= 0) != (local_truth > 0))),
|
||||
"magnitude_median": float(np.median(abs(values))),
|
||||
"magnitude_minimum": float(np.min(abs(values))),
|
||||
"phase_error_rms_rad": float(np.sqrt(np.mean(np.angle(values * local_truth) ** 2))),
|
||||
"imaginary_rms": float(np.sqrt(np.mean(values.imag ** 2))),
|
||||
"timing_offset_samples": transition["timing_samples"],
|
||||
})
|
||||
|
||||
assign_classifications(packet_rows)
|
||||
candidate_summary, candidate_rows = candidate_analysis(strict, expected_starts)
|
||||
tx_header_identical = all(np.array_equal(tx_headers[0], item) for item in tx_headers[1:])
|
||||
tx_header_decodes = [lab066.phy.lab045.decode_phy_header_soft(2.0 * item - 1.0) for item in tx_headers]
|
||||
software_current = {
|
||||
mode: lab066.decode(mode, TX_PATH, diagnostics=False)
|
||||
for mode in ("legacy", "t2")
|
||||
}
|
||||
software_16_of_16 = all(
|
||||
result["packet_output"] == 16 and result["exact_payload_match"] == 16
|
||||
for result in software_current.values()
|
||||
)
|
||||
groups = group_summary(packet_rows)
|
||||
distributions = {
|
||||
label: {
|
||||
metric: distribution(packet_rows, packets, metric)
|
||||
for metric in (
|
||||
"preamble_errors", "preamble_error_rate", "preamble_correlation",
|
||||
"preamble_rms_symbol_deviation", "preamble_matched_power",
|
||||
"preamble_residual_power", "preamble_matched_residual_db",
|
||||
"header_raw_errors", "header_voted_errors",
|
||||
)
|
||||
}
|
||||
for label, packets in (("passed", PASSED_PACKETS), ("failed", FAILED_PACKETS))
|
||||
}
|
||||
failed_classes = {str(row["packet"]): row["classification"] for row in packet_rows if row["packet"] in FAILED_PACKETS}
|
||||
passed_local_residual = float(np.median([
|
||||
row["local_residual_power"] for row in packet_rows if row["packet"] in PASSED_PACKETS
|
||||
]))
|
||||
failed_local_residual = float(np.median([
|
||||
row["local_residual_power"] for row in packet_rows if row["packet"] in FAILED_PACKETS
|
||||
]))
|
||||
passed_local_ratio = float(np.median([
|
||||
row["local_matched_residual_db"] for row in packet_rows if row["packet"] in PASSED_PACKETS
|
||||
]))
|
||||
failed_local_ratio = float(np.median([
|
||||
row["local_matched_residual_db"] for row in packet_rows if row["packet"] in FAILED_PACKETS
|
||||
]))
|
||||
failed_error_positions = [
|
||||
position for row in packet_rows if row["packet"] in FAILED_PACKETS
|
||||
for position in row["header_raw_error_positions"]
|
||||
]
|
||||
repeated_failed_positions = sorted({
|
||||
position for position in failed_error_positions
|
||||
if failed_error_positions.count(position) > 1
|
||||
})
|
||||
diagnostic_strict_agreement = all(
|
||||
row["header_accepted"] == (row["strict_result"] == "PASS")
|
||||
for row in packet_rows
|
||||
)
|
||||
timing_agreement = all(
|
||||
row["timing_offset_samples"] == row["reference_timing_offset_samples"]
|
||||
for row in packet_rows
|
||||
)
|
||||
cfo_scale_small = max(abs(row["phase_error_from_cfo_mismatch_over_header_rad"])
|
||||
for row in packet_rows) < 0.1
|
||||
candidate_state_independent = (
|
||||
candidate_summary["persistent_state_between_rejected_candidates"] is False
|
||||
and candidate_summary["accepted_until_changes_only_after_crc_pass"] is True
|
||||
)
|
||||
local_quality_conditions = {
|
||||
"tx_and_software_valid": bool(
|
||||
tx_header_identical and all(item["passed"] for item in tx_header_decodes)
|
||||
and software_16_of_16
|
||||
),
|
||||
"strict_header_reproduced": diagnostic_strict_agreement,
|
||||
"no_common_failed_error_position": not repeated_failed_positions,
|
||||
"failed_local_residual_at_least_20_percent_higher": (
|
||||
failed_local_residual > 1.2 * passed_local_residual
|
||||
),
|
||||
"failed_local_matched_residual_at_least_1_db_lower": (
|
||||
failed_local_ratio < passed_local_ratio - 1.0
|
||||
),
|
||||
"receiver_and_oracle_timing_agree": timing_agreement,
|
||||
"cfo_mismatch_phase_scale_below_0p1_rad": cfo_scale_small,
|
||||
"candidate_state_independent": candidate_state_independent,
|
||||
}
|
||||
root_cause = (
|
||||
"LOCAL_RF_QUALITY" if all(local_quality_conditions.values())
|
||||
else "NOT_LOCALIZED"
|
||||
)
|
||||
small_fix_evidence = {
|
||||
"receiver_header_logic": bool(not diagnostic_strict_agreement),
|
||||
"timing_transition": bool(not timing_agreement and not cfo_scale_small),
|
||||
"carrier_transition": bool(not cfo_scale_small and timing_agreement),
|
||||
"candidate_state_handling": bool(not candidate_state_independent),
|
||||
}
|
||||
one_small_fix = any(small_fix_evidence.values())
|
||||
plots = make_plots(packet_rows, transitions, candidate_summary)
|
||||
write_csvs(packet_rows, candidate_rows, bit_rows, transition_rows)
|
||||
save_json(OUTPUT / "transition_symbols.json", transitions)
|
||||
summary = {
|
||||
"lab": "Lab067",
|
||||
"mode": "OFFLINE_DIAGNOSTIC_ONLY",
|
||||
"rf_tx": 0,
|
||||
"hardware_accessed": False,
|
||||
"lab066_integrity": integrity,
|
||||
"historical_strict_result_unchanged": {
|
||||
"legacy": "10/16", "t2": "10/16",
|
||||
"passed_packets": sorted(PASSED_PACKETS), "failed_packets": sorted(FAILED_PACKETS),
|
||||
},
|
||||
"packet_positions_verified": len(expected_starts) == 16 and all(
|
||||
row["candidate_difference_samples"] is not None and abs(row["candidate_difference_samples"]) <= 64
|
||||
for row in packet_rows
|
||||
),
|
||||
"packet_rows": packet_rows,
|
||||
"passed_failed_distributions": distributions,
|
||||
"start_middle_end_groups": groups,
|
||||
"failed_packet_classifications": failed_classes,
|
||||
"header_error_position_analysis": {
|
||||
"failed_raw_error_count": len(failed_error_positions),
|
||||
"positions_repeated_between_failed_packets": repeated_failed_positions,
|
||||
"systematic_common_position_found": bool(repeated_failed_positions),
|
||||
"interpretation": "Позиции ошибок различаются; общей битовой позиции отказа нет.",
|
||||
},
|
||||
"local_quality_evidence": {
|
||||
"passed_local_residual_power_median": passed_local_residual,
|
||||
"failed_local_residual_power_median": failed_local_residual,
|
||||
"passed_local_matched_residual_db_median": passed_local_ratio,
|
||||
"failed_local_matched_residual_db_median": failed_local_ratio,
|
||||
"failed_minus_passed_residual_power_median": failed_local_residual - passed_local_residual,
|
||||
"failed_minus_passed_matched_residual_db_median": failed_local_ratio - passed_local_ratio,
|
||||
"scope": "last 128 preamble symbols plus complete header",
|
||||
"decision_conditions": local_quality_conditions,
|
||||
},
|
||||
"legacy_t2_header_difference": (
|
||||
"NONE" if all(row["legacy_t2_header_differences"] == 0 for row in packet_rows)
|
||||
else "PRESENT"
|
||||
),
|
||||
"t2_influence_starts_at_symbol": PREAMBLE_SYMBOLS,
|
||||
"t2_explanation": "T2 начинает сопровождение после преамбулы; его решения заголовка сравнены побитно со штатным legacy.",
|
||||
"candidate_analysis": candidate_summary,
|
||||
"tx_validation": {
|
||||
"headers_identical_at_all_packet_positions": tx_header_identical,
|
||||
"all_headers_decode_with_correct_magic_version_length_checksum": all(item["passed"] for item in tx_header_decodes),
|
||||
"decoded_packet_lengths": [item["packet_bytes"] for item in tx_header_decodes],
|
||||
"software_roundtrip_current": software_current,
|
||||
"legacy_and_t2_16_of_16": software_16_of_16,
|
||||
},
|
||||
"header_failure_root_cause": root_cause,
|
||||
"one_small_fix_justified": one_small_fix,
|
||||
"branch_decision": (
|
||||
"ONE_SMALL_FIX_JUSTIFIED" if one_small_fix
|
||||
else "NO_SMALL_FIX_JUSTIFIED"
|
||||
),
|
||||
"small_fix_assessment": {
|
||||
"evidence_flags": small_fix_evidence,
|
||||
"receiver_header_logic_error_found": small_fix_evidence["receiver_header_logic"],
|
||||
"timing_transition_error_found": small_fix_evidence["timing_transition"],
|
||||
"carrier_transition_error_explains_six_failures": small_fix_evidence["carrier_transition"],
|
||||
"candidate_state_error_found": small_fix_evidence["candidate_state_handling"],
|
||||
"reason": "TX и программный тракт дают 16/16; ошибки заголовка не имеют общей позиции, timing совпадает, а локальный остаток в failed header-окнах выше.",
|
||||
},
|
||||
"achieved_result": "10/16 exact full packets at 500 ksymbol/s, TX VGA 20 dB, hardware-valid OTA capture",
|
||||
"recommended_next_project_branch": "CRSF / ELRS CONTROL AND TELEMETRY",
|
||||
"qpsk_note": "QPSK с меньшей символьной скоростью остаётся будущим кандидатом для видеоканала; в Lab067 не реализован.",
|
||||
"plots": plots,
|
||||
"lab067_commit": "NOT PERFORMED",
|
||||
"push": "NOT PERFORMED",
|
||||
}
|
||||
save_json(OUTPUT / "summary.json", summary)
|
||||
return summary
|
||||
|
||||
|
||||
def run_functional_tests() -> None:
|
||||
expected = np.repeat(np.arange(HEADER_BITS, dtype=np.uint8) % 2, HEADER_REPETITION)
|
||||
exact = header_diagnostics(2.0 * expected.astype(float) - 1.0, expected)
|
||||
assert exact["raw_header_errors"] == 0
|
||||
assert exact["voted_header_errors"] == 0
|
||||
changed = expected.copy(); changed[3:5] ^= 1
|
||||
result = header_diagnostics(2.0 * changed.astype(float) - 1.0, expected)
|
||||
assert result["raw_header_errors"] == 2
|
||||
assert result["voted_header_errors"] == 1
|
||||
|
||||
|
||||
def main() -> None:
|
||||
run_functional_tests()
|
||||
result = analyze()
|
||||
print(json.dumps({
|
||||
"lab": result["lab"],
|
||||
"historical_strict_result": result["historical_strict_result_unchanged"],
|
||||
"root_cause": result["header_failure_root_cause"],
|
||||
"one_small_fix_justified": result["one_small_fix_justified"],
|
||||
"failed_packet_classifications": result["failed_packet_classifications"],
|
||||
"plots": result["plots"],
|
||||
}, ensure_ascii=False, indent=2))
|
||||
print("RF TX = 0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
151
tests/test_lab065_rf_level_linearity.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Lab065: неизменный waveform, TX VGA 20 дБ и границы классификации."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from experiments import lab065_rf_level_linearity as lab
|
||||
from tools.hackrf_process_check import ProcessCheck, ProcessState
|
||||
|
||||
|
||||
def test_only_operating_parameter_change_is_tx_vga():
|
||||
old = lab.lab064.parameters()
|
||||
new = lab.parameters()
|
||||
assert new.pop("geometry_unchanged_from_lab064") is True
|
||||
assert new.pop("waveform_source") == "Lab064 exact CS8 copy"
|
||||
assert new["tx_vga_db"] == 20
|
||||
new["tx_vga_db"] = 10
|
||||
assert new == old
|
||||
|
||||
|
||||
def test_tx_command_uses_20_db_and_rx_remains_24_24():
|
||||
transmit = lab.transfer_command("tx.cs8", 100, transmit=True)
|
||||
receive = lab.transfer_command("rx.cs8", 100)
|
||||
assert transmit[transmit.index("-x") + 1] == "20"
|
||||
assert receive[receive.index("-l") + 1] == "24"
|
||||
assert receive[receive.index("-g") + 1] == "24"
|
||||
assert transmit[-5:] == receive[-5:] == ["-a", "0", "-p", "0", "-B"]
|
||||
|
||||
|
||||
def test_exact_lab064_waveform_is_required():
|
||||
metadata = lab.validate_lab064_waveform()
|
||||
assert metadata["tx_sha256"] == lab.EXPECTED_TX_SHA256
|
||||
assert metadata["tx_samples"] == 6_000_000
|
||||
|
||||
|
||||
def _result(matched_gain, residual_gain, improvements, errors=(0.0, 0.0, 0.0)):
|
||||
old = {row["label"]: row for row in json.loads(
|
||||
lab.LAB064_RESULT.read_text(encoding="utf-8")
|
||||
)["rows"]}
|
||||
rows = []
|
||||
for index, label in enumerate(("L0", "L1", "L2", "L3")):
|
||||
baseline = old[label]
|
||||
rows.append({
|
||||
"label": label,
|
||||
"matched_signal_power": baseline["matched_signal_power"] * 10 ** (matched_gain[index] / 10),
|
||||
"residual_power": baseline["residual_power"] * 10 ** (residual_gain[index] / 10),
|
||||
"matched_to_residual_db": baseline["matched_to_residual_db"] + improvements[index],
|
||||
"gain_power_abs_h_squared": baseline["gain_power_abs_h_squared"] * 10 ** (matched_gain[index] / 10),
|
||||
})
|
||||
return {
|
||||
"capture_valid": True,
|
||||
"rows": rows,
|
||||
"comparisons": [{"linearity_error_db": error} for error in errors],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("result", "expected"),
|
||||
[
|
||||
(_result([10] * 4, [1] * 4, [9] * 4), "LINEAR_GAIN_IMPROVEMENT"),
|
||||
(_result([10] * 4, [10] * 4, [0] * 4), "RESIDUAL_RISES_WITH_SIGNAL"),
|
||||
(_result([1] * 4, [0] * 4, [1] * 4), "NO_MEANINGFUL_GAIN"),
|
||||
(_result([10] * 4, [1] * 4, [9] * 4, errors=(0, 0, -1.6)), "COMPRESSION_AT_HIGHER_TX_GAIN"),
|
||||
],
|
||||
)
|
||||
def test_classification_cases(result, expected):
|
||||
comparison = lab.compare_sessions(
|
||||
json.loads(lab.LAB064_RESULT.read_text(encoding="utf-8")), result["rows"]
|
||||
)
|
||||
assert lab.classify_result(result, comparison)["case"] == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", [None, "clock", "overrun", "clipping"])
|
||||
def test_preflight_never_transmits_and_fails_closed(tmp_path, monkeypatch, failure):
|
||||
raw, output = tmp_path / "raw", tmp_path / "output"
|
||||
raw.mkdir()
|
||||
output.mkdir()
|
||||
monkeypatch.setattr(lab, "RAW", raw)
|
||||
monkeypatch.setattr(lab, "OUTPUT", output)
|
||||
(raw / "tx_level_staircase.cs8").write_bytes(b"test")
|
||||
metadata = {"tx_sha256": "TEST"}
|
||||
(output / "software_gate.json").write_text('{"passed": true, "tx_sha256": "TEST"}')
|
||||
monkeypatch.setattr(lab, "validate_prepared", lambda: metadata)
|
||||
monkeypatch.setattr(lab.lab054.phy.lab045, "_verify_device_inventory", lambda runner: {"passed": True})
|
||||
monkeypatch.setattr(lab.lab054, "disable_clock_outputs", lambda runner: [])
|
||||
monkeypatch.setattr(lab, "parameters", lambda: {})
|
||||
|
||||
def clock(point, runner, guard, **kwargs):
|
||||
return {"passed": failure != "clock", "master_reg29": 0x26,
|
||||
"slave_reg29": 0x26, "slave_clkin_status": "DETECTED"}
|
||||
|
||||
def runner(command):
|
||||
assert "-t" not in command
|
||||
if "-r" in command:
|
||||
value = 127 if failure == "clipping" else 1
|
||||
Path(command[command.index("-r") + 1]).write_bytes(bytes([value, value]) * 800_000)
|
||||
log = "1 RX overruns" if failure == "overrun" else "0 RX overruns"
|
||||
return subprocess.CompletedProcess(command, 0, log, "")
|
||||
|
||||
options = {
|
||||
"runner": runner,
|
||||
"clock_checker": clock,
|
||||
"process_checker": lambda: ProcessCheck(ProcessState.CLEAR, ()),
|
||||
"sleep_function": lambda _: None,
|
||||
}
|
||||
if failure:
|
||||
with pytest.raises(RuntimeError):
|
||||
lab.preflight(**options)
|
||||
else:
|
||||
assert lab.preflight(**options)["passed"]
|
||||
assert json.loads((raw / "preflight_result.json").read_text())["rf_tx_count"] == 0
|
||||
|
||||
|
||||
def test_capture_is_unavailable_without_new_authorization():
|
||||
with pytest.raises(PermissionError):
|
||||
lab.capture_once(authorization=False)
|
||||
|
||||
|
||||
def test_existing_attempt_blocks_capture_before_hardware(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(lab, "RAW", tmp_path)
|
||||
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "TEST"})
|
||||
monkeypatch.setattr(lab, "require_preflight", lambda metadata: {})
|
||||
(tmp_path / "hardware_attempt.json").write_text("{}", encoding="utf-8")
|
||||
with pytest.raises(FileExistsError):
|
||||
lab.capture_once(
|
||||
authorization=True,
|
||||
runner=lambda command: pytest.fail("hardware must not run"),
|
||||
process_starter=lambda *args: pytest.fail("hardware must not run"),
|
||||
)
|
||||
|
||||
|
||||
def test_cli_labels_current_rf_state_after_completed_capture(monkeypatch, capsys):
|
||||
monkeypatch.setattr(
|
||||
lab,
|
||||
"capture_once",
|
||||
lambda authorization=False: {
|
||||
"stage": "SINGLE_RF_LEVEL_CAPTURE", "rf_tx_count": 1, "passed": True,
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
sys, "argv", ["lab065", "--stage", "capture", "--authorize-one-rf"]
|
||||
)
|
||||
lab.main()
|
||||
output = capsys.readouterr().out
|
||||
assert '"rf_tx_count": 1' in output
|
||||
assert output.endswith("RF TX NOW = 0\n")
|
||||
149
tests/test_lab066_full_packet_rf_retest.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""Lab066: исправленные паузы, строгий software gate и запрет TX."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from experiments import lab066_full_packet_rf_retest as lab
|
||||
from tools.hackrf_process_check import ProcessCheck, ProcessState
|
||||
|
||||
|
||||
def test_fixed_layout_and_operating_point():
|
||||
waveform, layout = lab.build_waveform()
|
||||
assert len(lab.plans()) == 16
|
||||
assert all(len(frame.plan.payload) == 256 for frame in lab.plans())
|
||||
assert len({frame.plan.payload for frame in lab.plans()}) == 16
|
||||
assert lab.PACKET_SAMPLES == 92_256
|
||||
assert lab.INTER_PACKET_GAP_SAMPLES == 40_000
|
||||
assert lab.NUMBER_OF_GAPS == 15
|
||||
assert lab.GUARD_SAMPLES == 800_000
|
||||
assert lab.BASE_TX_SAMPLES == 2_076_096
|
||||
assert lab.EXPECTED_TX_SAMPLES == 3_676_096
|
||||
assert len(waveform) == 2 * lab.EXPECTED_TX_SAMPLES
|
||||
assert [row["packet_index"] for row in layout] == list(range(1, 17))
|
||||
assert lab.parameters()["tx_vga_db"] == 20
|
||||
assert lab.parameters()["rx_lna_db"] == lab.parameters()["rx_vga_db"] == 24
|
||||
|
||||
|
||||
def test_guards_and_all_gaps_are_exact_zero():
|
||||
waveform, layout = lab.build_waveform()
|
||||
samples = memoryview(waveform)
|
||||
assert not any(samples[:2 * lab.GUARD_SAMPLES])
|
||||
assert not any(samples[-2 * lab.GUARD_SAMPLES:])
|
||||
for row in layout[:-1]:
|
||||
low = 2 * row["following_gap_start_sample"]
|
||||
high = low + 2 * row["following_gap_samples"]
|
||||
assert high - low == 2 * 40_000
|
||||
assert not any(samples[low:high])
|
||||
|
||||
|
||||
def test_transfer_commands_are_rx_only_or_fixed_tx():
|
||||
receive = lab.transfer_command("rx.cs8", 800_000)
|
||||
transmit = lab.transfer_command("tx.cs8", 100, transmit=True)
|
||||
assert "-r" in receive and "-t" not in receive
|
||||
assert "-t" in transmit and "-r" not in transmit
|
||||
assert receive[receive.index("-f") + 1] == "1400000000"
|
||||
assert receive[receive.index("-l") + 1] == receive[receive.index("-g") + 1] == "24"
|
||||
assert transmit[transmit.index("-x") + 1] == "20"
|
||||
|
||||
|
||||
def test_prepare_metadata_and_two_strict_roundtrips(tmp_path, monkeypatch):
|
||||
raw, output = tmp_path / "raw", tmp_path / "processed"
|
||||
monkeypatch.setattr(lab, "RAW", raw); monkeypatch.setattr(lab, "OUTPUT", output)
|
||||
metadata = lab.prepare()
|
||||
assert metadata["tx_samples"] == lab.EXPECTED_TX_SAMPLES
|
||||
assert metadata["tx_sha256"] == hashlib.sha256((raw / "tx_stream.cs8").read_bytes()).hexdigest()
|
||||
result = lab.software_gate()
|
||||
for mode in ("legacy", "t2"):
|
||||
row = result[mode]
|
||||
assert row["header_pass"] == row["fec_pass"] == row["crc_pass"] == 16
|
||||
assert row["packet_output"] == row["exact_payload_match"] == 16
|
||||
assert row["packet_indexes"] == list(range(1, 17))
|
||||
assert row["exact_ordered_payloads"] and row["passed"]
|
||||
assert row["fallback_between_modes"] is False
|
||||
assert result["t2"]["t2_state_scope"] == "NEW_PREAMBLE_ESTIMATE_PER_PACKET"
|
||||
assert result["t2"]["t2_state_reset_evidence"][
|
||||
"tracking_runs_per_accepted_packet"
|
||||
] == [2] * 16
|
||||
assert result["t2_state_reset_verified_by_fresh_preamble_per_packet"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", [None, "clock", "clipping", "strong", "overrun"])
|
||||
def test_preflight_never_transmits_and_fails_closed(tmp_path, monkeypatch, failure):
|
||||
raw, output = tmp_path / "raw", tmp_path / "processed"
|
||||
raw.mkdir(); output.mkdir()
|
||||
monkeypatch.setattr(lab, "RAW", raw); monkeypatch.setattr(lab, "OUTPUT", output)
|
||||
(raw / "tx_stream.cs8").write_bytes(b"test")
|
||||
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "TEST"})
|
||||
(output / "software_gate.json").write_text('{"passed": true, "tx_sha256": "TEST"}')
|
||||
monkeypatch.setattr(lab.lab054.phy.lab045, "_verify_device_inventory", lambda runner: {"passed": True})
|
||||
|
||||
def clock(point, runner, guard, **kwargs):
|
||||
return {"passed": failure != "clock", "master_reg29": 0x26,
|
||||
"slave_reg29": 0x26, "slave_clkin_status": "DETECTED"}
|
||||
|
||||
def runner(command):
|
||||
assert "-t" not in command
|
||||
if "-r" in command:
|
||||
value = 127 if failure == "clipping" else (110 if failure == "strong" else 1)
|
||||
Path(command[command.index("-r") + 1]).write_bytes(bytes([value, value]) * lab.PREFLIGHT_SAMPLES)
|
||||
log = "1 RX overruns\n0 RX overruns" if failure == "overrun" else "0 RX overruns"
|
||||
return subprocess.CompletedProcess(command, 0, log, "")
|
||||
|
||||
monkeypatch.setattr(lab.lab054, "disable_clock_outputs", lambda runner: [])
|
||||
kwargs = dict(runner=runner, clock_checker=clock,
|
||||
process_checker=lambda: ProcessCheck(ProcessState.CLEAR, ()),
|
||||
sleep_function=lambda _: None)
|
||||
if failure:
|
||||
with pytest.raises(RuntimeError): lab.preflight(**kwargs)
|
||||
else:
|
||||
assert lab.preflight(**kwargs)["passed"]
|
||||
record = json.loads((raw / "preflight_result.json").read_text())
|
||||
assert record["rf_tx_count"] == 0
|
||||
if failure == "overrun":
|
||||
assert record["rx_counter_summary"]["maximum_count"] == 1
|
||||
|
||||
|
||||
def test_capture_refuses_without_authorization_before_hardware(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
|
||||
with pytest.raises(PermissionError):
|
||||
lab.capture_once(authorization=False, runner=lambda command: pytest.fail("hardware"))
|
||||
assert not (tmp_path / "hardware_attempt.json").exists()
|
||||
|
||||
|
||||
def test_future_analysis_declares_strict_modes_and_physical_comparison():
|
||||
source = Path(lab.__file__).read_text(encoding="utf-8")
|
||||
assert 'decode("legacy", rx_path, diagnostics=True)' in source
|
||||
assert 'decode("t2", rx_path, diagnostics=True)' in source
|
||||
assert "physical_packet_diagnostics(tx, rx, location)" in source
|
||||
assert "if not strict_runs_complete" in source
|
||||
assert '["Lab056 B", "Lab062", "Lab066"]' in source
|
||||
|
||||
|
||||
def test_future_analysis_rejects_capture_from_another_configuration(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
|
||||
rx = tmp_path / "rx_stream.cs8"; rx.write_bytes(b"\x00\x00")
|
||||
capture = {
|
||||
"lab": "Lab066", "rf_tx_count": 1, "automatic_tx_repeats": 0,
|
||||
"point": {}, "parameters": {}, "tx_sha256": "WRONG",
|
||||
"rx_sha256": lab.sha256(rx),
|
||||
}
|
||||
(tmp_path / "capture_result.json").write_text(json.dumps(capture))
|
||||
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "EXPECTED"})
|
||||
monkeypatch.setattr(lab, "decode", lambda *args, **kwargs: pytest.fail("decode"))
|
||||
with pytest.raises(RuntimeError, match="подтверждённой"):
|
||||
lab.analyze_saved_capture()
|
||||
|
||||
|
||||
def test_second_preflight_is_refused_before_hardware(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(lab, "RAW", tmp_path); monkeypatch.setattr(lab, "OUTPUT", tmp_path)
|
||||
(tmp_path / "preflight_attempt.json").write_text("{}")
|
||||
monkeypatch.setattr(lab, "validate_prepared", lambda: {"tx_sha256": "TEST"})
|
||||
(tmp_path / "software_gate.json").write_text('{"passed": true, "tx_sha256": "TEST"}')
|
||||
with pytest.raises(FileExistsError):
|
||||
lab.preflight(runner=lambda command: pytest.fail("hardware"))
|
||||
79
tests/test_lab067_header_failure_analysis.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Проверки диагностических вычислений Lab067 без raw IQ и оборудования."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from experiments import lab067_header_failure_analysis as lab067
|
||||
|
||||
|
||||
def test_header_diagnostics_repeats_strict_soft_combining() -> None:
|
||||
expected = lab067.lab066.plans()[0].plan.phy_header_bits.astype(np.uint8)
|
||||
soft = 2.0 * expected.astype(float) - 1.0
|
||||
# Две копии одного бита имеют неверный знак, но малы по модулю. Третья
|
||||
# правильная копия сильнее, поэтому штатная мягкая сумма сохраняет бит.
|
||||
soft[:2] *= -0.1
|
||||
result = lab067.header_diagnostics(soft, expected)
|
||||
assert result["raw_header_errors"] == 2
|
||||
assert result["voted_header_errors"] == 0
|
||||
assert result["accepted"] is True
|
||||
|
||||
|
||||
def test_header_diagnostics_reports_field_and_copy_errors() -> None:
|
||||
expected = lab067.lab066.plans()[0].plan.phy_header_bits.astype(np.uint8)
|
||||
received = expected.copy()
|
||||
received[24:26] ^= 1
|
||||
result = lab067.header_diagnostics(
|
||||
2.0 * received.astype(float) - 1.0,
|
||||
expected,
|
||||
)
|
||||
assert result["raw_header_errors"] == 2
|
||||
assert result["copy_errors"] == [1, 1, 0]
|
||||
assert result["voted_header_errors"] == 1
|
||||
assert result["magic_errors"] == 1
|
||||
assert result["accepted"] is False
|
||||
|
||||
|
||||
def test_strict_candidate_selection_uses_nearest_expected_position() -> None:
|
||||
rows = [
|
||||
{"absolute_refined": 980, "packet_output": False, "crc": False, "fec": False, "header": False},
|
||||
{"absolute_refined": 1003, "packet_output": False, "crc": False, "fec": False, "header": True},
|
||||
]
|
||||
selected = lab067.strict_candidate_for_packet(rows, 1000)
|
||||
assert selected is rows[1]
|
||||
|
||||
|
||||
def test_strict_candidate_selection_never_prefers_farther_success() -> None:
|
||||
exact_failure = {
|
||||
"absolute_refined": 1000, "packet_output": False,
|
||||
"crc": False, "fec": False, "header": False,
|
||||
}
|
||||
farther_success = {
|
||||
"absolute_refined": 1064, "packet_output": True,
|
||||
"crc": True, "fec": True, "header": True,
|
||||
}
|
||||
selected = lab067.strict_candidate_for_packet(
|
||||
[farther_success, exact_failure], 1000
|
||||
)
|
||||
assert selected is exact_failure
|
||||
|
||||
|
||||
def test_strict_candidate_tie_break_does_not_use_verdict() -> None:
|
||||
lower_failure = {
|
||||
"absolute_refined": 999, "packet_output": False,
|
||||
"crc": False, "fec": False, "header": False,
|
||||
}
|
||||
upper_success = {
|
||||
"absolute_refined": 1001, "packet_output": True,
|
||||
"crc": True, "fec": True, "header": True,
|
||||
}
|
||||
selected = lab067.strict_candidate_for_packet(
|
||||
[upper_success, lower_failure], 1000
|
||||
)
|
||||
assert selected is lower_failure
|
||||
|
||||
|
||||
def test_lab067_has_no_rf_entry_point() -> None:
|
||||
assert not hasattr(lab067, "capture_once")
|
||||
assert lab067.RX_PATH.name == "rx_stream.cs8"
|
||||
assert lab067.FAILED_PACKETS == {1, 2, 3, 9, 15, 16}
|
||||