Lab066: validate 500 ksymbol/s full-packet OTA reception
This commit is contained in:
@@ -2924,3 +2924,80 @@ clipping0, cleanup без ошибок; процессы после CLEAR. RX:16
|
|||||||
RF TX=0. Lab065 не коммитировалась, push не выполнялся, Lab066 не создана.
|
RF TX=0. Lab065 не коммитировалась, push не выполнялся, Lab066 не создана.
|
||||||
Следующий этап: STOP, рассмотрение результата и отдельное разрешение на коммит
|
Следующий этап: STOP, рассмотрение результата и отдельное разрешение на коммит
|
||||||
Lab065; повторная передача или изменение усилений запрещены.
|
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 не нужен.
|
||||||
|
|||||||
224
data/processed/lab066/capture_result.json
Normal file
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
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
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
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
3788
data/processed/lab066/rf_result.json
Normal file
File diff suppressed because it is too large
Load Diff
382
data/processed/lab066/rf_summary.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
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
|
||||||
|
}
|
||||||
@@ -700,3 +700,44 @@
|
|||||||
- **Независимая проверка:** прямой пересчёт CS8 подтвердил SHA, счётчики,
|
- **Независимая проверка:** прямой пересчёт CS8 подтвердил SHA, счётчики,
|
||||||
локализацию, фон, matched/residual, `h`, CFO и спектральные полосы. Расхождений
|
локализацию, фон, matched/residual, `h`, CFO и спектральные полосы. Расхождений
|
||||||
нет. Quick gate1000/38/13 PASS, pytest1000 passed, diff check PASS.
|
нет. 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 не создавалась.
|
||||||
|
|||||||
92
docs/lab066_current_state.md
Normal file
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
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
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 запрещены.
|
||||||
786
experiments/lab066_full_packet_rf_retest.py
Normal file
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()
|
||||||
149
tests/test_lab066_full_packet_rf_retest.py
Normal file
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"))
|
||||||
Reference in New Issue
Block a user