Lab056: compare 20 and 500 ksymbol/s in controlled RF session

This commit is contained in:
LittleSam129
2026-09-10 13:58:11 +03:00
parent 5add95ecb8
commit 92477a4632
27 changed files with 6518 additions and 0 deletions

View File

@@ -2218,3 +2218,229 @@ HEAD прежний0368c6d3e930f4d245e056223fd4e5c4f70e6b49, main; предыд
изменения Lab054 сохранены. Git add/commit/push/pull/fetch не выполнялись. изменения Lab054 сохранены. Git add/commit/push/pull/fetch не выполнялись.
Следующий этап: STOP и согласование измерительной схемы, приборов и допустимой Следующий этап: STOP и согласование измерительной схемы, приборов и допустимой
частоты; никакого автоматического аппаратного продолжения. частоты; никакого автоматического аппаратного продолжения.
# Запись 047
## Дата
9 сентября 2026 года
## Тема
Lab055 — фиксация результата; Lab056 — программная подготовка эфирного ABAB
## Выполнено
По новому явному разрешению пользователя Lab055 зафиксирована коммитом
5add95ecb8865f59075bbbced08d97b183fb3b48 с сообщением
Lab055: audit received signal energy across symbol rates.
В коммите семь файлов только Lab055: анализатор, тесты, отчёт, два JSON,
запись 046 и раздел индекса. Предшествующие изменения Lab054 сохранены
незакоммиченными. Перед коммитом quick_gate 823/28/13 PASS, pytest 823 passed.
Push не выполнялся. Исторический Lab054 NOT_A, 0/16, не изменён;
повторная проверка SHA-256 всех 34 входов Lab055 не выявила изменений.
Прямой выбор пользователя — эфирный опыт вместо предпочтительного кабеля
из приложения. Подготовлен один TX-файл ABAB: 20/500/20/500 ксимв/с,
прежние PRBS7/BPSK/RRC, Fs 8 Мвыб/с и цифровая несущая +50 кГц.
Созданы experiments/lab056_abab_energy.py, lab056_capture_runner.py,
tests/test_lab056_preparation.py, docs/lab056_current_state.md и малые
результаты data/processed/lab056. Большие CS8 находятся в data/raw/lab056.
TX RMS всего сегмента A/B: 44,004413/44,001925 код; разница мощности
−0,000491 дБ. Активные участки отдельно: −0,042606 дБ; клиппинг 0.
TX 10 588 544 отсчёта, 1,323568 с; RX планируется 3 с с упреждением 0,5 с.
A1=A2 и B1=B2 побайтно. Пять gaps по 0,1 с содержат нулевой цифровой IQ
при работающем TX-процессе: это не доказательство выключенного RF.
Анализатор сохраняет полную и AC-мощность RX, локальный фон, устойчивую
разность мощностей, комплексный коэффициент передачи, согласованную
компоненту, остаток и интегральный спектр. Мощность и энергия символа
выводятся отдельно. RX не выравнивается по амплитуде после приёма.
Искусственные записи с одинаковым усилением и падением B на 6,020600 дБ
обработаны успешно: оценки разницы коэффициента передачи соответственно
+0,001786/−0,002894 дБ и −6,014144/−6,022175 дБ для двух пар.
Это программные проверки, не аппаратный результат.
Подготовлен RX-first runner с единственной конечной TX-попыткой, без повтора,
проверкой процессов, SHA, размеров, счётчиков потерь и cleanup. Common clock
проверен только mock-тестами; реальные reg29/CLKIN сейчас NOT_PERFORMED.
Частота не назначена: 1400 МГц пересекает запрещённую для излучения полосу
1400–1427 МГц (ITU RR2024, п. 5.340). Для будущего опыта нужны допустимая
частота/полоса, страна и подтверждение антенн/неизменной геометрии.
Эфирный ABAB измеряет весь путь, включая антенны и распространение;
он не изолирует внутренние аппаратные потери HackRF и не измеряет dBm.
Verifier независимо пересчитал коэффициенты, проверил TX и mock-отказы.
Воспроизведённая ошибка разбора счётчиков с префиксом RX/TX исправлена;
повторная проверка закрыла находку. Остаточных воспроизведённых находок нет.
34 целевых теста PASS; итоговые quick_gate 857/30/13 PASS,
pytest 857 passed, 0 failed, 96,50 с; git diff --check PASS.
HackRF не запускался, RF TX=0. Lab056 не коммитилась; push не выполнялся.
Следующий этап: STOP, согласование допустимой эфирной конфигурации и отдельное
разрешение на конкретный однократный RF-сеанс перед аппаратным preflight.
# Запись 048
## Дата
10 сентября 2026 года
## Тема
Lab056 — принятое пользователем изменение методики на контролируемый OTA
## Выполнено
Пользователь сообщил об отсутствии необходимого SMA-переходника и осознанно
выбрал эфирный ABAB. Кабельный TX → 30 дБ → RX отложен до получения
переходника как отдельная проверка. Центральную частоту определяет пользователь:
RF_FREQ = USER_AUTHORIZED_RF_HZ. Конкретное значение пока не сообщено;
обязательные поля страны/регуляторной полосы убраны из параметров runner,
национальная регуляторика повторно не исследовалась. В отчёте подготовлены
поля антенн, расстояния, ориентации/поляризации, LOS/NLOS и частоты.
Готовый ABAB waveform, метаданные, прежние результаты и исходная сводка
857 passed сохранены. RMS A/B 44,004413/44,001925 код, разница мощности
−0,000491 дБ, TX 1,323568 с, RX 3 с. Усиления TX10/RX24/24,
Fs 8 Мвыб/с и несущая +50 кГц не менялись; внешних PA/LNA нет.
В runner оформлен отдельный RX-only preflight: common clock с обоими
reg29=0x26 и SLAVE CLKIN=DETECTED, 0,1 с приёма фона для проверки уровня,
сохранение результата и cleanup, затем STOP перед отдельным разрешением TX.
При частоте UNKNOWN аппаратных действий нет. capture_once требует PASS
этой конфигурации, отдельной авторизации и свежей проверки опоры.
При клиппинге до TX — RF TX=0; при обнаружении только в записи ABAB —
RF TX=1 и отказ без повтора. Счётчик уже выполненной передачи не обнуляется.
Ни частота, ни gains автоматически не подбираются.
Добавлен явный комплексный h прежней модели, сводки X/Y/Z и повторяемость
A2/A1, B2/B1. Те же искусственные IQ прочитаны повторно с новыми именами
выходных JSON. Для равного усиления X/Y/Z −0,046404/−0,000553/−14,025804 дБ;
для ослабления B на 6,020600 дБ: −6,039124/−6,018156/−20,018524 дБ.
Это программные значения, не аппаратный результат Lab056. Эфирный опыт
будет характеризовать весь путь, а не отдельно внутренние потери HackRF.
44 целевых теста PASS. Итоговые quick_gate 867/30/13 PASS,
pytest 867 passed, 0 failed, 89,32 с; git diff --check PASS.
Verifier независимо исполнил 44 случая с данными в памяти, без записи
и оборудования; воспроизведённых находок нет. Исторический Lab054 NOT_A
сохранён. HEAD 5add95ecb8865f59075bbbced08d97b183fb3b48, Lab056 не коммитилась,
push не выполнялся. Аппаратных команд нет, RF TX=0.
Следующий этап: получить RF_FREQ, выполнить разрешённый RX-only аппаратный
preflight, остановиться с кратким отчётом и ждать отдельного разрешения
на один ABAB-сеанс. Кабельный контроль и PA автоматически не добавляются.
# Запись 049
## Дата
10 сентября 2026 года
## Тема
Lab056 — аппаратный common-clock и RX-only preflight на частоте пользователя
## Выполнено
Пользователь задал RF_FREQ=1400000000 Гц только для текущей лабораторной
проверки. Это не рабочая частота SDR_Rover; ориентир полевых работ около
180 МГц и варианты 50/144 МГц здесь не исследуются. Задана геометрия OTA:
имеющиеся антенны 1400 МГц, LOS, неподвижное оборудование, одинаковые
поляризация/ориентация, расстояние примерно 3–5 м. Точные модели антенн
и измеренное расстояние не сообщены; описание сохранено без выдуманных данных.
Выполнен только разрешённый аппаратный preflight. Инвентаризация подтвердила
MASTER ...323b97d3 и SLAVE ...2e5327d3. После коротких RX-start обоих устройств
MASTER reg29=0x26, SLAVE reg29=0x26, SLAVE CLKIN=DETECTED; опора обнаружена
первым запросом через 67,787 мс ожидания. Затем SLAVE записал 800 000
комплексных отсчётов, 0,1 с, на той же частоте, Fs 8 Мвыб/с, RX24/24 дБ,
RF amplifier/Bias-Tee OFF. Внешних PA/LNA нет. TX не запускался.
CS8 1 600 000 байт, SHA-256
c4257f26e4cc8562eb32228f9a50fdeb055879c928af0744cdf4a81cf7cd085b.
RMS 2,203152571 код, мощность 4,85388125 код², AC-мощность 0,6658039582 код²,
максимумы абсолютных I/Q 4/6 код. Клиппинг 0 из 800 000 отсчётов, overruns 0.
Отсутствие клиппинга фона без TX не гарантирует отсутствие перегрузки
при будущем включении TX. После проверки CLKOUT обеих плат выключены,
процессы CLEAR до/после, cleanup без ошибок.
Сохранены исходный RX, preflight_result/attempt, план и журнал фактических
команд. Три hackrf_transfer-команды были только RX: два коротких запуска
для опоры и запись фона; TX-команд 0. Отчёт docs/lab056_rx_preflight.md.
Verifier только по сохранённым файлам подтвердил 12 команд, регистры,
CLKIN, параметры и cleanup; пересчитал SHA и мощность/клиппинг вторым
способом. Воспроизведённых расхождений нет, PASS, аппаратных команд не было.
Код и готовый TX не менялись; SHA совпадают с проверенной версией.
Последние программные проверки этой версии: 44 целевых теста,
quick_gate 867/30/13 PASS, pytest 867 passed. Git diff --check PASS.
Lab054 NOT_A сохранён. HEAD 5add95ecb8865f59075bbbced08d97b183fb3b48;
Lab056 не коммитилась, push не выполнялся. RF TX=0.
Следующий этап: STOP с техническим READY FOR SINGLE ABAB TX=YES;
дождаться отдельного разрешения пользователя на единственный ABAB TX.
# Запись 050
## Дата
10 сентября 2026 года
## Тема
Lab056 — единственный OTA ABAB 20/500 ксимв/с; большой провал не воспроизведён
## Выполнено
После отдельного явного разрешения пользователя выполнен один конечный
ABAB TX с неизменным ранее подготовленным файлом 10 588 544 отсчёта,
1,323568 с. RX запущен первым и сохранил 24 000 000 комплексных отсчётов,
48 000 000 байт, 3 с. Частота 1400000000 Гц только для Lab056,
Fs 8 Мвыб/с, TX10/RX24/24 дБ, несущая +50 кГц, прежние PRBS7/BPSK/RRC;
RF amplifier/Bias-Tee OFF, внешних PA/LNA нет. Геометрия по заданию
пользователя: имеющиеся антенны 1400 МГц, LOS, примерно 3–5 м,
неподвижность и одинаковые поляризация/ориентация. Параметры между A/B
не менялись. Код, пороги, timing, FEC/CRC и цифровой TX не менялись.
Свежая опора перед сеансом PASS: оба reg29=0x26, SLAVE CLKIN=DETECTED
через 137,359 мс штатного ожидания. По журналу RX стартовал на 0,517342 с
раньше TX. RX overruns=0, TX underruns=0, клиппинг 0 до и после ABAB.
Полный RX RMS 2,235380477 код, максимумы абсолютных I/Q 5/6 код.
Все 14 аппаратных команд сохранены; среди них один TX без повтора.
CLKOUT после опыта выключены, процессы CLEAR, cleanup без ошибок.
Подготовленный анализатор нашёл четыре сегмента с корреляцией 0,384366.
Начала A1/B1/A2/B2 в RX: 5 254 791 / 7 709 991 / 10 149 062 /
12 604 263; общий сдвиг относительно TX расходится лишь на один отсчёт.
Все активные окна 0,2048 с, порядок ABAB и gaps подтверждены.
Полезные мощности после фона A1/B1/A2/B2:
0,462680958 / 0,476010015 / 0,414075173 / 0,423007891 код².
Согласованные компоненты: 0,312320987 / 0,297815525 /
0,299566893 / 0,285847043 код². Raw/AC/фон/комплексный h/остаток/спектры
по каждому сегменту приведены в docs/lab056_rf_result.md и rf_analysis.json.
Итоги по средним линейным мощностям:
X, принятая мощность B/A после вычитания фона: +0,108895 дБ;
Y, коэффициент передачи abs(h)² B/A: −0,163042 дБ;
Z, энергия символа этой же принятой мощности: −13,870505 дБ.
Повтор A2/A1 по мощности −0,482024 дБ, B2/B1 −0,512676 дБ;
по abs(h)² −0,181074/−0,178136 дБ. Парные оценки B/A близки.
Прежний провал порядка 15,41 дБ в одной короткой OTA-сессии не воспроизвёлся.
Это не выделяет собственные потери HackRF из антенн и распространения
и не объясняет причину старого различия раздельных опытов. Кабельный контроль
после SMA-переходника остаётся отдельным необходимым этапом для такого
разделения. PA не назначен. Packet/PHY PASS, видео и FEC не исследовались.
Исторический Lab054 NOT_A, 0/16 сохранён; 34 исходных SHA Lab055 не изменились.
Verifier только по сохранённым файлам независимо подтвердил команды,
SHA/размеры/клиппинг, четыре начала, мощности/фон и комплексный МНК;
расчёты согласованы до 1e−12, воспроизведённых находок нет.
RX SHA e336dd653fa554058052fcf851e9f2462f66b8cf7c710815ca2b9a09ffc23ad4.
TX SHA 5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600.
Сохранены raw IQ, журналы, машинная сводка rf_result.json и график
data/processed/lab056/rf_overview.png. Код прежний: последние проверки
44 целевых теста, quick_gate 867/30/13, pytest 867 passed; diff check PASS.
HEAD 5add95ecb8865f59075bbbced08d97b183fb3b48. Lab056 не коммитилась,
push не выполнялся. Выполненных TX=1, сейчас RF TX=0, повторов нет.
Следующий этап: STOP и рассмотрение отчёта пользователем; никакого
автоматического второго RF-сеанса, кабельного контроля или добавления PA.

View File

@@ -0,0 +1,76 @@
{
"date": "2026-09-09",
"stage": "SOFTWARE_READY_RF_CONFIGURATION_PENDING",
"lab055_commit": "5add95ecb8865f59075bbbced08d97b183fb3b48",
"new_head": "5add95ecb8865f59075bbbced08d97b183fb3b48",
"lab055_commit_files": 7,
"lab055_inputs_sha256_rechecked": 34,
"lab055_inputs_changed": [],
"lab054_official_result": "NOT_A",
"lab054_packets": "0/16",
"rf_tx": 0,
"hardware_commands_executed": 0,
"hardware_common_clock_preflight": "NOT_PERFORMED",
"mock_common_clock": "PASS",
"rf_frequency_hz": null,
"physical_path": "OTA_REQUESTED_FREQUENCY_AND_GEOMETRY_PENDING",
"waiting_for_explicit_rf_authorization": true,
"lab056_committed": false,
"push_performed": false,
"targeted_tests": {
"passed": 34,
"failed": 0
},
"quick_gate": {
"command": "python tools/quick_gate.py",
"passed": true,
"core_tests": 857,
"functional_labs": 30,
"skipped_labs": 13
},
"pytest": {
"command": "python -m pytest tests/ -q -p no:cacheprovider",
"passed": 857,
"failed": 0,
"elapsed_seconds": 96.5
},
"diff_check": "PASS",
"verifier": {
"mode": "READ_ONLY_SOFTWARE",
"remaining_reproduced_findings": 0,
"counter_parser_finding": "FIXED_AND_REPRODUCIBLY_CLOSED"
},
"synthetic_equal": {
"passed": true,
"expected_gain_difference_db": 0.0,
"measured_pairs_db": [
0.0017861283874782213,
-0.0028938012026523408
]
},
"synthetic_minus6db": {
"passed": true,
"expected_gain_difference_db": -6.020599913279624,
"measured_pairs_db": [
-6.014143555521402,
-6.022174975174189
]
},
"power_units": "RX_CODE_SQUARED_NOT_CALIBRATED_RF_WATTS",
"gap_is_zero_digital_iq_not_rf_off": true,
"ota_scope": "ENTIRE_TX_ANTENNA_PROPAGATION_RX_PATH_NOT_ISOLATED_HACKRF",
"artifact_sha256": {
"experiments/lab056_abab_energy.py": "a36d277998b9b07c97eed0cfedec98ffed1ee2ea44e150f870b191b9a2d957c7",
"experiments/lab056_capture_runner.py": "4ccfec4bfd123602d61dba5107e5ac7763326aaaf6cdb2b1e7dbcfdfcb059488",
"tests/test_lab056_preparation.py": "74b428096a28c91ce603d3e13ad03ef9526a1a5a4be2d5001ec61441a4d1da1e",
"experiments/lab054_hardware_control.py": "199046f939d39eaa7b2a5769149dd43052b353bba79b4cccba60b7ab72071101",
"experiments/lab055_energy_audit.py": "ac4da1e31bc20b1740aebfab31f1368adb4ed6afe845ba8c4e45f579ea0f3753",
"data/processed/lab056/preparation.json": "535375d08a0699ac4be31992ddbb6c5d175b3156a0a3b508b45d7cb567c24071",
"data/processed/lab056/software_equal.json": "2c8bd05a0a1d488961a9a353c3ba9bf624e929792b66371bdb2b190b2cf22ca8",
"data/processed/lab056/software_minus6db.json": "c015de2d5238d5d3c1ea9d56b0e4a5da200f2e0f14e3640fa27a396ffd489f67",
"data/raw/lab056/tx_abab.cs8": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"data/raw/lab056/tx_metadata.json": "535375d08a0699ac4be31992ddbb6c5d175b3156a0a3b508b45d7cb567c24071",
"data/raw/lab056/synthetic_equal.cs8": "a88afcc1bbeb50092f9f5baca6eb607d25daf508e0fafcdcada318818d9ffd62",
"data/raw/lab056/synthetic_minus6db.cs8": "a189c1ffaf029983d41f7d87e262cbbc28ba740fc8915a75e41ee0adec9c1fe6"
}
}

View File

@@ -0,0 +1,135 @@
{
"date": "2026-09-10",
"stage": "OTA_CORRECTION_READY_WAITING_USER_RF_FREQ",
"head": "5add95ecb8865f59075bbbced08d97b183fb3b48",
"method": "CONTROLLED_OTA_ABAB",
"reason": "USER_REPORTED_MISSING_SMA_ADAPTER",
"conducted_test": "DEFERRED_SEPARATE_CONTROL_AFTER_ADAPTER",
"rf_frequency_hz": null,
"rf_frequency_source": "USER_AUTHORIZED_RF_HZ",
"country_required": false,
"national_regulatory_research_performed": false,
"geometry": {
"tx_antenna": null,
"rx_antenna": null,
"distance_m": null,
"orientation": null,
"los_nlos": null
},
"rf_tx": 0,
"hardware_commands_executed": 0,
"hardware_common_clock": "NOT_PERFORMED",
"next_step": "USER_RF_FREQ_THEN_RX_ONLY_PREFLIGHT_THEN_STOP_BEFORE_TX_AUTHORIZATION",
"preflight_rx_seconds": 0.1,
"rx_capture_seconds": 3.0,
"tx_seconds": 1.323568,
"preflight_background_does_not_prove_no_clipping_during_tx": true,
"saturation_policy": {
"before_tx": "STOP_WITH_TX_COUNT_0",
"during_tx": "PRESERVE_ACTUAL_TX_COUNT_1_AND_STOP_NO_RETRY"
},
"baseline_acceptance_preserved": {
"path": "data/processed/lab056/acceptance.json",
"sha256": "ba48c2bb22947333aee99d5e72b0c6b661e617b1047b2672cb2d4c21ef721320",
"pytest_passed": 857
},
"targeted_tests": {
"passed": 44,
"failed": 0
},
"quick_gate": {
"passed": true,
"core_tests": 867,
"functional_labs": 30,
"skipped_labs": 13
},
"pytest": {
"command": "python -m pytest tests/ -q -p no:cacheprovider",
"passed": 867,
"failed": 0,
"elapsed_seconds": 89.32
},
"verifier": {
"mode": "READ_ONLY_IN_MEMORY",
"cases_passed": 44,
"remaining_reproduced_findings": 0
},
"original_segment_measurements_and_pairs_unchanged": true,
"synthetic_comparison_summaries": {
"equal": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": -0.0012085532551280598,
"background_difference_db": -0.0017255178918689754,
"received_power_difference_db": -0.005652782408264756,
"channel_gain_difference_db": -0.0023081300526506692,
"matched_component_difference_db": -0.00230813005264874
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.00036367664985406537,
"background_difference_db": -0.011492673362299905,
"received_power_difference_db": 0.004534904727870617,
"channel_gain_difference_db": -0.006988059642781434,
"matched_component_difference_db": -0.006988059642780951
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": -0.04640374115997541,
"channel_gain_difference_db": -0.000552584218408658,
"energy_per_symbol_difference_db": -14.025803827880353,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
},
"minus6db": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": -0.0012085532551280598,
"background_difference_db": -0.0017255178918689754,
"received_power_difference_db": -0.005652782408264756,
"channel_gain_difference_db": -0.0023081300526506692,
"matched_component_difference_db": -0.00230813005264874
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.0016044036249902335,
"background_difference_db": -0.011492673362299905,
"received_power_difference_db": 0.016513577295241087,
"channel_gain_difference_db": -0.0103395497054386,
"matched_component_difference_db": -0.010339549705438118
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": -6.039124271880495,
"channel_gain_difference_db": -6.018156341674954,
"energy_per_symbol_difference_db": -20.01852435860087,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
}
},
"lab055_inputs_rechecked": 34,
"lab055_inputs_changed": [],
"lab054_official": "NOT_A, 0/16",
"preserved_artifact_sha256": {
"experiments/lab054_hardware_control.py": "199046f939d39eaa7b2a5769149dd43052b353bba79b4cccba60b7ab72071101",
"experiments/lab055_energy_audit.py": "ac4da1e31bc20b1740aebfab31f1368adb4ed6afe845ba8c4e45f579ea0f3753",
"data/processed/lab056/preparation.json": "535375d08a0699ac4be31992ddbb6c5d175b3156a0a3b508b45d7cb567c24071",
"data/processed/lab056/software_equal.json": "2c8bd05a0a1d488961a9a353c3ba9bf624e929792b66371bdb2b190b2cf22ca8",
"data/processed/lab056/software_minus6db.json": "c015de2d5238d5d3c1ea9d56b0e4a5da200f2e0f14e3640fa27a396ffd489f67",
"data/raw/lab056/tx_abab.cs8": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"data/raw/lab056/tx_metadata.json": "535375d08a0699ac4be31992ddbb6c5d175b3156a0a3b508b45d7cb567c24071",
"data/raw/lab056/synthetic_equal.cs8": "a88afcc1bbeb50092f9f5baca6eb607d25daf508e0fafcdcada318818d9ffd62",
"data/raw/lab056/synthetic_minus6db.cs8": "a189c1ffaf029983d41f7d87e262cbbc28ba740fc8915a75e41ee0adec9c1fe6"
},
"new_artifact_sha256": {
"experiments/lab056_abab_energy.py": "323408b3bc1aa02c8b2f2855cd6295ac2efd99f13afec2ca0c872aa013a78f96",
"experiments/lab056_capture_runner.py": "24c17060b8b3e3f6899f9e922ae818e7ae2efba456c5f5cada7c69b5e5429334",
"tests/test_lab056_preparation.py": "d48aef79ae2455ca397862082a89f4c5f9a7e4d678e9b464c8489dd0e319a5ac",
"docs/lab056_current_state.md": "1accf50e4b67ec60fd6e8f041048d4b4b4a04551ebb51e43dafb0d52e40c7e35",
"data/processed/lab056/software_equal_ota_summary.json": "2a04b074eb9f59e726fd9cb70744498384ce4a5aa3c3bd7e92ae21f4e55d05a1",
"data/processed/lab056/software_minus6db_ota_summary.json": "01eb38bdc7f3bbd5bf468959cf7395a2b865318d6340cc9a70683576606cd3e0"
},
"lab056_commit": false,
"push": false,
"diff_check": "PASS"
}

View File

@@ -0,0 +1,173 @@
{
"stage": "PREPARATION_ONLY",
"rf_tx": 0,
"rf_frequency_hz": null,
"physical_path": "OTA_REQUESTED_FREQUENCY_AND_GEOMETRY_PENDING",
"sample_rate_hz": 8000000,
"carrier_hz": 50000,
"tx_samples": 10588544,
"tx_bytes": 21177088,
"tx_seconds": 1.323568,
"rx_samples": 24000000,
"rx_seconds": 3.0,
"rx_lead_seconds": 0.5,
"gap_seconds": 0.1,
"gap_is_zero_digital_iq_not_rf_off": true,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"start": 800000,
"samples": 1655200,
"active_start": 808400,
"active_samples": 1638400,
"whole": {
"samples": 1655200,
"mean_i": -3.624939584340261e-06,
"mean_q": -8.458192363460609e-06,
"power_code2": 1936.3883820686322,
"ac_power_code2": 1936.3883820685476,
"rms_code": 44.00441321127498,
"peak_magnitude_code": 69.29646455628166,
"max_abs_i": 68.0,
"max_abs_q": 69.0,
"papr_db": 3.9442968742176006,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.09681941910343161
},
"active": {
"samples": 1638400,
"mean_i": 2.99072265625e-05,
"mean_q": -0.0008270263671875,
"power_code2": 1956.0524084472656,
"ac_power_code2": 1956.0524077623986,
"rms_code": 44.22728126900031,
"peak_magnitude_code": 69.29646455628166,
"max_abs_i": 68.0,
"max_abs_q": 69.0,
"papr_db": 3.9004166907590005,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.09780262042236328
}
},
{
"label": "B1",
"symbol_rate": 500000,
"start": 3255200,
"samples": 1639072,
"active_start": 3255536,
"active_samples": 1638400,
"whole": {
"samples": 1639072,
"mean_i": 0.00011042833993869702,
"mean_q": -0.002146336463559868,
"power_code2": 1936.1693793805275,
"ac_power_code2": 1936.169374761573,
"rms_code": 44.00192472359053,
"peak_magnitude_code": 68.6804193347711,
"max_abs_i": 66.0,
"max_abs_q": 68.0,
"papr_db": 3.8672252879366917,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.003872338758761055
},
"active": {
"samples": 1638400,
"mean_i": -8.544921875e-06,
"mean_q": -0.002210693359375,
"power_code2": 1936.9563952636718,
"ac_power_code2": 1936.9563903764335,
"rms_code": 44.010866786098084,
"peak_magnitude_code": 68.6804193347711,
"max_abs_i": 66.0,
"max_abs_q": 68.0,
"papr_db": 3.865460322480824,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0038739127905273434
}
},
{
"label": "A2",
"symbol_rate": 20000,
"start": 5694272,
"samples": 1655200,
"active_start": 5702672,
"active_samples": 1638400,
"whole": {
"samples": 1655200,
"mean_i": -3.624939584340261e-06,
"mean_q": -8.458192363460609e-06,
"power_code2": 1936.3883820686322,
"ac_power_code2": 1936.3883820685476,
"rms_code": 44.00441321127498,
"peak_magnitude_code": 69.29646455628166,
"max_abs_i": 68.0,
"max_abs_q": 69.0,
"papr_db": 3.9442968742176006,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.09681941910343161
},
"active": {
"samples": 1638400,
"mean_i": 2.99072265625e-05,
"mean_q": -0.0008270263671875,
"power_code2": 1956.0524084472656,
"ac_power_code2": 1956.0524077623986,
"rms_code": 44.22728126900031,
"peak_magnitude_code": 69.29646455628166,
"max_abs_i": 68.0,
"max_abs_q": 69.0,
"papr_db": 3.9004166907590005,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.09780262042236328
}
},
{
"label": "B2",
"symbol_rate": 500000,
"start": 8149472,
"samples": 1639072,
"active_start": 8149808,
"active_samples": 1638400,
"whole": {
"samples": 1639072,
"mean_i": 0.00011042833993869702,
"mean_q": -0.002146336463559868,
"power_code2": 1936.1693793805275,
"ac_power_code2": 1936.169374761573,
"rms_code": 44.00192472359053,
"peak_magnitude_code": 68.6804193347711,
"max_abs_i": 66.0,
"max_abs_q": 68.0,
"papr_db": 3.8672252879366917,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.003872338758761055
},
"active": {
"samples": 1638400,
"mean_i": -8.544921875e-06,
"mean_q": -0.002210693359375,
"power_code2": 1936.9563952636718,
"ac_power_code2": 1936.9563903764335,
"rms_code": 44.010866786098084,
"peak_magnitude_code": 68.6804193347711,
"max_abs_i": 66.0,
"max_abs_q": 68.0,
"papr_db": 3.865460322480824,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0038739127905273434
}
}
],
"whole_power_difference_db": -0.000491208472157679,
"active_power_difference_db": -0.0426064264748898
}

View File

@@ -0,0 +1,530 @@
{
"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX",
"rf_tx": 0,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"rx_sha256": "e336dd653fa554058052fcf851e9f2462f66b8cf7c710815ca2b9a09ffc23ad4",
"alignment": {
"start": 4454784,
"correlation": 0.3843661479500724,
"coarse_cfo_hz": 0.0,
"stride": 32
},
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9375872802734375,
"mean_q": 1.827298583984375,
"power_code2": 5.303053588867187,
"ac_power_code2": 1.084963565705345,
"rms_code": 2.3028359882690705,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.88257840909542,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002651526794433594,
"band_27000_power": 0.3472643231791639,
"band_675000_power": 0.5082992616247881,
"power_standard_error": 0.020810438717554197
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9454104166666667,
"mean_q": 1.8225354166666667,
"power_code2": 4.857945833333333,
"ac_power_code2": 0.642509632387152,
"rms_code": 2.204074824803671,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 5.0,
"max_abs_q": 5.0,
"papr_db": 7.759453298373341,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00024289729166666665,
"band_27000_power": 0.011441788935577513,
"band_675000_power": 0.1346566488652154,
"power_standard_error": 0.011146582753825698
},
{
"samples": 480000,
"mean_i": 0.9274020833333333,
"mean_q": 1.8395416666666666,
"power_code2": 4.84604375,
"ac_power_code2": 0.6020555824262157,
"rms_code": 2.2013731510127945,
"peak_magnitude_code": 5.0,
"max_abs_i": 3.0,
"max_abs_q": 4.0,
"papr_db": 7.125526780497051,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002423021875,
"band_27000_power": 0.009082996916230657,
"band_675000_power": 0.10440265872939565,
"power_standard_error": 0.005986244399660487
}
],
"background_power": 0.6222826074066838,
"background_subtracted_raw": 0.46268095829866107,
"background_subtracted_uncertainty": 0.029702310624809113,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.46268095829866107,
"channel_gain_complex": {
"real": -0.008543898749645305,
"imag": 0.009310849226837265,
"phase_rad_at_active_start": 2.3132658926165255
},
"channel_gain_power": 0.0001596901191690867,
"measurement": {
"frame": 1,
"start": 5254791,
"alignment_adjustment": 7,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9375872802734375,
"mean_q": 1.827298583984375,
"power_code2": 5.303053588867187,
"ac_power_code2": 1.084963565705345,
"rms_code": 2.3028359882690705,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.88257840909542,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002651526794433594
},
"cfo_hz": -0.028056675950692833,
"gain_abs": 0.012636855588677378,
"dc_i": 0.9388651889450522,
"dc_q": 1.8271201059862494,
"train_signal_power": 0.312403496916226,
"train_residual_power": 0.722356125700177,
"train_actual_ac_power": 1.0347596226164035,
"train_cross_power": 7.854827899222983e-17,
"heldout_signal_power": 0.31232098749561993,
"heldout_residual_power": 0.8215137283109276,
"heldout_actual_ac_power": 1.135170838604222,
"heldout_cross_power": 0.0013361227976749755,
"heldout_residual_band_27000_power": 0.03428707776367196,
"heldout_residual_band_675000_power": 0.18938129777540513,
"heldout_residual_band_1000000_power": 0.21334080727941662,
"heldout_residual_matched_power": 11.421082264513538,
"effective_signal_to_residual_db": -4.200136561843322
}
},
{
"label": "B1",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9203680419921875,
"mean_q": 1.8407562255859375,
"power_code2": 5.328170166015625,
"ac_power_code2": 1.0927093512617043,
"rms_code": 2.30828294756419,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 9.266344273087686,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.0656340332031251e-05,
"band_27000_power": 0.029697349664473127,
"band_675000_power": 0.5128322432606733,
"power_standard_error": 0.031085529715571047
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9274020833333333,
"mean_q": 1.8395416666666666,
"power_code2": 4.84604375,
"ac_power_code2": 0.6020555824262157,
"rms_code": 2.2013731510127945,
"peak_magnitude_code": 5.0,
"max_abs_i": 3.0,
"max_abs_q": 4.0,
"papr_db": 7.125526780497051,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 9.692087499999999e-06,
"band_27000_power": 0.009082996916230657,
"band_675000_power": 0.10440265872939565,
"power_standard_error": 0.005986244399660487
},
{
"samples": 480000,
"mean_i": 0.9213041666666667,
"mean_q": 1.8316708333333334,
"power_code2": 4.8351625,
"ac_power_code2": 0.6313430907986115,
"rms_code": 2.198900293328463,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 3.0,
"max_abs_q": 5.0,
"papr_db": 7.3056227355026895,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 9.670325e-06,
"band_27000_power": 0.011430099601181243,
"band_675000_power": 0.13125486016140642,
"power_standard_error": 0.0037407319242408464
}
],
"background_power": 0.6166993366124136,
"background_subtracted_raw": 0.4760100146492907,
"background_subtracted_uncertainty": 0.03454282477879906,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.4760100146492907,
"channel_gain_complex": {
"real": -0.008089659584664989,
"imag": 0.009397483555426113,
"phase_rad_at_active_start": 2.2815457153366783
},
"channel_gain_power": 0.00015375528937026635,
"measurement": {
"frame": 1,
"start": 7709991,
"alignment_adjustment": 7,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9203680419921875,
"mean_q": 1.8407562255859375,
"power_code2": 5.328170166015625,
"ac_power_code2": 1.0927093512617043,
"rms_code": 2.30828294756419,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 9.266344273087686,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.0656340332031251e-05
},
"cfo_hz": -0.0731458112990823,
"gain_abs": 0.012399810053797854,
"dc_i": 0.920656229639791,
"dc_q": 1.8411261312835556,
"train_signal_power": 0.2978190572101343,
"train_residual_power": 0.7068527315140937,
"train_actual_ac_power": 1.0046717887242282,
"train_cross_power": -6.682154829462661e-17,
"heldout_signal_power": 0.29781552489257346,
"heldout_residual_power": 0.884789710339823,
"heldout_actual_ac_power": 1.1807473535638735,
"heldout_cross_power": -0.001857881668523386,
"heldout_residual_band_27000_power": 0.015187641333986188,
"heldout_residual_band_675000_power": 0.217181000785572,
"heldout_residual_band_1000000_power": 0.24523520739571264,
"heldout_residual_matched_power": 3.0703433988460103,
"effective_signal_to_residual_db": -4.72892729922049
}
},
{
"label": "A2",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9284002685546875,
"mean_q": 1.84388427734375,
"power_code2": 5.311703491210937,
"ac_power_code2": 1.0498672043230375,
"rms_code": 2.304713320829933,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 8.062450931004047,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002655851745605469,
"band_27000_power": 0.33592297470769195,
"band_675000_power": 0.500120184644421,
"power_standard_error": 0.01600143643844262
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.92130625,
"mean_q": 1.8316708333333334,
"power_code2": 4.83516875,
"ac_power_code2": 0.6313455020269103,
"rms_code": 2.198901714492942,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 3.0,
"max_abs_q": 5.0,
"papr_db": 7.30561712175391,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002417584375,
"band_27000_power": 0.011430090514699676,
"band_675000_power": 0.13125475431725353,
"power_standard_error": 0.0037385404799615833
},
{
"samples": 480000,
"mean_i": 0.9283375,
"mean_q": 1.8603020833333332,
"power_code2": 4.962772916666666,
"ac_power_code2": 0.6402385615060755,
"rms_code": 2.2277281963171958,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 8.357545135545596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00024813864583333333,
"band_27000_power": 0.011660806052520012,
"band_675000_power": 0.13208548597324482,
"power_standard_error": 0.009340294961573032
}
],
"background_power": 0.6357920317664929,
"background_subtracted_raw": 0.4140751725565446,
"background_subtracted_uncertainty": 0.017352868452820306,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.4140751725565446,
"channel_gain_complex": {
"real": -0.007000883443887156,
"imag": 0.010205712402071436,
"phase_rad_at_active_start": 2.172048716307076
},
"channel_gain_power": 0.000153168934628688,
"measurement": {
"frame": 1,
"start": 10149062,
"alignment_adjustment": 6,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9284002685546875,
"mean_q": 1.84388427734375,
"power_code2": 5.311703491210937,
"ac_power_code2": 1.0498672043230375,
"rms_code": 2.304713320829933,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 8.062450931004047,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002655851745605469
},
"cfo_hz": 0.005915202430533777,
"gain_abs": 0.012376143770524322,
"dc_i": 0.9271611521067152,
"dc_q": 1.8443065713552416,
"train_signal_power": 0.29964603349233376,
"train_residual_power": 0.7155398694209132,
"train_actual_ac_power": 1.0151859029132475,
"train_cross_power": 2.4270516152391507e-16,
"heldout_signal_power": 0.29956689346716014,
"heldout_residual_power": 0.7826844440402193,
"heldout_actual_ac_power": 1.084551933216437,
"heldout_cross_power": 0.002300595709057756,
"heldout_residual_band_27000_power": 0.03443073439045887,
"heldout_residual_band_675000_power": 0.19776523377923858,
"heldout_residual_band_1000000_power": 0.22176016276354857,
"heldout_residual_matched_power": 11.341896374078306,
"effective_signal_to_residual_db": -4.170928864706292
}
},
{
"label": "B2",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9302490234375,
"mean_q": 1.854871826171875,
"power_code2": 5.396551513671875,
"ac_power_code2": 1.0906387765392669,
"rms_code": 2.3230478931076464,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.806675296737527,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.0793103027343752e-05,
"band_27000_power": 0.030437416621842406,
"band_675000_power": 0.5333148504916424,
"power_standard_error": 0.03318734421277559
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9283375,
"mean_q": 1.8603020833333332,
"power_code2": 4.962772916666666,
"ac_power_code2": 0.6402385615060755,
"rms_code": 2.2277281963171958,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 8.357545135545596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 9.925545833333333e-06,
"band_27000_power": 0.011660770674569443,
"band_675000_power": 0.13208533141133466,
"power_standard_error": 0.009339092140283283
},
{
"samples": 480000,
"mean_i": 0.9385041666666667,
"mean_q": 1.8505104166666666,
"power_code2": 5.000202083333333,
"ac_power_code2": 0.6950232102907972,
"rms_code": 2.236113164250265,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.634104411823291,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.0000404166666666e-05,
"band_27000_power": 0.011507358850526561,
"band_675000_power": 0.12385207349410385,
"power_standard_error": 0.037784295029485354
}
],
"background_power": 0.6676308858984363,
"background_subtracted_raw": 0.42300789064083055,
"background_subtracted_uncertainty": 0.04722771590637799,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.42300789064083055,
"channel_gain_complex": {
"real": -0.006710536973189343,
"imag": 0.010126447128417442,
"phase_rad_at_active_start": 2.1560299377113283
},
"channel_gain_power": 0.000147576237913175,
"measurement": {
"frame": 1,
"start": 12604263,
"alignment_adjustment": 7,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9302490234375,
"mean_q": 1.854871826171875,
"power_code2": 5.396551513671875,
"ac_power_code2": 1.0906387765392669,
"rms_code": 2.3230478931076464,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.806675296737527,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.0793103027343752e-05
},
"cfo_hz": 0.0026425717900911143,
"gain_abs": 0.012148096061242479,
"dc_i": 0.9315836612485033,
"dc_q": 1.8553663362050554,
"train_signal_power": 0.28585043299602836,
"train_residual_power": 0.7245263851886631,
"train_actual_ac_power": 1.010376818184691,
"train_cross_power": -8.954642582992278e-17,
"heldout_signal_power": 0.2858470426337267,
"heldout_residual_power": 0.8807229722868642,
"heldout_actual_ac_power": 1.1709047864903603,
"heldout_cross_power": 0.004334771569769229,
"heldout_residual_band_27000_power": 0.018795072832326204,
"heldout_residual_band_675000_power": 0.3125053797217844,
"heldout_residual_band_1000000_power": 0.3566678823693195,
"heldout_residual_matched_power": 4.361909383453252,
"effective_signal_to_residual_db": -4.887056210122219
}
}
],
"pairs": [
{
"pair": 1,
"received_power_difference_db": 0.1233446340794126,
"channel_gain_difference_db": -0.1644798001918496,
"matched_component_difference_db": -0.20653835698711864,
"matched_component_symbol_energy_difference_db": -14.185938443707496
},
{
"pair": 2,
"received_power_difference_db": 0.09269277113182464,
"channel_gain_difference_db": -0.16154256719796828,
"matched_component_difference_db": -0.2036011239932389,
"matched_component_symbol_energy_difference_db": -14.183001210713615
}
],
"comparison_summary": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": 0.007078081316567549,
"background_difference_db": 0.0932741793444109,
"received_power_difference_db": -0.4820243490149181,
"channel_gain_difference_db": -0.18107353366707263,
"matched_component_difference_db": -0.1810735336670716
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.055382405794076286,
"background_difference_db": 0.34462938523123676,
"received_power_difference_db": -0.5126762119625052,
"channel_gain_difference_db": -0.17813630067319114,
"matched_component_difference_db": -0.17813630067319214
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": 0.10889530008758355,
"channel_gain_difference_db": -0.16304154709643187,
"energy_per_symbol_difference_db": -13.870504786632791,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
},
"outside_waveform_background": [
{
"start": 3654784,
"samples": 400000,
"mean_i": 0.9425475,
"mean_q": 1.823755,
"power_code2": 4.8400925,
"ac_power_code2": 0.6256144102187502,
"rms_code": 2.2000210226268293,
"peak_magnitude_code": 5.0,
"max_abs_i": 3.0,
"max_abs_q": 4.0,
"papr_db": 7.1308634705743845,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000242004625,
"band_27000_power": 0.011134411836977165,
"band_675000_power": 0.12535191126457693,
"power_standard_error": 0.002472279873801531
},
{
"start": 15443328,
"samples": 400000,
"mean_i": 0.938865,
"mean_q": 1.832065,
"power_code2": 4.891855,
"ac_power_code2": 0.6539253475500004,
"rms_code": 2.2117538289782614,
"peak_magnitude_code": 5.0,
"max_abs_i": 3.0,
"max_abs_q": 4.0,
"papr_db": 7.0846643308644,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00024459275,
"band_27000_power": 0.012745444722198074,
"band_675000_power": 0.13232609430446385,
"power_standard_error": 0.008612381752597367
}
],
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@@ -0,0 +1,336 @@
{
"date": "2026-09-10",
"stage": "OTA_ABAB_VALID_LARGE_DROP_NOT_REPRODUCED_STOP",
"rf_tx_current": 0,
"completed_tx_sessions": 1,
"automatic_tx_repeats": 0,
"authorization_consumed": true,
"hardware": {
"passed": true,
"rf_tx_count": 1,
"point": {
"frequency_hz": 1400000000,
"physical_path": "MASTER TX -> TX antenna -> free-space LOS -> RX antenna -> SLAVE RX; user-specified approximate distance 3-5 m; fixed equipment and cables",
"path_confirmed": true,
"tx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"rx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"distance_m": null,
"orientation": "Both stationary; same antenna orientation and polarization, per user",
"visibility": "LOS"
},
"process_before": {
"state": "CLEAR",
"attempts": [
{
"state": "CLEAR",
"mechanism": "psutil",
"matches": [],
"inspected": 302,
"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": 137.35900004394352,
"elapsed_ms": 137.35900004394352,
"polls": [
{
"query": 1,
"start_ms": 50.7242000894621,
"end_ms": 69.19760000891984,
"raw": "CLKIN status: no clock signal detected",
"state": "NOT_DETECTED",
"exit_code": 0
},
{
"query": 2,
"start_ms": 119.4211000110954,
"end_ms": 137.35900004394352,
"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.938690185546875,
"mean_q": 1.825347900390625,
"power_code2": 4.84039306640625,
"ac_power_code2": 0.6273588445037603,
"rms_code": 2.2000893314604864,
"peak_magnitude_code": 4.47213595499958,
"max_abs_i": 3.0,
"max_abs_q": 4.0,
"papr_db": 6.161493654989252,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002420196533203125,
"saturation_detected": false,
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
},
"rx_sha256": "e336dd653fa554058052fcf851e9f2462f66b8cf7c710815ca2b9a09ffc23ad4",
"rx_level": {
"samples": 24000000,
"mean_i": 0.9305354583333333,
"mean_q": 1.8321060833333334,
"power_code2": 4.996925875,
"ac_power_code2": 0.7744169351973671,
"rms_code": 2.235380476563218,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 9.545096066616024,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00024984629375,
"saturation_detected": false,
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
},
"process_after": {
"state": "CLEAR",
"attempts": [
{
"state": "CLEAR",
"mechanism": "psutil",
"matches": [],
"inspected": 302,
"errors": []
}
]
},
"cleanup_errors": []
},
"rx_bytes": 48000000,
"rx_samples": 24000000,
"rx_seconds": 3.0,
"tx_bytes": 21177088,
"tx_samples": 10588544,
"tx_waveform_seconds": 1.323568,
"rx_overruns": 0,
"tx_underruns": 0,
"rx_clipped_samples": 0,
"rx_clipped_components": 0,
"all_four_segments_present": true,
"segmentation_start_spread_samples": 1,
"hardware_parameters_constant_between_segments": true,
"physical_geometry_source": "USER_DESCRIPTION_NOT_SOFTWARE_MEASUREMENT",
"audited_hardware_commands": 14,
"audited_tx_commands": 1,
"segments": [
{
"label": "A1",
"start_sample": 5254791,
"end_sample_exclusive": 6909991,
"start_seconds": 0.656848875,
"end_seconds": 0.863748875,
"inferred_sequence_start_sample": 4454791,
"active_samples": 1638400,
"raw_rx_power_code2": 5.303053588867187,
"rx_ac_power_code2": 1.084963565705345,
"background_power_code2": 0.6222826074066838,
"background_subtracted_signal_power_code2": 0.46268095829866107,
"background_subtracted_uncertainty_code2": 0.029702310624809113,
"background_subtracted_stable": true,
"complex_gain_h": {
"real": -0.008543898749645305,
"imag": 0.009310849226837265,
"phase_rad_at_active_start": 2.3132658926165255
},
"channel_gain_power": 0.0001596901191690867,
"matched_signal_component_power_code2": 0.31232098749561993,
"residual_power_code2": 0.8215137283109276,
"spectral_band_27000_power_code2": 0.3472643231791639,
"spectral_band_675000_power_code2": 0.5082992616247881
},
{
"label": "B1",
"start_sample": 7709991,
"end_sample_exclusive": 9349063,
"start_seconds": 0.963748875,
"end_seconds": 1.168632875,
"inferred_sequence_start_sample": 4454791,
"active_samples": 1638400,
"raw_rx_power_code2": 5.328170166015625,
"rx_ac_power_code2": 1.0927093512617043,
"background_power_code2": 0.6166993366124136,
"background_subtracted_signal_power_code2": 0.4760100146492907,
"background_subtracted_uncertainty_code2": 0.03454282477879906,
"background_subtracted_stable": true,
"complex_gain_h": {
"real": -0.008089659584664989,
"imag": 0.009397483555426113,
"phase_rad_at_active_start": 2.2815457153366783
},
"channel_gain_power": 0.00015375528937026635,
"matched_signal_component_power_code2": 0.29781552489257346,
"residual_power_code2": 0.884789710339823,
"spectral_band_27000_power_code2": 0.029697349664473127,
"spectral_band_675000_power_code2": 0.5128322432606733
},
{
"label": "A2",
"start_sample": 10149062,
"end_sample_exclusive": 11804262,
"start_seconds": 1.26863275,
"end_seconds": 1.47553275,
"inferred_sequence_start_sample": 4454790,
"active_samples": 1638400,
"raw_rx_power_code2": 5.311703491210937,
"rx_ac_power_code2": 1.0498672043230375,
"background_power_code2": 0.6357920317664929,
"background_subtracted_signal_power_code2": 0.4140751725565446,
"background_subtracted_uncertainty_code2": 0.017352868452820306,
"background_subtracted_stable": true,
"complex_gain_h": {
"real": -0.007000883443887156,
"imag": 0.010205712402071436,
"phase_rad_at_active_start": 2.172048716307076
},
"channel_gain_power": 0.000153168934628688,
"matched_signal_component_power_code2": 0.29956689346716014,
"residual_power_code2": 0.7826844440402193,
"spectral_band_27000_power_code2": 0.33592297470769195,
"spectral_band_675000_power_code2": 0.500120184644421
},
{
"label": "B2",
"start_sample": 12604263,
"end_sample_exclusive": 14243335,
"start_seconds": 1.575532875,
"end_seconds": 1.780416875,
"inferred_sequence_start_sample": 4454791,
"active_samples": 1638400,
"raw_rx_power_code2": 5.396551513671875,
"rx_ac_power_code2": 1.0906387765392669,
"background_power_code2": 0.6676308858984363,
"background_subtracted_signal_power_code2": 0.42300789064083055,
"background_subtracted_uncertainty_code2": 0.04722771590637799,
"background_subtracted_stable": true,
"complex_gain_h": {
"real": -0.006710536973189343,
"imag": 0.010126447128417442,
"phase_rad_at_active_start": 2.1560299377113283
},
"channel_gain_power": 0.000147576237913175,
"matched_signal_component_power_code2": 0.2858470426337267,
"residual_power_code2": 0.8807229722868642,
"spectral_band_27000_power_code2": 0.030437416621842406,
"spectral_band_675000_power_code2": 0.5333148504916424
}
],
"comparison_summary": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": 0.007078081316567549,
"background_difference_db": 0.0932741793444109,
"received_power_difference_db": -0.4820243490149181,
"channel_gain_difference_db": -0.18107353366707263,
"matched_component_difference_db": -0.1810735336670716
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.055382405794076286,
"background_difference_db": 0.34462938523123676,
"received_power_difference_db": -0.5126762119625052,
"channel_gain_difference_db": -0.17813630067319114,
"matched_component_difference_db": -0.17813630067319214
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": 0.10889530008758355,
"channel_gain_difference_db": -0.16304154709643187,
"energy_per_symbol_difference_db": -13.870504786632791,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
},
"pairs": [
{
"pair": 1,
"received_power_difference_db": 0.1233446340794126,
"channel_gain_difference_db": -0.1644798001918496,
"matched_component_difference_db": -0.20653835698711864,
"matched_component_symbol_energy_difference_db": -14.185938443707496
},
{
"pair": 2,
"received_power_difference_db": 0.09269277113182464,
"channel_gain_difference_db": -0.16154256719796828,
"matched_component_difference_db": -0.2036011239932389,
"matched_component_symbol_energy_difference_db": -14.183001210713615
}
],
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE",
"units": "RX_CODE_SQUARED_AND_CODE_SQUARED_SECONDS_NOT_CALIBRATED_WATTS_OR_JOULES",
"interpretation": {
"historical_15_41_db_drop_reproduced": false,
"isolated_hackrf_loss_established": false,
"scope": "ENTIRE_OTA_PATH_IN_THIS_SESSION",
"conducted_control_required_to_separate_hardware_and_channel": true,
"external_amplifier_necessity_established": false,
"packet_acceptance_tested": false
},
"verifier": {
"passed": true,
"mode": "SAVED_FILES_READ_ONLY_NO_HARDWARE",
"independent_methods": [
"INTEGER_RAW_MOMENTS",
"LOCAL_VARIANCE",
"WEIGHTED_PHASE_CFO",
"COMPLEX_LSTSQ"
],
"remaining_reproduced_findings": 0
},
"lab054_official": "NOT_A, 0/16",
"lab055_inputs_unchanged": 34,
"last_software_tests_same_code": {
"targeted": 44,
"pytest": 867,
"quick_gate": [
867,
30,
13
]
},
"code_or_tx_changed": false,
"head": "5add95ecb8865f59075bbbced08d97b183fb3b48",
"lab056_committed": false,
"push": false,
"stop": true,
"artifact_sha256": {
"data/raw/lab056/tx_abab.cs8": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"data/raw/lab056/tx_metadata.json": "535375d08a0699ac4be31992ddbb6c5d175b3156a0a3b508b45d7cb567c24071",
"data/raw/lab056/rx_stream.cs8": "e336dd653fa554058052fcf851e9f2462f66b8cf7c710815ca2b9a09ffc23ad4",
"data/raw/lab056/hardware_attempt.json": "24dfed564a260c6286233a21823cf13f5d19731adaa5bf6664debeddde634ffd",
"data/raw/lab056/capture_result.json": "107ff5b43cd98acbb6ea77631894a1ff2fcb95868702f2dafa73675b69a16bdd",
"data/raw/lab056/tx_process_result.json": "88e13edd733430b2a42dd176ff275f518be39e1733da6b904a03493b99354475",
"data/raw/lab056/rx_stdout.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"data/raw/lab056/rx_stderr.txt": "3b0c3dc749f353bad62f36b198745d9dd2850372ad2a0450ae645afad300361a",
"data/processed/lab056/rf_analysis.json": "21576f57910458a3fbe0090894a6ba61540b8a1a08094fca16cd06c46058d141",
"data/processed/lab056/rf_session_authorization.json": "52bf979934c2c27bee055b07e09685248d411a48b79f49b6f000b5e2c631e0c1",
"data/processed/lab056/rf_session_commands.json": "c816829a57f8613c29fa228ce1a03b93df79997e619d8dfaa827186c7820171b",
"data/processed/lab056/rf_overview.png": "c08a8dab99677afb46b0cc43dbf2360da47da35a03dc00fdec48618c7c794f4f",
"experiments/lab056_capture_runner.py": "24c17060b8b3e3f6899f9e922ae818e7ae2efba456c5f5cada7c69b5e5429334",
"experiments/lab056_abab_energy.py": "323408b3bc1aa02c8b2f2855cd6295ac2efd99f13afec2ca0c872aa013a78f96"
}
}

View File

@@ -0,0 +1,30 @@
{
"authorized_tx_sessions": 1,
"automatic_retry": false,
"point": {
"frequency_hz": 1400000000,
"physical_path": "MASTER TX -> TX antenna -> free-space LOS -> RX antenna -> SLAVE RX; user-specified approximate distance 3-5 m; fixed equipment and cables",
"path_confirmed": true,
"tx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"rx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"distance_m": null,
"orientation": "Both stationary; same antenna orientation and polarization, per user",
"visibility": "LOS"
},
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"tx_samples": 10588544,
"tx_seconds": 1.323568,
"rx_samples": 24000000,
"rx_seconds": 3.0,
"tx_gain_db": 10,
"rx_gain_db": [
24,
24
],
"amp": false,
"bias_tee": false,
"sample_rate_hz": 8000000,
"digital_carrier_hz": 50000,
"source": "Explicit user authorization in current turn",
"recorded_at_utc": "2026-09-10T09:32:24.185185+00:00"
}

View File

@@ -0,0 +1,262 @@
{
"tx_process_invocations": 1,
"automatic_retry": false,
"commands": [
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_info.exe"
],
"started_at_utc": "2026-09-10T09:32:24.697133+00:00",
"returncode": 0,
"stdout": "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\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.719605+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:32:24.719707+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.736985+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:32:24.737066+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.753370+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-r",
"NUL",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"65536",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:32:24.753426+00:00",
"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 65536/0Mio\nStop with Ctrl-C\n 0.3 MiB / 0.018 sec = 14.8 MiB/second, average power -29.9 dBfs, 9376 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.01795 s\nhackrf_stop_rx() done\nTransfer statistics:\n274144 bytes transferred by M0\n262144 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:32:24.790201+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_debug.exe",
"-d",
"000000000000000097c063dc323b97d3",
"--si5351c",
"-n",
"29",
"-r"
],
"started_at_utc": "2026-09-10T09:32:24.790253+00:00",
"returncode": 0,
"stdout": "[ 29] -> 0x26\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.807254+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"1"
],
"started_at_utc": "2026-09-10T09:32:24.807310+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.823113+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-i"
],
"started_at_utc": "2026-09-10T09:32:24.858007+00:00",
"returncode": 0,
"stdout": "CLKIN status: no clock signal detected\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.876412+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-i"
],
"started_at_utc": "2026-09-10T09:32:24.926726+00:00",
"returncode": 0,
"stdout": "CLKIN status: clock signal detected\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.944618+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-r",
"NUL",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"65536",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:32:24.944681+00:00",
"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 65536/0Mio\nStop with Ctrl-C\n 0.3 MiB / 0.017 sec = 15.4 MiB/second, average power -35.4 dBfs, 8832 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.01704 s\nhackrf_stop_rx() done\nTransfer statistics:\n274976 bytes transferred by M0\n262144 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:32:24.979112+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_debug.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"--si5351c",
"-n",
"29",
"-r"
],
"started_at_utc": "2026-09-10T09:32:24.979161+00:00",
"returncode": 0,
"stdout": "[ 29] -> 0x26\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:24.995463+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-r",
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab056\\rx_stream.cs8",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"24000000",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:32:24.995551+00:00",
"background_process": true,
"stdout_path": "C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab056\\rx_stdout.txt",
"stderr_path": "C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab056\\rx_stderr.txt",
"returncode": 0,
"finished_at_utc": "2026-09-10T09:32:28.029923+00:00",
"returncode_after_cleanup": 0,
"cleanup_at_utc": "2026-09-10T09:32:28.683438+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-t",
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab056\\tx_abab.cs8",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"10588544",
"-x",
"10",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:32:25.512893+00:00",
"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 10588544/10Mio\nStop with Ctrl-C\n16.0 MiB / 1.004 sec = 15.9 MiB/second, average power -11.3 dBfs, 13920 bytes filled in buffer, 0 underruns, longest 0 bytes\n 5.2 MiB / 0.322 sec = 16.1 MiB/second, average power -11.2 dBfs, 5120 bytes filled in buffer, 0 underruns, longest 0 bytes\n\nExiting...\nTotal time: 1.32538 s\nhackrf_stop_tx() done\nTransfer statistics:\n21200896 bytes transferred by M0\n21200896 bytes transferred by M4\n0 underruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:32:26.901844+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:32:28.683494+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:28.698845+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:32:28.698895+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:32:28.714042+00:00"
}
],
"orchestration_finished_at_utc": "2026-09-10T09:32:29.203758+00:00"
}

View File

@@ -0,0 +1,218 @@
{
"rf_tx_count": 0,
"tx_commands": 0,
"commands": [
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_info.exe"
],
"started_at_utc": "2026-09-10T09:16:28.777578+00:00",
"returncode": 0,
"stdout": "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\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:28.849704+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:16:28.849843+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:28.881216+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:16:28.881271+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:28.899025+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-r",
"NUL",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"65536",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:16:28.899092+00:00",
"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 65536/0Mio\nStop with Ctrl-C\n 0.3 MiB / 0.019 sec = 13.5 MiB/second, average power -27.9 dBfs, 9280 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.01979 s\nhackrf_stop_rx() done\nTransfer statistics:\n276256 bytes transferred by M0\n262144 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:16:28.950743+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_debug.exe",
"-d",
"000000000000000097c063dc323b97d3",
"--si5351c",
"-n",
"29",
"-r"
],
"started_at_utc": "2026-09-10T09:16:28.950806+00:00",
"returncode": 0,
"stdout": "[ 29] -> 0x26\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:28.980731+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"1"
],
"started_at_utc": "2026-09-10T09:16:28.980796+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:28.996692+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-i"
],
"started_at_utc": "2026-09-10T09:16:29.030946+00:00",
"returncode": 0,
"stdout": "CLKIN status: clock signal detected\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:29.048522+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-r",
"NUL",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"65536",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:16:29.048596+00:00",
"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 65536/0Mio\nStop with Ctrl-C\n 0.3 MiB / 0.019 sec = 14.0 MiB/second, average power -32.0 dBfs, 9120 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.01894 s\nhackrf_stop_rx() done\nTransfer statistics:\n273920 bytes transferred by M0\n262144 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:16:29.084743+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_debug.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"--si5351c",
"-n",
"29",
"-r"
],
"started_at_utc": "2026-09-10T09:16:29.084857+00:00",
"returncode": 0,
"stdout": "[ 29] -> 0x26\n",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:29.100716+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_transfer.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-r",
"C:\\Users\\user\\Desktop\\projects\\SDR_Rover\\data\\raw\\lab056\\preflight_rx.cs8",
"-f",
"1400000000",
"-s",
"8000000",
"-n",
"800000",
"-l",
"24",
"-g",
"24",
"-a",
"0",
"-p",
"0",
"-B"
],
"started_at_utc": "2026-09-10T09:16:29.100863+00:00",
"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.119 sec = 15.4 MiB/second, average power -35.2 dBfs, 15552 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.11922 s\nhackrf_stop_rx() done\nTransfer statistics:\n1913376 bytes transferred by M0\n1900544 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n",
"finished_at_utc": "2026-09-10T09:16:29.239488+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc323b97d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:16:29.271470+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:29.288606+00:00"
},
{
"command": [
"C:\\Users\\user\\Tools\\HackRF\\Library\\bin\\hackrf_clock.exe",
"-d",
"000000000000000097c063dc2e5327d3",
"-o",
"0"
],
"started_at_utc": "2026-09-10T09:16:29.288689+00:00",
"returncode": 0,
"stdout": "",
"stderr": "",
"finished_at_utc": "2026-09-10T09:16:29.309700+00:00"
}
]
}

View File

@@ -0,0 +1,26 @@
{
"point": {
"frequency_hz": 1400000000,
"physical_path": "MASTER TX -> TX antenna -> free-space LOS -> RX antenna -> SLAVE RX; user-specified approximate distance 3-5 m; fixed equipment and cables",
"path_confirmed": true,
"tx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"rx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"distance_m": null,
"orientation": "Both stationary; same antenna orientation and polarization, per user",
"visibility": "LOS"
},
"distance_description": "Approximately 3-5 m per user; exact measured value not supplied",
"frequency_role": "CURRENT_LAB056_TEST_ONLY_NOT_ROVER_OPERATING_BAND",
"field_band_context": "Approximately 180 MHz considered separately; 50/144 MHz outside Lab056",
"scope": "COMMON_CLOCK_AND_RX_ONLY_PREFLIGHT_THEN_STOP",
"tx_authorized": false,
"rf_tx_count": 0,
"rx_preflight_samples": 800000,
"sample_rate_hz": 8000000,
"rx_lna_db": 24,
"rx_vga_db": 24,
"rf_amplifier": false,
"bias_tee": false,
"external_pa_lna": false,
"recorded_at_utc": "2026-09-10T09:16:28.289788+00:00"
}

View File

@@ -0,0 +1,114 @@
{
"passed": true,
"rf_tx_count": 0,
"point": {
"frequency_hz": 1400000000,
"physical_path": "MASTER TX -> TX antenna -> free-space LOS -> RX antenna -> SLAVE RX; user-specified approximate distance 3-5 m; fixed equipment and cables",
"path_confirmed": true,
"tx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"rx_antenna": "Existing 1400 MHz antenna, as described by user; model/marking not supplied",
"distance_m": null,
"orientation": "Both stationary; same antenna orientation and polarization, per user",
"visibility": "LOS"
},
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"stop_before_tx": true,
"process_before": {
"state": "CLEAR",
"attempts": [
{
"state": "CLEAR",
"mechanism": "psutil",
"matches": [],
"inspected": 297,
"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": 1,
"time_to_detect_ms": 67.78709997888654,
"elapsed_ms": 67.78709997888654,
"polls": [
{
"query": 1,
"start_ms": 50.16230000182986,
"end_ms": 67.78709997888654,
"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.119 sec = 15.4 MiB/second, average power -35.2 dBfs, 15552 bytes free in buffer, 0 overruns, longest 0 bytes\n\nExiting...\nTotal time: 0.11922 s\nhackrf_stop_rx() done\nTransfer statistics:\n1913376 bytes transferred by M0\n1900544 bytes transferred by M4\n0 overruns, longest 0 bytes\nhackrf_close() done\nhackrf_exit() done\nfclose() done\nexit\n"
},
"rx_level": {
"samples": 800000,
"mean_i": 0.92535625,
"mean_q": 1.82532,
"power_code2": 4.85388125,
"ac_power_code2": 0.6658039581859372,
"rms_code": 2.20315257074947,
"peak_magnitude_code": 6.082762530298219,
"max_abs_i": 4.0,
"max_abs_q": 6.0,
"papr_db": 8.821125769372884,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002426940625,
"saturation_detected": false,
"analog_compression": "UNKNOWN_WITHOUT_CALIBRATION"
},
"rx_sha256": "c4257f26e4cc8562eb32228f9a50fdeb055879c928af0744cdf4a81cf7cd085b",
"process_after": {
"state": "CLEAR",
"attempts": [
{
"state": "CLEAR",
"mechanism": "psutil",
"matches": [],
"inspected": 297,
"errors": []
}
]
},
"cleanup_errors": [],
"source_path": "data/raw/lab056/preflight_result.json",
"source_sha256": "b691e51653f9e814627fd100cf99de0f22fef837b177030227ecddc5af573cf3",
"ready_for_single_abab_tx": true,
"tx_authorization_received": false,
"verifier": {
"mode": "SAVED_FILES_READ_ONLY",
"passed": true,
"reproduced_discrepancies": 0,
"independent_recalculation": "INTEGER_MOMENTS_AND_RAIL_COUNTS",
"hardware_commands": 0
},
"audited_hardware_commands": 12,
"audited_rx_transfers": 3,
"audited_tx_commands": 0,
"frequency_role": "LAB056_TEST_ONLY_NOT_ROVER_OPERATING_BAND",
"stop": true
}

View File

@@ -0,0 +1,489 @@
{
"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX",
"rf_tx": 0,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"rx_sha256": "a88afcc1bbeb50092f9f5baca6eb607d25daf508e0fafcdcada318818d9ffd62",
"alignment": {
"start": 4109216,
"correlation": 0.712109578315035,
"coarse_cfo_hz": 0.25,
"stride": 32
},
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219,
"band_27000_power": 1.729349736724617,
"band_675000_power": 1.8014764601725908,
"power_standard_error": 0.039755811858960266
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0012270833333334,
"mean_q": 2.0008,
"power_code2": 5.88834375,
"ac_power_code2": 0.8826874375998264,
"rms_code": 2.4265909729494997,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.924048425663756,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002944171875,
"band_27000_power": 0.002998337896541192,
"band_675000_power": 0.0744039583655369,
"power_standard_error": 0.0012691882704167598
},
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029416375,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
}
],
"background_power": 0.8838202337955727,
"background_subtracted_raw": 1.7124521150103154,
"background_subtracted_uncertainty": 0.039782742266144235,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7124521150103154,
"channel_gain_power": 0.0007924265932905088,
"measurement": {
"frame": 1,
"start": 4909213,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219
},
"cfo_hz": 0.1968177799965115,
"gain_abs": 0.028150072704888503,
"dc_i": 0.9997948620728929,
"dc_q": 2.0010324962850192,
"train_signal_power": 1.55023266362049,
"train_residual_power": 0.9894122701085495,
"train_actual_ac_power": 2.539644933729041,
"train_cross_power": 2.1058155219577658e-15,
"heldout_signal_power": 1.549823229026632,
"heldout_residual_power": 0.8945490859865625,
"heldout_actual_ac_power": 2.6529010618609536,
"heldout_cross_power": 0.20852874684775813,
"heldout_residual_band_27000_power": 0.010080618023636462,
"heldout_residual_band_675000_power": 0.08179753911385561,
"heldout_residual_band_1000000_power": 0.11760791625473926,
"heldout_residual_matched_power": 3.4888049404460095,
"effective_signal_to_residual_db": 2.3867798958438566
}
},
{
"label": "B1",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.998636474609375,
"mean_q": 1.9995599365234376,
"power_code2": 7.572622680664063,
"ac_power_code2": 2.5771079324942088,
"rms_code": 2.751839871915527,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 7.0,
"papr_db": 8.367570257627738,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5145245361328126e-05,
"band_27000_power": 0.08434245734375428,
"band_675000_power": 1.7818539229368797,
"power_standard_error": 0.03929664272862704
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.176655e-05,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
},
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766129166666667e-05,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
}
],
"background_power": 0.8848416145507811,
"background_subtracted_raw": 1.6922663179434276,
"background_subtracted_uncertainty": 0.03930519138544367,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.6922663179434276,
"channel_gain_power": 0.0007927525626002337,
"measurement": {
"frame": 1,
"start": 7364413,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.998636474609375,
"mean_q": 1.9995599365234376,
"power_code2": 7.572622680664063,
"ac_power_code2": 2.5771079324942088,
"rms_code": 2.751839871915527,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 7.0,
"papr_db": 8.367570257627738,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5145245361328126e-05
},
"cfo_hz": 0.20030217648162243,
"gain_abs": 0.028155861958040526,
"dc_i": 0.9986008794024919,
"dc_q": 2.00024705167195,
"train_signal_power": 1.5355362521933293,
"train_residual_power": 0.9879108077929747,
"train_actual_ac_power": 2.523447059986303,
"train_cross_power": -1.306454944227653e-15,
"heldout_signal_power": 1.5355180397870447,
"heldout_residual_power": 0.8925132940453717,
"heldout_actual_ac_power": 2.630769751790605,
"heldout_cross_power": 0.2027384179581883,
"heldout_residual_band_27000_power": 0.0033142687288771526,
"heldout_residual_band_675000_power": 0.0813443897071584,
"heldout_residual_band_1000000_power": 0.11762789507725498,
"heldout_residual_matched_power": 0.9819762872749349,
"effective_signal_to_residual_db": 2.3564022937017817
}
},
{
"label": "A2",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875,
"band_27000_power": 1.7263462989934586,
"band_675000_power": 1.7983862355807485,
"power_standard_error": 0.03961683202831068
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029415322916666667,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
},
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002941610416666667,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
}
],
"background_power": 0.8834691483528649,
"background_subtracted_raw": 1.7102246351553774,
"background_subtracted_uncertainty": 0.03964405579856132,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7102246351553774,
"channel_gain_power": 0.0007920055569282462,
"measurement": {
"frame": 1,
"start": 9803485,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875
},
"cfo_hz": 0.19617739757807567,
"gain_abs": 0.02814259328719097,
"dc_i": 1.0004532662364058,
"dc_q": 2.0003859700871685,
"train_signal_power": 1.5494089856585476,
"train_residual_power": 0.9895407718712785,
"train_actual_ac_power": 2.5389497575298265,
"train_cross_power": 4.0495384823202584e-16,
"heldout_signal_power": 1.548999768607681,
"heldout_residual_power": 0.8941535479569137,
"heldout_actual_ac_power": 2.6484381326123456,
"heldout_cross_power": 0.20528481604775145,
"heldout_residual_band_27000_power": 0.009765782070021769,
"heldout_residual_band_675000_power": 0.08171891866368976,
"heldout_residual_band_1000000_power": 0.1177276686242853,
"heldout_residual_matched_power": 3.3724974710374163,
"effective_signal_to_residual_db": 2.3863924873600086
}
},
{
"label": "B2",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.99942626953125,
"mean_q": 1.999466552734375,
"power_code2": 7.5732568359375,
"ac_power_code2": 2.5765374722048655,
"rms_code": 2.751955093372256,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.739298282383329,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5146513671875001e-05,
"band_27000_power": 0.08437478193987907,
"band_675000_power": 1.7815424066137258,
"power_standard_error": 0.03932113008198285
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766441666666666e-05,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
},
{
"samples": 480000,
"mean_i": 0.9988083333333333,
"mean_q": 1.9994125,
"power_code2": 5.878066666666666,
"ac_power_code2": 0.8827982347743042,
"rms_code": 2.4244724512080285,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.457388406130481,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1756133333333333e-05,
"band_27000_power": 0.0029587129864172557,
"band_675000_power": 0.07444268200354834,
"power_standard_error": 0.0016031313210695718
}
],
"background_power": 0.8825031661848954,
"background_subtracted_raw": 1.69403430601997,
"background_subtracted_uncertainty": 0.039335034220138454,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.69403430601997,
"channel_gain_power": 0.0007914780017038821,
"measurement": {
"frame": 1,
"start": 12258685,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.99942626953125,
"mean_q": 1.999466552734375,
"power_code2": 7.5732568359375,
"ac_power_code2": 2.5765374722048655,
"rms_code": 2.751955093372256,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.739298282383329,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5146513671875001e-05
},
"cfo_hz": 0.197924755610056,
"gain_abs": 0.028133218829417335,
"dc_i": 1.0007017709706703,
"dc_q": 1.999435627739676,
"train_signal_power": 1.5330674686733408,
"train_residual_power": 0.9881175175558363,
"train_actual_ac_power": 2.5211849862291755,
"train_cross_power": -1.4430123762565473e-15,
"heldout_signal_power": 1.5330492855483506,
"heldout_residual_power": 0.8931876810530448,
"heldout_actual_ac_power": 2.6318932139011078,
"heldout_cross_power": 0.20565624729971124,
"heldout_residual_band_27000_power": 0.0033209337668698995,
"heldout_residual_band_675000_power": 0.08160471700839024,
"heldout_residual_band_1000000_power": 0.11754379856225462,
"heldout_residual_matched_power": 0.9863150049618183,
"effective_signal_to_residual_db": 2.346133924823488
}
}
],
"pairs": [
{
"pair": 1,
"received_power_difference_db": -0.051497256938167285,
"channel_gain_difference_db": 0.0017861283874782213,
"matched_component_difference_db": -0.04027242840779062,
"matched_component_symbol_energy_difference_db": -14.019672515128168
},
{
"pair": 2,
"received_power_difference_db": -0.04130956980203142,
"channel_gain_difference_db": -0.0028938012026523408,
"matched_component_difference_db": -0.044952357997923256,
"matched_component_symbol_energy_difference_db": -14.0243524447183
}
],
"outside_waveform_background": [
{
"start": 3309216,
"samples": 400000,
"mean_i": 0.9997525,
"mean_q": 2.0,
"power_code2": 5.8853875,
"ac_power_code2": 0.8858824387437494,
"rms_code": 2.4259817600303593,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.926229353802586,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294269375,
"band_27000_power": 0.00300239645688663,
"band_675000_power": 0.07464470494185707,
"power_standard_error": 0.001189001018090443
},
{
"start": 15097760,
"samples": 400000,
"mean_i": 1.00201,
"mean_q": 1.9998775,
"power_code2": 5.8860125,
"ac_power_code2": 0.8824784448937502,
"rms_code": 2.4261105704398553,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.451521679042596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294300625,
"band_27000_power": 0.002877436011683443,
"band_675000_power": 0.07431026562360402,
"power_standard_error": 0.0010078630955450837
}
],
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B",
"synthetic": true,
"expected_gain_difference_db": 0.0,
"known_sequence_start": 4160013,
"passed": true
}

View File

@@ -0,0 +1,532 @@
{
"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX",
"rf_tx": 0,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"rx_sha256": "a88afcc1bbeb50092f9f5baca6eb607d25daf508e0fafcdcada318818d9ffd62",
"alignment": {
"start": 4109216,
"correlation": 0.712109578315035,
"coarse_cfo_hz": 0.25,
"stride": 32
},
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219,
"band_27000_power": 1.729349736724617,
"band_675000_power": 1.8014764601725908,
"power_standard_error": 0.039755811858960266
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0012270833333334,
"mean_q": 2.0008,
"power_code2": 5.88834375,
"ac_power_code2": 0.8826874375998264,
"rms_code": 2.4265909729494997,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.924048425663756,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002944171875,
"band_27000_power": 0.002998337896541192,
"band_675000_power": 0.0744039583655369,
"power_standard_error": 0.0012691882704167598
},
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029416375,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
}
],
"background_power": 0.8838202337955727,
"background_subtracted_raw": 1.7124521150103154,
"background_subtracted_uncertainty": 0.039782742266144235,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7124521150103154,
"channel_gain_complex": {
"real": -0.0027287468398317707,
"imag": -0.028017504063970737,
"phase_rad_at_active_start": -1.6678844763817968
},
"channel_gain_power": 0.0007924265932905088,
"measurement": {
"frame": 1,
"start": 4909213,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219
},
"cfo_hz": 0.1968177799965115,
"gain_abs": 0.028150072704888503,
"dc_i": 0.9997948620728929,
"dc_q": 2.0010324962850192,
"train_signal_power": 1.55023266362049,
"train_residual_power": 0.9894122701085495,
"train_actual_ac_power": 2.539644933729041,
"train_cross_power": 2.1058155219577658e-15,
"heldout_signal_power": 1.549823229026632,
"heldout_residual_power": 0.8945490859865625,
"heldout_actual_ac_power": 2.6529010618609536,
"heldout_cross_power": 0.20852874684775813,
"heldout_residual_band_27000_power": 0.010080618023636462,
"heldout_residual_band_675000_power": 0.08179753911385561,
"heldout_residual_band_1000000_power": 0.11760791625473926,
"heldout_residual_matched_power": 3.4888049404460095,
"effective_signal_to_residual_db": 2.3867798958438566
}
},
{
"label": "B1",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.998636474609375,
"mean_q": 1.9995599365234376,
"power_code2": 7.572622680664063,
"ac_power_code2": 2.5771079324942088,
"rms_code": 2.751839871915527,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 7.0,
"papr_db": 8.367570257627738,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5145245361328126e-05,
"band_27000_power": 0.08434245734375428,
"band_675000_power": 1.7818539229368797,
"power_standard_error": 0.03929664272862704
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.176655e-05,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
},
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766129166666667e-05,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
}
],
"background_power": 0.8848416145507811,
"background_subtracted_raw": 1.6922663179434276,
"background_subtracted_uncertainty": 0.03930519138544367,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.6922663179434276,
"channel_gain_complex": {
"real": 0.007947194796737946,
"imag": -0.027011009930451232,
"phase_rad_at_active_start": -1.2846501572766547
},
"channel_gain_power": 0.0007927525626002337,
"measurement": {
"frame": 1,
"start": 7364413,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.998636474609375,
"mean_q": 1.9995599365234376,
"power_code2": 7.572622680664063,
"ac_power_code2": 2.5771079324942088,
"rms_code": 2.751839871915527,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 7.0,
"papr_db": 8.367570257627738,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5145245361328126e-05
},
"cfo_hz": 0.20030217648162243,
"gain_abs": 0.028155861958040526,
"dc_i": 0.9986008794024919,
"dc_q": 2.00024705167195,
"train_signal_power": 1.5355362521933293,
"train_residual_power": 0.9879108077929747,
"train_actual_ac_power": 2.523447059986303,
"train_cross_power": -1.306454944227653e-15,
"heldout_signal_power": 1.5355180397870447,
"heldout_residual_power": 0.8925132940453717,
"heldout_actual_ac_power": 2.630769751790605,
"heldout_cross_power": 0.2027384179581883,
"heldout_residual_band_27000_power": 0.0033142687288771526,
"heldout_residual_band_675000_power": 0.0813443897071584,
"heldout_residual_band_1000000_power": 0.11762789507725498,
"heldout_residual_matched_power": 0.9819762872749349,
"effective_signal_to_residual_db": 2.3564022937017817
}
},
{
"label": "A2",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875,
"band_27000_power": 1.7263462989934586,
"band_675000_power": 1.7983862355807485,
"power_standard_error": 0.03961683202831068
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029415322916666667,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
},
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002941610416666667,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
}
],
"background_power": 0.8834691483528649,
"background_subtracted_raw": 1.7102246351553774,
"background_subtracted_uncertainty": 0.03964405579856132,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7102246351553774,
"channel_gain_complex": {
"real": 0.01755314566236957,
"imag": -0.02199755973474973,
"phase_rad_at_active_start": -0.8973012782222721
},
"channel_gain_power": 0.0007920055569282462,
"measurement": {
"frame": 1,
"start": 9803485,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875
},
"cfo_hz": 0.19617739757807567,
"gain_abs": 0.02814259328719097,
"dc_i": 1.0004532662364058,
"dc_q": 2.0003859700871685,
"train_signal_power": 1.5494089856585476,
"train_residual_power": 0.9895407718712785,
"train_actual_ac_power": 2.5389497575298265,
"train_cross_power": 4.0495384823202584e-16,
"heldout_signal_power": 1.548999768607681,
"heldout_residual_power": 0.8941535479569137,
"heldout_actual_ac_power": 2.6484381326123456,
"heldout_cross_power": 0.20528481604775145,
"heldout_residual_band_27000_power": 0.009765782070021769,
"heldout_residual_band_675000_power": 0.08171891866368976,
"heldout_residual_band_1000000_power": 0.1177276686242853,
"heldout_residual_matched_power": 3.3724974710374163,
"effective_signal_to_residual_db": 2.3863924873600086
}
},
{
"label": "B2",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.99942626953125,
"mean_q": 1.999466552734375,
"power_code2": 7.5732568359375,
"ac_power_code2": 2.5765374722048655,
"rms_code": 2.751955093372256,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.739298282383329,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5146513671875001e-05,
"band_27000_power": 0.08437478193987907,
"band_675000_power": 1.7815424066137258,
"power_standard_error": 0.03932113008198285
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766441666666666e-05,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
},
{
"samples": 480000,
"mean_i": 0.9988083333333333,
"mean_q": 1.9994125,
"power_code2": 5.878066666666666,
"ac_power_code2": 0.8827982347743042,
"rms_code": 2.4244724512080285,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.457388406130481,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1756133333333333e-05,
"band_27000_power": 0.0029587129864172557,
"band_675000_power": 0.07444268200354834,
"power_standard_error": 0.0016031313210695718
}
],
"background_power": 0.8825031661848954,
"background_subtracted_raw": 1.69403430601997,
"background_subtracted_uncertainty": 0.039335034220138454,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.69403430601997,
"channel_gain_complex": {
"real": 0.02447249803166995,
"imag": -0.013877133774443209,
"phase_rad_at_active_start": -0.5158392359795115
},
"channel_gain_power": 0.0007914780017038821,
"measurement": {
"frame": 1,
"start": 12258685,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.99942626953125,
"mean_q": 1.999466552734375,
"power_code2": 7.5732568359375,
"ac_power_code2": 2.5765374722048655,
"rms_code": 2.751955093372256,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.739298282383329,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.5146513671875001e-05
},
"cfo_hz": 0.197924755610056,
"gain_abs": 0.028133218829417335,
"dc_i": 1.0007017709706703,
"dc_q": 1.999435627739676,
"train_signal_power": 1.5330674686733408,
"train_residual_power": 0.9881175175558363,
"train_actual_ac_power": 2.5211849862291755,
"train_cross_power": -1.4430123762565473e-15,
"heldout_signal_power": 1.5330492855483506,
"heldout_residual_power": 0.8931876810530448,
"heldout_actual_ac_power": 2.6318932139011078,
"heldout_cross_power": 0.20565624729971124,
"heldout_residual_band_27000_power": 0.0033209337668698995,
"heldout_residual_band_675000_power": 0.08160471700839024,
"heldout_residual_band_1000000_power": 0.11754379856225462,
"heldout_residual_matched_power": 0.9863150049618183,
"effective_signal_to_residual_db": 2.346133924823488
}
}
],
"pairs": [
{
"pair": 1,
"received_power_difference_db": -0.051497256938167285,
"channel_gain_difference_db": 0.0017861283874782213,
"matched_component_difference_db": -0.04027242840779062,
"matched_component_symbol_energy_difference_db": -14.019672515128168
},
{
"pair": 2,
"received_power_difference_db": -0.04130956980203142,
"channel_gain_difference_db": -0.0028938012026523408,
"matched_component_difference_db": -0.044952357997923256,
"matched_component_symbol_energy_difference_db": -14.0243524447183
}
],
"comparison_summary": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": -0.0012085532551280598,
"background_difference_db": -0.0017255178918689754,
"received_power_difference_db": -0.005652782408264756,
"channel_gain_difference_db": -0.0023081300526506692,
"matched_component_difference_db": -0.00230813005264874
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.00036367664985406537,
"background_difference_db": -0.011492673362299905,
"received_power_difference_db": 0.004534904727870617,
"channel_gain_difference_db": -0.006988059642781434,
"matched_component_difference_db": -0.006988059642780951
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": -0.04640374115997541,
"channel_gain_difference_db": -0.000552584218408658,
"energy_per_symbol_difference_db": -14.025803827880353,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
},
"outside_waveform_background": [
{
"start": 3309216,
"samples": 400000,
"mean_i": 0.9997525,
"mean_q": 2.0,
"power_code2": 5.8853875,
"ac_power_code2": 0.8858824387437494,
"rms_code": 2.4259817600303593,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.926229353802586,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294269375,
"band_27000_power": 0.00300239645688663,
"band_675000_power": 0.07464470494185707,
"power_standard_error": 0.001189001018090443
},
{
"start": 15097760,
"samples": 400000,
"mean_i": 1.00201,
"mean_q": 1.9998775,
"power_code2": 5.8860125,
"ac_power_code2": 0.8824784448937502,
"rms_code": 2.4261105704398553,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.451521679042596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294300625,
"band_27000_power": 0.002877436011683443,
"band_675000_power": 0.07431026562360402,
"power_standard_error": 0.0010078630955450837
}
],
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B",
"synthetic": true,
"source_result_preserved": "software_equal.json"
}

View File

@@ -0,0 +1,489 @@
{
"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX",
"rf_tx": 0,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"rx_sha256": "a189c1ffaf029983d41f7d87e262cbbc28ba740fc8915a75e41ee0adec9c1fe6",
"alignment": {
"start": 4109216,
"correlation": 0.593709534474723,
"coarse_cfo_hz": 0.25,
"stride": 32
},
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219,
"band_27000_power": 1.729349736724617,
"band_675000_power": 1.8014764601725908,
"power_standard_error": 0.039755811858960266
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0012270833333334,
"mean_q": 2.0008,
"power_code2": 5.88834375,
"ac_power_code2": 0.8826874375998264,
"rms_code": 2.4265909729494997,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.924048425663756,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002944171875,
"band_27000_power": 0.002998337896541192,
"band_675000_power": 0.0744039583655369,
"power_standard_error": 0.0012691882704167598
},
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029416375,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
}
],
"background_power": 0.8838202337955727,
"background_subtracted_raw": 1.7124521150103154,
"background_subtracted_uncertainty": 0.039782742266144235,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7124521150103154,
"channel_gain_power": 0.0007924265932905088,
"measurement": {
"frame": 1,
"start": 4909213,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219
},
"cfo_hz": 0.1968177799965115,
"gain_abs": 0.028150072704888503,
"dc_i": 0.9997948620728929,
"dc_q": 2.0010324962850192,
"train_signal_power": 1.55023266362049,
"train_residual_power": 0.9894122701085495,
"train_actual_ac_power": 2.539644933729041,
"train_cross_power": 2.1058155219577658e-15,
"heldout_signal_power": 1.549823229026632,
"heldout_residual_power": 0.8945490859865625,
"heldout_actual_ac_power": 2.6529010618609536,
"heldout_cross_power": 0.20852874684775813,
"heldout_residual_band_27000_power": 0.010080618023636462,
"heldout_residual_band_675000_power": 0.08179753911385561,
"heldout_residual_band_1000000_power": 0.11760791625473926,
"heldout_residual_matched_power": 3.4888049404460095,
"effective_signal_to_residual_db": 2.3867798958438566
}
},
{
"label": "B1",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.998985595703125,
"mean_q": 1.99885986328125,
"power_code2": 6.303458251953125,
"ac_power_code2": 1.3100452784940613,
"rms_code": 2.5106688853676276,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 4.0,
"max_abs_q": 6.0,
"papr_db": 8.132049758894885,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.260691650390625e-05,
"band_27000_power": 0.023313641821752495,
"band_675000_power": 0.5022288931564798,
"power_standard_error": 0.009890376052273048
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.176655e-05,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
},
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766129166666667e-05,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
}
],
"background_power": 0.8848416145507811,
"background_subtracted_raw": 0.42520366394328013,
"background_subtracted_uncertainty": 0.009924287307427941,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.42520366394328013,
"channel_gain_power": 0.0001984013788928856,
"measurement": {
"frame": 1,
"start": 7364413,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.998985595703125,
"mean_q": 1.99885986328125,
"power_code2": 6.303458251953125,
"ac_power_code2": 1.3100452784940613,
"rms_code": 2.5106688853676276,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 4.0,
"max_abs_q": 6.0,
"papr_db": 8.132049758894885,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.260691650390625e-05
},
"cfo_hz": 0.2030817936052521,
"gain_abs": 0.014085502436650445,
"dc_i": 0.9994693255003283,
"dc_q": 1.9991360434375782,
"train_signal_power": 0.38429709867591966,
"train_residual_power": 0.9122921896535339,
"train_actual_ac_power": 1.2965892883294532,
"train_cross_power": 1.1296519275560969e-16,
"heldout_signal_power": 0.38429254067549146,
"heldout_residual_power": 0.8884947950547913,
"heldout_actual_ac_power": 1.3235018891986572,
"heldout_cross_power": 0.050714553468375057,
"heldout_residual_band_27000_power": 0.00304194937150677,
"heldout_residual_band_675000_power": 0.07681375917338087,
"heldout_residual_band_1000000_power": 0.11305538222420118,
"heldout_residual_matched_power": 0.9157792365512673,
"effective_signal_to_residual_db": -3.639929333248138
}
},
{
"label": "A2",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875,
"band_27000_power": 1.7263462989934586,
"band_675000_power": 1.7983862355807485,
"power_standard_error": 0.03961683202831068
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029415322916666667,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
},
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002941610416666667,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
}
],
"background_power": 0.8834691483528649,
"background_subtracted_raw": 1.7102246351553774,
"background_subtracted_uncertainty": 0.03964405579856132,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7102246351553774,
"channel_gain_power": 0.0007920055569282462,
"measurement": {
"frame": 1,
"start": 9803485,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875
},
"cfo_hz": 0.19617739757807567,
"gain_abs": 0.02814259328719097,
"dc_i": 1.0004532662364058,
"dc_q": 2.0003859700871685,
"train_signal_power": 1.5494089856585476,
"train_residual_power": 0.9895407718712785,
"train_actual_ac_power": 2.5389497575298265,
"train_cross_power": 4.0495384823202584e-16,
"heldout_signal_power": 1.548999768607681,
"heldout_residual_power": 0.8941535479569137,
"heldout_actual_ac_power": 2.6484381326123456,
"heldout_cross_power": 0.20528481604775145,
"heldout_residual_band_27000_power": 0.009765782070021769,
"heldout_residual_band_675000_power": 0.08171891866368976,
"heldout_residual_band_1000000_power": 0.1177276686242853,
"heldout_residual_matched_power": 3.3724974710374163,
"effective_signal_to_residual_db": 2.3863924873600086
}
},
{
"label": "B2",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9994140625,
"mean_q": 1.999407958984375,
"power_code2": 6.305787353515625,
"ac_power_code2": 1.3093266987428072,
"rms_code": 2.511132683375298,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.317395958495091,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.261157470703125e-05,
"band_27000_power": 0.023440988618055048,
"band_675000_power": 0.5015327722551061,
"power_standard_error": 0.009843366386499481
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766441666666666e-05,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
},
{
"samples": 480000,
"mean_i": 0.9988083333333333,
"mean_q": 1.9994125,
"power_code2": 5.878066666666666,
"ac_power_code2": 0.8827982347743042,
"rms_code": 2.4244724512080285,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.457388406130481,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1756133333333333e-05,
"band_27000_power": 0.0029587129864172557,
"band_675000_power": 0.07444268200354834,
"power_standard_error": 0.0016031313210695718
}
],
"background_power": 0.8825031661848954,
"background_subtracted_raw": 0.4268235325579117,
"background_subtracted_uncertainty": 0.009898762952718625,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.4268235325579117,
"channel_gain_power": 0.00019792959281029668,
"measurement": {
"frame": 1,
"start": 12258685,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9994140625,
"mean_q": 1.999407958984375,
"power_code2": 6.305787353515625,
"ac_power_code2": 1.3093266987428072,
"rms_code": 2.511132683375298,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.317395958495091,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.261157470703125e-05
},
"cfo_hz": 0.20036016971332649,
"gain_abs": 0.014068745246477977,
"dc_i": 1.0007268354608458,
"dc_q": 1.9995225464921158,
"train_signal_power": 0.3833832641867325,
"train_residual_power": 0.9125567848168452,
"train_actual_ac_power": 1.2959400490035784,
"train_cross_power": 3.459732500488144e-16,
"heldout_signal_power": 0.3833787170249446,
"heldout_residual_power": 0.8881519217870418,
"heldout_actual_ac_power": 1.3227168214883227,
"heldout_cross_power": 0.051186182676336164,
"heldout_residual_band_27000_power": 0.003087916076869909,
"heldout_residual_band_675000_power": 0.0766452188649128,
"heldout_residual_band_1000000_power": 0.11237262287689712,
"heldout_residual_matched_power": 0.9135235192972193,
"effective_signal_to_residual_db": -3.648592601804231
}
}
],
"pairs": [
{
"pair": 1,
"received_power_difference_db": -6.05021438090514,
"channel_gain_difference_db": -6.014143555521402,
"matched_component_difference_db": -6.056202112316672,
"matched_component_symbol_energy_difference_db": -20.035602199037047
},
{
"pair": 2,
"received_power_difference_db": -6.028048021201634,
"channel_gain_difference_db": -6.022174975174189,
"matched_component_difference_db": -6.06423353196946,
"matched_component_symbol_energy_difference_db": -20.04363361868984
}
],
"outside_waveform_background": [
{
"start": 3309216,
"samples": 400000,
"mean_i": 0.9997525,
"mean_q": 2.0,
"power_code2": 5.8853875,
"ac_power_code2": 0.8858824387437494,
"rms_code": 2.4259817600303593,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.926229353802586,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294269375,
"band_27000_power": 0.00300239645688663,
"band_675000_power": 0.07464470494185707,
"power_standard_error": 0.001189001018090443
},
{
"start": 15097760,
"samples": 400000,
"mean_i": 1.00201,
"mean_q": 1.9998775,
"power_code2": 5.8860125,
"ac_power_code2": 0.8824784448937502,
"rms_code": 2.4261105704398553,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.451521679042596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294300625,
"band_27000_power": 0.002877436011683443,
"band_675000_power": 0.07431026562360402,
"power_standard_error": 0.0010078630955450837
}
],
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B",
"synthetic": true,
"expected_gain_difference_db": -6.020599913279624,
"known_sequence_start": 4160013,
"passed": true
}

View File

@@ -0,0 +1,532 @@
{
"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX",
"rf_tx": 0,
"tx_sha256": "5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600",
"rx_sha256": "a189c1ffaf029983d41f7d87e262cbbc28ba740fc8915a75e41ee0adec9c1fe6",
"alignment": {
"start": 4109216,
"correlation": 0.593709534474723,
"coarse_cfo_hz": 0.25,
"stride": 32
},
"segments": [
{
"label": "A1",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219,
"band_27000_power": 1.729349736724617,
"band_675000_power": 1.8014764601725908,
"power_standard_error": 0.039755811858960266
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0012270833333334,
"mean_q": 2.0008,
"power_code2": 5.88834375,
"ac_power_code2": 0.8826874375998264,
"rms_code": 2.4265909729494997,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.924048425663756,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002944171875,
"band_27000_power": 0.002998337896541192,
"band_675000_power": 0.0744039583655369,
"power_standard_error": 0.0012691882704167598
},
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029416375,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
}
],
"background_power": 0.8838202337955727,
"background_subtracted_raw": 1.7124521150103154,
"background_subtracted_uncertainty": 0.039782742266144235,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7124521150103154,
"channel_gain_complex": {
"real": -0.0027287468398317707,
"imag": -0.028017504063970737,
"phase_rad_at_active_start": -1.6678844763817968
},
"channel_gain_power": 0.0007924265932905088,
"measurement": {
"frame": 1,
"start": 4909213,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9995880126953125,
"mean_q": 2.00025390625,
"power_code2": 7.596464233398438,
"ac_power_code2": 2.596272348805888,
"rms_code": 2.7561683971409363,
"peak_magnitude_code": 6.708203932499369,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 7.726010163935343,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0003798232116699219
},
"cfo_hz": 0.1968177799965115,
"gain_abs": 0.028150072704888503,
"dc_i": 0.9997948620728929,
"dc_q": 2.0010324962850192,
"train_signal_power": 1.55023266362049,
"train_residual_power": 0.9894122701085495,
"train_actual_ac_power": 2.539644933729041,
"train_cross_power": 2.1058155219577658e-15,
"heldout_signal_power": 1.549823229026632,
"heldout_residual_power": 0.8945490859865625,
"heldout_actual_ac_power": 2.6529010618609536,
"heldout_cross_power": 0.20852874684775813,
"heldout_residual_band_27000_power": 0.010080618023636462,
"heldout_residual_band_675000_power": 0.08179753911385561,
"heldout_residual_band_1000000_power": 0.11760791625473926,
"heldout_residual_matched_power": 3.4888049404460095,
"effective_signal_to_residual_db": 2.3867798958438566
}
},
{
"label": "B1",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.998985595703125,
"mean_q": 1.99885986328125,
"power_code2": 6.303458251953125,
"ac_power_code2": 1.3100452784940613,
"rms_code": 2.5106688853676276,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 4.0,
"max_abs_q": 6.0,
"papr_db": 8.132049758894885,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.260691650390625e-05,
"band_27000_power": 0.023313641821752495,
"band_675000_power": 0.5022288931564798,
"power_standard_error": 0.009890376052273048
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989395833333333,
"mean_q": 2.000110416666667,
"power_code2": 5.883275,
"ac_power_code2": 0.8849530299913191,
"rms_code": 2.42554633021099,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.9277884895206405,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.176655e-05,
"band_27000_power": 0.003012451963227414,
"band_675000_power": 0.07417080321149919,
"power_standard_error": 0.0013506571909630695
},
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766129166666667e-05,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
}
],
"background_power": 0.8848416145507811,
"background_subtracted_raw": 0.42520366394328013,
"background_subtracted_uncertainty": 0.009924287307427941,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.42520366394328013,
"channel_gain_complex": {
"real": 0.003963777071987038,
"imag": -0.013516280931398084,
"phase_rad_at_active_start": -1.2855349404464855
},
"channel_gain_power": 0.0001984013788928856,
"measurement": {
"frame": 1,
"start": 7364413,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.998985595703125,
"mean_q": 1.99885986328125,
"power_code2": 6.303458251953125,
"ac_power_code2": 1.3100452784940613,
"rms_code": 2.5106688853676276,
"peak_magnitude_code": 6.4031242374328485,
"max_abs_i": 4.0,
"max_abs_q": 6.0,
"papr_db": 8.132049758894885,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.260691650390625e-05
},
"cfo_hz": 0.2030817936052521,
"gain_abs": 0.014085502436650445,
"dc_i": 0.9994693255003283,
"dc_q": 1.9991360434375782,
"train_signal_power": 0.38429709867591966,
"train_residual_power": 0.9122921896535339,
"train_actual_ac_power": 1.2965892883294532,
"train_cross_power": 1.1296519275560969e-16,
"heldout_signal_power": 0.38429254067549146,
"heldout_residual_power": 0.8884947950547913,
"heldout_actual_ac_power": 1.3235018891986572,
"heldout_cross_power": 0.050714553468375057,
"heldout_residual_band_27000_power": 0.00304194937150677,
"heldout_residual_band_675000_power": 0.07681375917338087,
"heldout_residual_band_1000000_power": 0.11305538222420118,
"heldout_residual_matched_power": 0.9157792365512673,
"effective_signal_to_residual_db": -3.639929333248138
}
},
{
"label": "A2",
"symbol_rate": 20000,
"raw_rx": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875,
"band_27000_power": 1.7263462989934586,
"band_675000_power": 1.7983862355807485,
"power_standard_error": 0.03961683202831068
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 0.9989791666666666,
"mean_q": 2.00009375,
"power_code2": 5.883064583333334,
"ac_power_code2": 0.8847301991102432,
"rms_code": 2.4255029547154408,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.45369731942485,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.00029415322916666667,
"band_27000_power": 0.0030190450807723868,
"band_675000_power": 0.07463668207172841,
"power_standard_error": 0.0009021263608471298
},
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.0002941610416666667,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
}
],
"background_power": 0.8834691483528649,
"background_subtracted_raw": 1.7102246351553774,
"background_subtracted_uncertainty": 0.03964405579856132,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 1.7102246351553774,
"channel_gain_complex": {
"real": 0.01755314566236957,
"imag": -0.02199755973474973,
"phase_rad_at_active_start": -0.8973012782222721
},
"channel_gain_power": 0.0007920055569282462,
"measurement": {
"frame": 1,
"start": 9803485,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 1.000250244140625,
"mean_q": 2.0000390625,
"power_code2": 7.5943505859375,
"ac_power_code2": 2.5936937835082423,
"rms_code": 2.755784931001964,
"peak_magnitude_code": 7.211102550927978,
"max_abs_i": 5.0,
"max_abs_q": 6.0,
"papr_db": 8.355127015785026,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000379717529296875
},
"cfo_hz": 0.19617739757807567,
"gain_abs": 0.02814259328719097,
"dc_i": 1.0004532662364058,
"dc_q": 2.0003859700871685,
"train_signal_power": 1.5494089856585476,
"train_residual_power": 0.9895407718712785,
"train_actual_ac_power": 2.5389497575298265,
"train_cross_power": 4.0495384823202584e-16,
"heldout_signal_power": 1.548999768607681,
"heldout_residual_power": 0.8941535479569137,
"heldout_actual_ac_power": 2.6484381326123456,
"heldout_cross_power": 0.20528481604775145,
"heldout_residual_band_27000_power": 0.009765782070021769,
"heldout_residual_band_675000_power": 0.08171891866368976,
"heldout_residual_band_1000000_power": 0.1177276686242853,
"heldout_residual_matched_power": 3.3724974710374163,
"effective_signal_to_residual_db": 2.3863924873600086
}
},
{
"label": "B2",
"symbol_rate": 500000,
"raw_rx": {
"samples": 1638400,
"mean_i": 0.9994140625,
"mean_q": 1.999407958984375,
"power_code2": 6.305787353515625,
"ac_power_code2": 1.3093266987428072,
"rms_code": 2.511132683375298,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.317395958495091,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.261157470703125e-05,
"band_27000_power": 0.023440988618055048,
"band_675000_power": 0.5015327722551061,
"power_standard_error": 0.009843366386499481
},
"local_zero_iq_background_windows": [
{
"samples": 480000,
"mean_i": 1.0004854166666666,
"mean_q": 2.0000104166666666,
"power_code2": 5.883220833333334,
"ac_power_code2": 0.8822080975954867,
"rms_code": 2.425535164315977,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.6186376661535915,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1766441666666666e-05,
"band_27000_power": 0.003053650977045963,
"band_675000_power": 0.073961606327725,
"power_standard_error": 0.0012067695631605175
},
{
"samples": 480000,
"mean_i": 0.9988083333333333,
"mean_q": 1.9994125,
"power_code2": 5.878066666666666,
"ac_power_code2": 0.8827982347743042,
"rms_code": 2.4244724512080285,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.457388406130481,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.1756133333333333e-05,
"band_27000_power": 0.0029587129864172557,
"band_675000_power": 0.07444268200354834,
"power_standard_error": 0.0016031313210695718
}
],
"background_power": 0.8825031661848954,
"background_subtracted_raw": 0.4268235325579117,
"background_subtracted_uncertainty": 0.009898762952718625,
"background_subtracted_stable": true,
"background_subtracted_signal_power": 0.4268235325579117,
"channel_gain_complex": {
"real": 0.012227408500259825,
"imag": -0.006958453432916718,
"phase_rad_at_active_start": -0.517378791591959
},
"channel_gain_power": 0.00019792959281029668,
"measurement": {
"frame": 1,
"start": 12258685,
"alignment_adjustment": -3,
"rx_active": {
"samples": 1638400,
"mean_i": 0.9994140625,
"mean_q": 1.999407958984375,
"power_code2": 6.305787353515625,
"ac_power_code2": 1.3093266987428072,
"rms_code": 2.511132683375298,
"peak_magnitude_code": 5.830951894845301,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 7.317395958495091,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 1.261157470703125e-05
},
"cfo_hz": 0.20036016971332649,
"gain_abs": 0.014068745246477977,
"dc_i": 1.0007268354608458,
"dc_q": 1.9995225464921158,
"train_signal_power": 0.3833832641867325,
"train_residual_power": 0.9125567848168452,
"train_actual_ac_power": 1.2959400490035784,
"train_cross_power": 3.459732500488144e-16,
"heldout_signal_power": 0.3833787170249446,
"heldout_residual_power": 0.8881519217870418,
"heldout_actual_ac_power": 1.3227168214883227,
"heldout_cross_power": 0.051186182676336164,
"heldout_residual_band_27000_power": 0.003087916076869909,
"heldout_residual_band_675000_power": 0.0766452188649128,
"heldout_residual_band_1000000_power": 0.11237262287689712,
"heldout_residual_matched_power": 0.9135235192972193,
"effective_signal_to_residual_db": -3.648592601804231
}
}
],
"pairs": [
{
"pair": 1,
"received_power_difference_db": -6.05021438090514,
"channel_gain_difference_db": -6.014143555521402,
"matched_component_difference_db": -6.056202112316672,
"matched_component_symbol_energy_difference_db": -20.035602199037047
},
{
"pair": 2,
"received_power_difference_db": -6.028048021201634,
"channel_gain_difference_db": -6.022174975174189,
"matched_component_difference_db": -6.06423353196946,
"matched_component_symbol_energy_difference_db": -20.04363361868984
}
],
"comparison_summary": {
"repeatability": [
{
"comparison": "A2/A1",
"raw_rx_power_difference_db": -0.0012085532551280598,
"background_difference_db": -0.0017255178918689754,
"received_power_difference_db": -0.005652782408264756,
"channel_gain_difference_db": -0.0023081300526506692,
"matched_component_difference_db": -0.00230813005264874
},
{
"comparison": "B2/B1",
"raw_rx_power_difference_db": 0.0016044036249902335,
"background_difference_db": -0.011492673362299905,
"received_power_difference_db": 0.016513577295241087,
"channel_gain_difference_db": -0.0103395497054386,
"matched_component_difference_db": -0.010339549705438118
}
],
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": -6.039124271880495,
"channel_gain_difference_db": -6.018156341674954,
"energy_per_symbol_difference_db": -20.01852435860087,
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"
},
"outside_waveform_background": [
{
"start": 3309216,
"samples": 400000,
"mean_i": 0.9997525,
"mean_q": 2.0,
"power_code2": 5.8853875,
"ac_power_code2": 0.8858824387437494,
"rms_code": 2.4259817600303593,
"peak_magnitude_code": 5.385164807134504,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.926229353802586,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294269375,
"band_27000_power": 0.00300239645688663,
"band_675000_power": 0.07464470494185707,
"power_standard_error": 0.001189001018090443
},
{
"start": 15097760,
"samples": 400000,
"mean_i": 1.00201,
"mean_q": 1.9998775,
"power_code2": 5.8860125,
"ac_power_code2": 0.8824784448937502,
"rms_code": 2.4261105704398553,
"peak_magnitude_code": 5.0990195135927845,
"max_abs_i": 4.0,
"max_abs_q": 5.0,
"papr_db": 6.451521679042596,
"clipped_components_fraction": 0.0,
"clipped_samples_fraction": 0.0,
"symbol_energy_code2_seconds": 0.000294300625,
"band_27000_power": 0.002877436011683443,
"band_675000_power": 0.07431026562360402,
"power_standard_error": 0.0010078630955450837
}
],
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B",
"synthetic": true,
"source_result_preserved": "software_minus6db.json"
}

View File

@@ -406,6 +406,51 @@
- **Далее:** STOP. Отдельное согласование измерительной схемы и допустимой - **Далее:** STOP. Отдельное согласование измерительной схемы и допустимой
частоты; усилитель не назначен, Git add/commit/push/pull/fetch не выполнялись. частоты; усилитель не назначен, Git add/commit/push/pull/fetch не выполнялись.
## Lab056 — подготовка одного эфирного ABAB, 20 и 500 ксимв/с
- **База:** Lab055 зафиксирована коммитом
`5add95ecb8865f59075bbbced08d97b183fb3b48`, только её семь файлов; без push.
- **Подготовлено:** один PRBS7/BPSK/RRC TX-файл ABAB, 1,323568 с;
Fs 8 Мвыб/с, цифровая несущая +50 кГц. RMS A/B 44,004413/44,001925 код,
разница мощности −0,000491 дБ; клиппинг 0. RX планируется на 3 с.
- **Проверено программно:** известные разницы усиления 0 и −6,020600 дБ
восстановлены с ошибкой менее 0,3 дБ в обеих парах. Анализируются отдельно
принятая мощность, фон, коэффициент передачи, остаток, спектр и энергия символа.
- **Проверки:** 34 целевых теста; quick_gate 857/30/13 PASS,
pytest 857 passed, 0 failed; verifier без остаточных находок; diff check PASS.
- **Граница:** пользователь выбрал эфир; частота/полоса и геометрия пока
не согласованы. 1400 МГц для излучения не используется. Общий такт проверен
только mock-вызовами; аппаратный preflight NOT_PERFORMED. RF TX=0.
Эфирный результат будет характеризовать весь путь с антеннами и распространением.
- **Отчёт:** [состояние](lab056_current_state.md),
[подготовка TX](../data/processed/lab056/preparation.json),
[сводка проверок](../data/processed/lab056/acceptance.json), журнал 047.
- **Далее:** STOP и отдельное разрешение на согласованный RF-сеанс.
Lab056 не коммитилась, push не выполнялся; Lab054 NOT_A сохранён.
- **Корректировка, запись 048:** кабельный контроль отложен из-за отсутствия
SMA-переходника; принят контролируемый OTA. RF_FREQ задаёт пользователь,
страна и границы диапазона не запрашиваются. Конкретная частота пока UNKNOWN.
Добавлен отдельный RX-only preflight с STOP перед разрешением TX, проверки
клиппинга до/после передачи, вывод комплексного h и повторов A2/A1, B2/B1.
TX и прежние результаты сохранены. 44 целевых теста; quick_gate 867/30/13
PASS, pytest 867 passed; verifier без находок. Аппаратных команд нет, RF TX=0.
[Сводка корректировки](../data/processed/lab056/ota_correction.json).
- **Аппаратный preflight, запись 049:** пользователь задал 1400000000 Гц
только для Lab056, OTA LOS примерно 3–5 м. Common clock PASS: оба reg29
0x26, SLAVE CLKIN DETECTED. RX-only 800 000 отсчётов/0,1 с, overruns 0,
клиппинг 0, RMS 2,203153 код. CLKOUT выключены, процессы CLEAR, RF TX=0.
[Отчёт preflight](lab056_rx_preflight.md). STOP до отдельного разрешения TX.
- **Единственный ABAB, запись 050:** после отдельного разрешения один TX
1,323568 с и RX 3 с; 4/4 сегмента, ошибка согласования начал ≤1 отсчёта,
клиппинг/overruns/underruns 0. X=+0,108895 дБ, Y=−0,163042 дБ,
Z=−13,870505 дБ. Повтор мощности A2/A1 −0,482024 дБ, B2/B1 −0,512676 дБ;
повтор коэффициента передачи примерно −0,18 дБ у обоих режимов.
Прежний провал 15,41 дБ в этой OTA-сессии не воспроизвёлся; собственные
потери HackRF не изолированы. Verifier подтвердил сырые данные и расчёты.
[RF-отчёт](lab056_rf_result.md), [сводка](../data/processed/lab056/rf_result.json),
[график](../data/processed/lab056/rf_overview.png). Выполненных TX 1, сейчас
RF TX=0, повторов нет. STOP, Lab056 не коммитилась, push не выполнялся.
## Правило хранения данных ## Правило хранения данных
Большие записи IQ, потоки `cs8`, бинарные объекты и другие аппаратные исходные Большие записи IQ, потоки `cs8`, бинарные объекты и другие аппаратные исходные

View File

@@ -0,0 +1,326 @@
# Lab056 — подготовка сравнения 20 и 500 ксимв/с в одной сессии
Подготовка 9 сентября 2026 года; корректировка OTA 10 сентября 2026 года.
Lab056 завершила единственный разрешённый OTA ABAB-сеанс 10 сентября.
Принятая мощность B/A +0,108895 дБ, коэффициент передачи по мощности
−0,163042 дБ, энергия символа −13,870505 дБ. Прежний провал 15,41 дБ
не воспроизвёлся. [Полный RF-отчёт](lab056_rf_result.md).
Выполненных передач 1, повторов 0; **RF TX сейчас = 0, STOP**.
Ниже сохранено описание этапов подготовки и preflight; актуальный итог
приведён в RF-отчёте и в заключительном блоке состояния.
Простыми словами: для будущего опыта создан файл, который дважды чередует
медленную и быструю передачу с практически одинаковой цифровой амплитудой.
Анализатор на искусственных записях различает равное усиление и падение на 6 дБ.
Как изменятся полезный принятый сигнал и фон между A/B в реальном эфире,
ещё не измерено; пока записан только RX-фон без TX.
Одинаковый TX VGA и одинаковая цифровая мощность не доказывают одинаковую
мощность на выходе HackRF. Для абсолютных ватт/dBm нужен калиброванный прибор.
## Зафиксированная база и границы задания
По новому явному разрешению пользователя создан коммит Lab055:
`5add95ecb8865f59075bbbced08d97b183fb3b48`
`Lab055: audit received signal energy across symbol rates`
В коммите ровно семь файлов: анализатор и тесты Lab055, её отчёт, два JSON
результата, только запись 046 журнала и только раздел Lab055 индекса.
Предшествующие незакоммиченные изменения Lab054 сохранены в рабочем каталоге.
Перед коммитом quick_gate: 823 теста, 28 функциональных проверок, 13 штатных
пропусков; pytest: 823 passed, 0 failed. Push не выполнялся.
Lab055 установила почти равную цифровую мощность активного TX
(1950,308/1950,598 код²), снижение согласованной RX-компоненты на 15,413 дБ
и сопоставимый фон в одинаковой полосе. Разные аппаратные сессии не позволяют
приписать это снижение одной скорости. Lab054 остаётся **NOT_A, 0/16**.
Повторно проверены все 34 SHA-256 входов Lab055: изменений нет.
Пользователь осознанно изменил методику: необходимого SMA-переходника нет,
поэтому сейчас выполняется подготовка контролируемого эфирного ABAB.
Кабельный контроль TX → 30 дБ → RX отложен до получения переходника
и будет отдельной проверкой, без автоматического запуска после Lab056.
Схема: MASTER TX → TX-антенна → свободное пространство → RX-антенна → SLAVE RX.
На всю ABAB сохраняются антенны, кабели, одинаковая поляризация, расстояние,
ориентация, положение оборудования, частота, усиления и common clock.
Антенны не трогают; по возможности исключают движение людей возле них.
Внешние PA и LNA, в том числе TQP3M9037, не используются.
`RF_FREQ = USER_AUTHORIZED_RF_HZ = 1400000000`: значение сообщил пользователь.
Частоту, разрешённую для лаборатории, определяет пользователь. Страна и
границы национальных диапазонов больше не являются обязательными полями
runner; повторное исследование национальной регуляторики не проводилось.
1400 МГц не подставляется по умолчанию. Mock-значения 915/916 МГц также
не являются выбором частоты для аппаратного опыта.
| Параметр эфирной схемы | Текущее значение |
|---|---|
| Центральная RF-частота | 1400000000 Гц, только лабораторная Lab056 |
| TX-антенна / RX-антенна | Имеющиеся антенны 1400 МГц по описанию пользователя; модели/маркировка не сообщены |
| Расстояние TX–RX | Примерно 3–5 м по заданию; точное измерение не сообщено |
| Взаимная ориентация и поляризация | Одинаковые, антенны неподвижны, по заданию пользователя |
| Прямая видимость LOS / отсутствие прямой видимости NLOS | LOS |
После конкретного RF_FREQ выполнен аппаратный RX-only preflight, PASS.
Сейчас STOP. TX — только после отдельного разрешения на один ABAB-сеанс.
Само сообщение частоты не является разрешением на TX. Частота 1400 МГц
не назначена рабочей частотой SDR_Rover: ориентир около 180 МГц и варианты
50/144 МГц рассматриваются отдельно, вне Lab056.
## Подготовленный TX
Файл: `data/raw/lab056/tx_abab.cs8`, signed int8 IQ, 21 177 088 байт.
SHA-256: `5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600`.
Параметры сохранены в `data/raw/lab056/tx_metadata.json` и
[preparation.json](../data/processed/lab056/preparation.json).
Используются прежние PRBS7, BPSK и RRC из тракта Lab043/Lab042:
roll-off 0,35, span 10 символов, защитные участки по 16 символов.
Fs = 8 000 000 комплексных отсчётов/с, цифровая несущая +50 кГц.
Алгоритмы PHY/FEC/CRC, пороги и штатный приёмник не изменялись.
| Параметр | A: 20 ксимв/с | B: 500 ксимв/с |
|---|---:|---:|
| Отсчётов на символ | 400 | 16 |
| Полезных PRBS7-символов | 4096 | 102400 |
| Длительность активных символов | 0,204800 с | 0,204800 с |
| Длительность сегмента с защитными участками | 0,206900 с | 0,204884 с |
| RMS всего сегмента после int8 | 44,004413 код | 44,001925 код |
| Средняя мощность всего сегмента | 1936,388382 код² | 1936,169379 код² |
| RMS активного участка | 44,227281 код | 44,010867 код |
| Средняя мощность активного участка | 1956,052408 код² | 1936,956395 код² |
| Клиппинг | 0 | 0 |
Разница средней мощности B/A всего сегмента: **−0,000491 дБ**,
критерий подготовки |разница| ≤ 0,01 дБ выполнен. Активные участки отличаются
на −0,042606 дБ из-за разной доли защитных участков; это явно сохранено
в метаданных и учитывается при сравнении коэффициента передачи.
RMS указан в кодах, средняя мощность — RMS² в код².
Последовательность: gap → A1 → gap → B1 → gap → A2 → gap → B2 → gap.
Каждый gap длится 0,1 с. A1 и A2 совпадают побайтно; B1 и B2 тоже.
Один файл содержит 10 588 544 комплексных отсчёта, **1,323568 с**.
План RX: 24 000 000 отсчётов, **3 с**, запуск на 0,5 с раньше TX.
Важно: gap — нулевые цифровые IQ при работающем TX-процессе.
Это не выключение передатчика; утечка несущей и собственный фон возможны.
Полная длительность файла включает эти участки.
## Измерения сохранённого RX
Анализатор `experiments/lab056_abab_energy.py` сначала ищет всю ABAB
корреляцией с известным TX: шаг 32 отсчёта, сетка остаточного частотного
смещения (CFO) от −2 до +2 Гц с шагом 0,25 Гц. Затем существующий метод
Lab055 уточняет задержку, CFO, фазу, постоянное смещение и комплексный
коэффициент на обучающих плитках; мощность компоненты и остатка проверяется
на отдельных плитках. При недостаточном совпадении результат UNKNOWN.
Порог 0,05 относится только к этой диагностической локализации;
он не меняет пороги PHY/CRC и не даёт независимого приёма пакетов.
Прореживание без фильтра используется только при грубом поиске. Мощности
и спектры вычисляются по исходным полным отсчётам. RX не масштабируется
для искусственного выравнивания A/B.
Для каждого A1/B1/A2/B2 сохраняются:
- Полная средняя IQ-мощность `mean(|r|²)` и мощность после удаления среднего
(AC), RMS, постоянное смещение, пик и клиппинг.
- Два ближайших окна фона по 60 мс в соседних нулевых цифровых участках.
Дополнительно — доступные окна по 50 мс вне всей последовательности.
Внешние окна нельзя автоматически назвать RF OFF без проверки времён
запуска/остановки: в текущей подготовке аппаратного фона ещё нет.
- Разность AC-мощности и среднего локального фона. Разброс оценивается
по неперекрывающимся блокам 4 мс, включая отличие фона до и после сегмента.
Если превышение не больше трёх оценок неопределённости, итоговая мощность
полезного сигнала `null`, а исходная разность сохраняется. Это критерий
численной устойчивости, не калиброванный доверительный интервал.
- Комплексный коэффициент `h = sum(conj(s)*r) / sum(|s|²)` после согласования
и удаления постоянного смещения; сравнивается `|h|²`.
- Мощность согласованной компоненты и остатка. Остаток включает шум и
искажения, которые не описывает модель; это не чистый тепловой шум.
- Интегральная мощность спектра Welch в полосах 27 и 675 кГц вокруг
+50 кГц. Для A штатная полоса 27 кГц, для B — 675 кГц;
обе полосы записываются также для сравнения фона в одинаковых условиях.
Отдельно для двух пар сохраняются B/A в дБ по принятой мощности после
вычитания фона, по `|h|²`, по согласованной компоненте и по её энергии
символа `P/Rs`. Единицы мощности — коды RX², энергии — код²·с;
это относительные цифровые оценки, не калиброванные ватты и джоули.
При одинаковой принятой мощности рост скорости в 25 раз даст уменьшение
энергии символа на 13,9794 дБ. Равенство принятой мощности сначала проверяется.
Корректировка добавляет явный вывод `h.real`, `h.imag` и фазы относительно
начала активного участка. Он вычисляется на тех же обучающих плитках
при уже найденном CFO; его модуль проверяется на совпадение с моделью
Lab055. Алгоритм согласования и разделение обучения/проверки сохранены.
Отдельно выводятся A2/A1 и B2/B1: полная raw-мощность, фон, полезная
мощность после вычитания фона, `|h|²` и согласованная компонента.
Итоговые X/Y/Z в `comparison_summary` используют отношение средних
линейных мощностей двух B к двум A. X — полезная принятая мощность
после вычитания фона, Y — `|h|²`, Z — энергия символа этой же полезной
мощности. Если вычитание фона неустойчиво хотя бы в одном сегменте,
X/Z остаются `null`; компонентные метрики и повторяемость сохраняются.
## Проверка искусственными записями
Созданы две явно синтетические записи CS8 по 3 с: одинаковое усиление
обоих режимов и уменьшение амплитуды B вдвое (мощность −6,020600 дБ).
В модель добавлены шум, постоянное смещение, CFO +0,2 Гц, фаза и неизвестное
для поиска начало 4 160 013 отсчётов. Известное начало записано в результат
после анализа и не передавалось алгоритму поиска.
| Вход модели | Пара | Разница принятой мощности B/A | Разница `|h|²` B/A | Разница энергии согласованной компоненты |
|---|---:|---:|---:|---:|
| Одинаковое усиление | 1 | −0,051497 дБ | +0,001786 дБ | −14,019673 дБ |
| Одинаковое усиление | 2 | −0,041310 дБ | −0,002894 дБ | −14,024352 дБ |
| B: −6,020600 дБ | 1 | −6,050214 дБ | −6,014144 дБ | −20,035602 дБ |
| B: −6,020600 дБ | 2 | −6,028048 дБ | −6,022175 дБ | −20,043634 дБ |
Обе проверки PASS: ошибка коэффициента передачи в каждой паре меньше
заданных 0,3 дБ. Разница принятой мощности включает небольшое различие
цифровой мощности активных участков. Это результаты модели, не RF Lab056.
Данные: [равное усиление](../data/processed/lab056/software_equal.json),
[ослабление B](../data/processed/lab056/software_minus6db.json).
При корректировке те же сохранённые IQ проанализированы без перегенерации.
Прежние файлы результатов сохранены; новые сводки:
[равное усиление, OTA summary](../data/processed/lab056/software_equal_ota_summary.json),
[ослабление B, OTA summary](../data/processed/lab056/software_minus6db_ota_summary.json).
| Искусственная запись | X: мощность B/A | Y: коэффициент передачи B/A | Z: энергия символа B/A | Повтор `|h|²`, A2/A1 | Повтор `|h|²`, B2/B1 |
|---|---:|---:|---:|---:|---:|
| Равное усиление | −0,046404 дБ | −0,000553 дБ | −14,025804 дБ | −0,002308 дБ | −0,006988 дБ |
| B: −6,020600 дБ | −6,039124 дБ | −6,018156 дБ | −20,018524 дБ | −0,002308 дБ | −0,010340 дБ |
Это по-прежнему результаты программной модели. Они не устанавливают
величину эфирного различия Lab056.
Повторный анализ существующего файла с новым именем результата:
```powershell
python experiments/lab056_abab_energy.py analyze --rx data/raw/lab056/synthetic_equal.cs8 --output data/processed/lab056/recheck_equal.json
```
Существующие выходные файлы не перезаписываются. Команды `prepare` и
`software` создают данные только при отсутствии соответствующих файлов.
## Подготовка однократного аппаратного запуска
`experiments/lab056_capture_runner.py` не имеет аппаратного CLI. Без конкретного
положительного целого RF_FREQ из задания пользователя аппаратные действия
не начинаются. Частота одинакова для всех RX-команд и единственной TX-команды;
цифровая несущая остаётся +50 кГц. Страна/регуляторная полоса не запрашиваются
программой. Геометрия сохраняется вместе с частотой в отдельном OperatingPoint.
Роли плат сохранены: MASTER/TX `000000000000000097c063dc323b97d3`,
SLAVE/RX `000000000000000097c063dc2e5327d3`. Планируется прежнее тактирование
10 МГц TCXO → MASTER → CLKOUT → SLAVE CLKIN. Требуются оба reg29 = 0x26
и SLAVE CLKIN = DETECTED после короткого RX-start. Сначала логика проверена
mock-вызовами; теперь эти значения подтверждены аппаратным preflight.
Вызов `preflight_only` после получения RF_FREQ проверит инвентаризацию,
common clock и сохранит 0,1 с RX без TX на заданной частоте при RX 24/24 дБ.
Он проверит размер, явный нулевой счётчик потерь, уровень и клиппинг,
запишет `preflight_result.json`, выключит CLKOUT и завершится с STOP.
Любая TX-команда внутри этого этапа запрещена обёрткой runner.
Файл попытки создаётся эксклюзивно; автоматического повтора нет.
После отдельного разрешения `capture_once` потребует PASS preflight той же
частоты, геометрии и SHA TX. Поскольку после preflight CLKOUT выключены,
перед фактическим сеансом опора проверяется заново при запуске. Это проверка
готовности перед единственным TX, а не повтор передачи.
TX VGA 10 дБ, RX LNA/VGA 24/24 дБ, RF amplifier и Bias-Tee OFF,
Fs 8 Мвыб/с фиксированы на всю ABAB. RX запускается первым; затем допускается
один TX-процесс с конечным числом отсчётов, без `-R`. Маркер попытки создаётся
эксклюзивно; ошибка расходует попытку, автоматического повтора нет.
Требуются процессы CLEAR до/после, правильный размер RX, явные нулевые
RX overruns/TX underruns и успешное выключение CLKOUT обеих плат.
После упреждения RX проверяются последние 65 536 записанных отсчётов:
при rail-кодах −128/127 TX не запускается, RF TX=0 и STOP. Полная запись
проверяется также после ABAB. Если клиппинг появился только при передаче,
фиксируется RF TX=1 и невалидное измерение без повтора. Уже выполненная
передача не переобозначается нулём. В отчёте сохраняются RMS, пики I/Q,
доли клиппинга; отсутствие rail-кодов само по себе не доказывает отсутствие
аналоговой компрессии. Gain не меняется автоматически ни в одном сценарии.
Аппаратные помощники переиспользованы из существующей Lab054; её файлы
`experiments/lab054_hardware_control.py` и `tools/hackrf_process_check.py`
включаются в фиксацию Lab056 как необходимые зависимости вместе с их
двумя тестовыми файлами. Без них runner Lab056 не импортируется в чистой
копии репозитория, что независимо воспроизведено verifier. Старые отчёты,
данные и записи журнала Lab054 в эту фиксацию не входят. Реализации
зависимостей не переписывались; аппаратная логика осталась проверенной.
Verifier независимо подтвердил SHA/размеры TX, нулевые gaps, побайтную
повторяемость A/B и пересчитал коэффициенты передачи вторым методом.
Он воспроизвёл ошибку разбора журнала `1 RX overruns\n0 overruns`;
исправленный разбор отклоняет ненулевые, неизвестные и неразобранные
счётчики. Повторные mock-вызовы `capture_once` закрыли находку.
Остаточных воспроизведённых находок нет. Оборудование verifier не использовал.
Исходная подготовка: 34 целевых теста passed; quick_gate: 857 тестов,
30 функциональных проверок, 13 штатных пропусков, PASS. Полный pytest:
857 passed, 0 failed, 96,50 с (кеш отключён из-за прежнего отказа доступа).
`git diff --check`: PASS. Машинная сводка:
[acceptance.json](../data/processed/lab056/acceptance.json).
Корректировка OTA: 44 целевых теста PASS; quick_gate 867 тестов /
30 функциональных проверок / 13 штатных пропусков, PASS; pytest
867 passed, 0 failed, 89,32 с. Verifier исполнил все 44 сценария
в памяти без файлов и оборудования; остаточных находок нет.
Новая сводка [ota_correction.json](../data/processed/lab056/ota_correction.json)
дополняет исходную acceptance, не заменяя исторические 857 passed.
## Как читать будущий эфирный результат
Эфирный ABAB сравнит весь путь: TX, антенны, распространение, RX. Малое
различие означает, что большой провал не воспроизведён в этом пути и этой
сессии. Оно не доказывает равенство мощности непосредственно на выходе TX.
Большое повторяемое различие тоже нельзя автоматически отнести внутрь
HackRF: частотная избирательность антенн и многолучевого распространения
остаётся частью измерения. Для отделения аппаратного эффекта пригодится
отдельно согласованный кабельный или калиброванный измерительный опыт.
Ни необходимость PA, ни изменение результатов Lab047/Lab054 из этой
подготовки не следуют. Архитектура проекта сохраняется: управление через
RadioMaster/ExpressLRS/CRSF, SDR — видеотракт с целью минимум 5 свежих кадров/с.
## Состояние перед STOP
```text
LAB055 COMMIT = 5add95ecb8865f59075bbbced08d97b183fb3b48
NEW HEAD = 5add95ecb8865f59075bbbced08d97b183fb3b48
LAB056 RESULT = OTA_ABAB_VALID_LARGE_DROP_NOT_REPRODUCED_STOP
PHYSICAL CONNECTION = OTA, LOS, fixed antennas, same polarization/orientation, approximately 3-5 m per user
RF FREQUENCY = 1400000000 Hz (Lab056 test only)
A SYMBOL RATE = 20 ksymbol/s
B SYMBOL RATE = 500 ksymbol/s
TX RMS A = 44.004413 code
TX RMS B = 44.001925 code
TX POWER DIFFERENCE B/A = -0.000491 dB (whole segment)
ABAB TX DURATION = 1.323568 s
RX PLANNED DURATION = 3 s, lead 0.5 s
COMMON CLOCK PREFLIGHT = HARDWARE PASS, both reg29=0x26, SLAVE CLKIN=DETECTED
RX PREFLIGHT = PASS, 800000 complex samples, 0.1 s, overruns=0
CLIPPING = 0
READY FOR ANOTHER TX = NO (authorized single attempt consumed)
COMPLETED TX SESSIONS = 1
AUTOMATIC TX REPEATS = 0
RX ABAB RECORD = 24000000 complex samples, 3 s, clipping=0, overruns=0
TX UNDERRUNS = 0
RECEIVED POWER DIFFERENCE B/A = +0.108895 dB
TX-MATCHED CHANNEL GAIN DIFFERENCE B/A = -0.163042 dB
ENERGY PER SYMBOL DIFFERENCE = -13.870505 dB
BASELINE pytest = 857 passed (сохранённый результат подготовки)
quick_gate = PASS, 867 tests / 30 functional / 13 skipped
pytest = PASS, 867 passed / 0 failed (после корректировки)
git diff --check = PASS
RF TX = 0
LAB056 COMMIT = NOT_PERFORMED
PUSH = NOT_PERFORMED
CURRENT SINGLE-TX AUTHORIZATION = CONSUMED
STOP
```

222
docs/lab056_rf_result.md Normal file
View File

@@ -0,0 +1,222 @@
# Lab056 — единственный OTA ABAB: результат
10 сентября 2026 года, сеанс 12:32:24–12:32:28 по Москве.
**Внутри одного короткого эфирного сеанса большой провал при переходе
20 → 500 ксимв/с не воспроизвёлся.** Принятая мощность после вычитания фона
у B выше A на 0,109 дБ; согласованный коэффициент передачи по мощности
ниже на 0,163 дБ. Обе пары A/B дают близкие результаты. Энергия символа
уменьшилась на 13,871 дБ: при почти одинаковой принятой мощности она
приходится на в 25 раз большее число символов в секунду.
Это результат всего OTA-пути с антеннами и распространением. Он не
измеряет выходные dBm HackRF и не объясняет задним числом причину прежнего
различия Lab047/Lab054 на 15,41 дБ. Lab054 остаётся **NOT_A, 0/16**.
Выполнена ровно **одна** передача. Сейчас **RF TX = 0**, передатчик
остановлен; повторов нет. Разрешённая попытка израсходована. **STOP.**
## Фактические условия
Пользователь отдельно разрешил этот сеанс после аппаратного RX-only
preflight. Частота 1 400 000 000 Гц относится только к Lab056, не является
выбором рабочего диапазона SDR_Rover. Ориентир около 180 МГц и варианты
50/144 МГц рассматриваются отдельно и здесь не исследовались.
OTA: MASTER TX → TX-антенна → LOS → RX-антенна → SLAVE RX. По заданию
пользователя антенны неподвижны, ориентация и поляризация одинаковы,
расстояние примерно 3–5 м, кабели не меняются. Использованы имеющиеся
антенны на 1400 МГц; модели/маркировка и точное измеренное расстояние
не сообщены. Программа не измеряет геометрию; это описание пользователя.
Один сохранённый TX-файл содержит A1 → gap → B1 → gap → A2 → gap → B2
с дополнительными начальным/конечным gaps. A = 20 ксимв/с, B = 500 ксимв/с.
Fs 8 Мвыб/с; цифровая несущая +50 кГц; прежние PRBS7/BPSK/RRC.
TX VGA 10 дБ, RX LNA/VGA 24/24 дБ, RF amplifier/Bias-Tee OFF.
Внешние PA/LNA отсутствуют. Частота, усиления, Fs и параметры формы
не менялись между сегментами. PHY/FEC/CRC, пороги и timing не изменялись.
Цифровой RMS всего сегмента A/B: 44,004413/44,001925 код;
разница мощности B/A −0,000491 дБ. Разница активных частей −0,042606 дБ
из-за разной доли защитных участков сохранена в метаданных и не скрывается.
RX не нормировался для искусственного выравнивания A/B.
## Полнота записи и аппаратные проверки
| Проверка | Фактический результат |
|---|---|
| Число TX-процессов | 1, без `-R`, конечный `-n 10588544` |
| TX-файл | 21 177 088 байт, 10 588 544 комплексных отсчёта, 1,323568 с |
| Основной RX | 48 000 000 байт, 24 000 000 комплексных отсчётов, ровно 3 с |
| Порядок запуска | RX раньше TX на 0,517342 с по журналу запуска процессов |
| MASTER / SLAVE reg29 | 0x26 / 0x26 |
| SLAVE CLKIN | DETECTED в пределах штатного ожидания запуска опоры |
| RX overruns / TX underruns | 0 / 0 по всем явным счётчикам журналов |
| Клиппинг RX до TX | 0 / 65 536 отсчётов проверенного хвоста |
| Клиппинг полного RX после ABAB | 0 / 24 000 000 отсчётов; 0 / 48 000 000 компонент |
| Полный RX: RMS / максимумы абсолютных I,Q | 2,235380477 код / 5, 6 код |
| Процессы до / после | CLEAR / CLEAR |
| Завершение | CLKOUT обеих плат выключены, ошибок cleanup нет |
Общая опора проверена при запуске заново, поскольку предыдущий preflight
завершился выключением CLKOUT. Первый запрос CLKIN ещё не обнаружил опору;
второй обнаружил её через 137,359 мс штатного ожидания. После PASS
состоялся единственный TX; опрос CLKIN не является повтором передачи.
В журнале 14 команд: инвентаризация, опора, два коротких RX-start по
65 536 отсчётов для выбора опоры, один основной RX, один TX и cleanup.
После запуска TX до его окончания других аппаратных команд нет.
Параметры команд и их исходный вывод сохранены; аппаратной перестройки
между A/B не было. Физическая неподвижность антенн следует из заданных
пользователем условий, а не из программного измерения.
Утилита указала TX transfer time 1,32538 с и RX transfer time 3,01535 с.
Это служебные интервалы обмена, отличные от длительности отсчётов файла;
они не используются вместо расчёта длительности по числу отсчётов и Fs.
Отсутствие rail-кодов не является калиброванной проверкой аналоговой
компрессии; обнаруженных цифровых признаков насыщения нет.
## Все четыре сегмента
Подготовленный анализатор нашёл ABAB с нормированной корреляцией 0,384366
и грубым остаточным CFO 0 Гц. Оценённые начала всей последовательности
по отдельным сегментам: 4 454 791, 4 454 791, 4 454 790, 4 454 791 отсчёт.
Разброс составляет один отсчёт, 0,125 мкс. Все сегменты расположены
внутри RX, идут в правильном порядке и не перекрываются.
| Сегмент | Начало сегмента, отсчёт RX | Начало, с | Конец, с | Активных отсчётов |
|---|---:|---:|---:|---:|
| A1 | 5 254 791 | 0,656848875 | 0,863748875 | 1 638 400 |
| B1 | 7 709 991 | 0,963748875 | 1,168632875 | 1 638 400 |
| A2 | 10 149 062 | 1,268632750 | 1,475532750 | 1 638 400 |
| B2 | 12 604 263 | 1,575532875 | 1,780416875 | 1 638 400 |
Длительность активного участка каждого сегмента 0,2048 с. С защитными
участками A длится 0,2069 с, B — 0,204884 с. Межсегментные интервалы
составляют 0,1 с с расхождением не более одного отсчёта из-за согласования.
В спектрах A и B видны соответственно узкая и широкая полосы PRBS/BPSK.
Границы, ширина спектров и согласованные компоненты подтверждают
наличие четырёх сегментов; это диагностическое измерение с TX-эталоном,
не независимый приём пакетов.
![Запись, мощности и спектры единственного ABAB](../data/processed/lab056/rf_overview.png)
## Измерения A1/B1/A2/B2
Все мощности ниже — **код² RX**, не ватты/dBm. `h` выражает отношение
цифровых кодов RX/TX после согласования и удаления постоянного смещения.
Для вычитания фона используется AC-мощность — после удаления среднего,
поэтому она показана отдельно от RAW, включающего постоянную составляющую.
| Метрика | A1 | B1 | A2 | B2 |
|---|---:|---:|---:|---:|
| RAW RX POWER, включая среднее | 5,303053589 | 5,328170166 | 5,311703491 | 5,396551514 |
| RX AC POWER, без среднего | 1,084963566 | 1,092709351 | 1,049867204 | 1,090638777 |
| BACKGROUND POWER, AC | 0,622282607 | 0,616699337 | 0,635792032 | 0,667630886 |
| BACKGROUND-SUBTRACTED SIGNAL POWER | 0,462680958 | 0,476010015 | 0,414075173 | 0,423007891 |
| Оценка неопределённости вычитания фона | 0,029702311 | 0,034542825 | 0,017352868 | 0,047227716 |
| TX-MATCHED COMPLEX GAIN h | −0,008543899 + j0,009310849 | −0,008089660 + j0,009397484 | −0,007000883 + j0,010205712 | −0,006710537 + j0,010126447 |
| Коэффициент передачи по мощности, abs(h)² | 0,000159690119 | 0,000153755289 | 0,000153168935 | 0,000147576238 |
| MATCHED SIGNAL COMPONENT POWER | 0,312320987 | 0,297815525 | 0,299566893 | 0,285847043 |
| RESIDUAL POWER | 0,821513728 | 0,884789710 | 0,782684444 | 0,880722972 |
| INTEGRATED SPECTRAL POWER, полоса 27 кГц | 0,347264323 | 0,029697350 | 0,335922975 | 0,030437417 |
| INTEGRATED SPECTRAL POWER, полоса 675 кГц | 0,508299262 | 0,512832243 | 0,500120185 | 0,533314850 |
Полосы спектрального интегрирования центрированы на +50 кГц. Фактическая
расчётная полоса A — 27 кГц, B — 675 кГц; строка 675 кГц также даёт
сравнение в общей полосе. Спектральные интегралы включают фон. Различие
значений в полосе 27 кГц между A/B не является потерей полной мощности B:
большая часть широкого сигнала B лежит вне этой узкой полосы.
Вычитание фона устойчиво во всех четырёх сегментах по заранее подготовленному
критерию: превышение больше трёх оценок неопределённости. Эта оценка учитывает
разброс блоков и отличие фона до/после, но не является калиброванным
доверительным интервалом. Фон берётся из ближайших нулевых цифровых gaps;
TX-процесс в них работает, поэтому gaps нельзя называть выключенным RF.
Отдельные внешние окна фона и их спектры сохранены в полном JSON.
Комплексный h оценён на обучающих плитках; его фаза относится к началу
активного участка после коррекции CFO. Мощность согласованной компоненты
и остатка измерена на проверочных плитках. Остаток содержит шум и
неописанные моделью искажения. Поэтому он не равен отдельно измеренному
фону, а согласованная компонента не равна всей мощности после вычитания фона.
Полная RAW/AC-статистика относится ко всему активному участку; плиточные
метрики не следует складывать с ней как одну точную энергетическую сумму.
## Повторяемость и итоговые X/Y/Z
В таблице повторяемости знак относится ко **второму повтору относительно
первого**: A2/A1 и B2/B1, 10 log10 отношения мощностей.
| Сравнение повторов | RAW RX | Фон AC | Полезная мощность после фона | abs(h)² | Согласованная компонента |
|---|---:|---:|---:|---:|---:|
| A1 / A2 REPEATABILITY: A2/A1 | +0,007078 дБ | +0,093274 дБ | −0,482024 дБ | −0,181074 дБ | −0,181074 дБ |
| B1 / B2 REPEATABILITY: B2/B1 | +0,055382 дБ | +0,344629 дБ | −0,512676 дБ | −0,178136 дБ | −0,178136 дБ |
| Пара A/B | Принятая мощность B/A после фона | abs(h)² B/A | Согласованная компонента B/A |
|---|---:|---:|---:|
| B1/A1 | +0,123345 дБ | −0,164480 дБ | −0,206538 дБ |
| B2/A2 | +0,092693 дБ | −0,161543 дБ | −0,203601 дБ |
Итоговые X/Y/Z — отношение средних **линейных** мощностей B1/B2 к A1/A2,
а не среднее значений в дБ. Z рассчитано из той же мощности после фона,
что X, с делением на скорость символов. Это энергия в код²·с, не джоули.
```text
500K / 20K RECEIVED POWER DIFFERENCE = +0.108895 dB
500K / 20K TX-MATCHED CHANNEL GAIN DIFFERENCE = -0.163042 dB
ENERGY PER SYMBOL DIFFERENCE = -13.870505 dB
```
Расчёт Z: +0,108895 − 10 log10(500000/20000) = −13,870505 дБ.
Отдельная энергия согласованной компоненты в парных результатах изменилась
на −14,185938 и −14,183001 дБ; это другой числитель, её не подменяют Z.
## Инженерная интерпретация
1. Внутри этой сессии 20 и 500 ксимв/с дали почти равные мощности. Небольшое
различие повторилось в обеих парах; утверждать точную частотную
характеристику по двум повторам нельзя.
2. Вторые повторы слабее первых на 0,48/0,51 дБ по мощности после фона
и примерно на 0,18 дБ по коэффициенту передачи. Фон также немного
меняется и содержит кратковременные всплески; при этом относительное
сравнение B/A сохраняется: расхождение двух оценок X около 0,031 дБ,
двух оценок Y около 0,003 дБ.
3. Прежний провал порядка 15,41 дБ **не воспроизвёлся** в текущей короткой
OTA-сессии. Причина различия старых раздельных опытов остаётся
неустановленной; их результаты не пересматриваются.
4. По OTA нельзя объявить эффект собственной аппаратной потерей HackRF
Plus: антенны и распространение входят в измеренный путь. В этой
сессии большого эффекта вообще не обнаружено. Абсолютная мощность
на выходе передатчика не измерена.
5. Кабельный контроль после получения SMA-переходника остаётся необходимым
отдельным этапом для отделения свойств аппаратуры от антенн и канала.
OTA его не заменяет. Автоматического повторения опыта или добавления
PA/внешнего LNA нет; необходимость усилителя не установлена.
## Проверка и артефакты
Verifier независимо прочитал журналы и raw IQ без оборудования и записи
файлов: проверил число команд, постоянство параметров, опору, размеры,
SHA, отсутствие клиппинга/потерь и согласованность четырёх начал. Raw AC,
два окна фона, CFO и комплексный МНК пересчитаны вторым способом;
результаты совпали с анализатором до 1e−12. Воспроизведённых находок нет.
RX SHA-256: `e336dd653fa554058052fcf851e9f2462f66b8cf7c710815ca2b9a09ffc23ad4`.
TX SHA-256: `5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600`.
- [Полный подготовленный анализ](../data/processed/lab056/rf_analysis.json).
- [Итоговая машинная сводка](../data/processed/lab056/rf_result.json).
- [Авторизация и фиксированные параметры](../data/processed/lab056/rf_session_authorization.json).
- [Фактические команды и время](../data/processed/lab056/rf_session_commands.json).
- Raw: `data/raw/lab056/rx_stream.cs8`, `capture_result.json`,
`hardware_attempt.json`, `tx_process_result.json`, `rx_stdout.txt`, `rx_stderr.txt`.
Исходные результаты подготовки, preflight и искусственные записи сохранены.
Код анализатора/runner и TX не изменялись; использована проверенная версия
44 целевых теста / quick_gate 867/30/13 / pytest 867 passed. Для анализа
аппаратной записи полные тесты повторно не запускались. Diff check PASS.
HEAD `5add95ecb8865f59075bbbced08d97b183fb3b48`, Lab056 не коммитилась,
push не выполнялся. Приём пакетов, видео и FEC не проверялись.
**Выполненных TX-сеансов: 1. RF TX сейчас = 0. STOP.**

View File

@@ -0,0 +1,98 @@
# Lab056 — аппаратный common-clock и RX-only preflight
10 сентября 2026 года, 12:16:28–12:16:29 по Москве.
**Аппаратный preflight PASS. RF TX = 0. STOP перед отдельным разрешением TX.**
Проверена общая опора и записан фон при выключенном передатчике.
Сравнение A/B по эфиру ещё не выполнялось.
```text
RF_FREQ = 1400000000 Hz
COMMON CLOCK = PASS; MASTER reg29=0x26; SLAVE reg29=0x26; SLAVE CLKIN=DETECTED
RX PREFLIGHT = PASS; 800000 complex samples; 0.1 s; RX overruns=0
CLIPPING = 0 / 800000 samples (0%)
RF TX = 0
READY FOR SINGLE ABAB TX = YES (technical preflight; separate authorization pending)
STOP
```
## Условия
По заданию пользователя 1400 МГц — только частота текущего лабораторного
Lab056. Она не назначает рабочий диапазон SDR_Rover. Ориентир дальнейших
полевых работ около 180 МГц рассматривается отдельно; антенна на этот
диапазон уже имеется. Варианты 50/144 МГц здесь не исследуются.
Схема задана пользователем: MASTER TX → TX-антенна → LOS → RX-антенна → SLAVE RX.
Используются имеющиеся антенны на 1400 МГц. Точные модели/маркировка не
сообщены и программно не установлены; они не являлись препятствием для
этого RX-only этапа. Антенны неподвижны, ориентация и поляризация одинаковы.
Расстояние по заданию примерно 3–5 м; точное измеренное значение отсутствует.
Кабели и геометрия сохраняются на будущую ABAB.
Аппаратный RX: Fs 8 Мвыб/с, LNA/VGA 24/24 дБ, RF amplifier и Bias-Tee OFF.
Внешние PA/LNA не используются. TX VGA 10 дБ остаётся запланированным
параметром будущего сеанса; TX в этом этапе не включался.
MASTER `000000000000000097c063dc323b97d3` и SLAVE
`000000000000000097c063dc2e5327d3` подтверждены свежей инвентаризацией.
Обе платы определены как HackRF One r9 с firmware/libhackrf 2024.02.1.
## Что выполнено
Перед оборудованием подтверждены HEAD, пустой Git index, SHA проверенного
кода и неизменность готового TX. Проверка процессов дала CLEAR.
Выполнены короткие RX-start по 65 536 отсчётов на MASTER и SLAVE для выбора
источника тактирования, чтения reg29 обеих плат и SLAVE CLKIN. Оба reg29
равны 0x26; CLKIN обнаружен первым запросом, через 67,787 мс после начала
ожидания опоры. Затем SLAVE записал 800 000 комплексных отсчётов RX, 0,1 с.
Все три команды hackrf_transfer работали только с `-r`, на одной частоте
и с одинаковыми RX-параметрами. Журнал команд не содержит `-t`.
| Метрика сохранённого RX | Значение |
|---|---:|
| Размер CS8 | 1 600 000 байт |
| Комплексных отсчётов | 800 000 |
| Полная средняя мощность | 4,85388125 код² |
| Мощность после удаления среднего | 0,6658039582 код² |
| RMS полного IQ | 2,203152571 код |
| Среднее I / Q | 0,92535625 / 1,82532 код |
| Максимум абсолютного I / Q | 4 / 6 код |
| Максимальная комплексная амплитуда | 6,08276253 код |
| Отсчётов с rail-кодами −128/127 | 0 |
| RX overruns | 0 |
Цифровых признаков насыщения в этом RX-only фоне нет. Это не доказывает
отсутствие перегрузки при включённом TX: его сигнал пока отсутствовал.
Из этих отсчётов также нельзя установить абсолютную RF-мощность или
доказать отсутствие аналоговой компрессии. При будущем разрешённом запуске
сохраняются подготовленные проверки клиппинга без автоподбора gain.
SHA-256 RX: `c4257f26e4cc8562eb32228f9a50fdeb055879c928af0744cdf4a81cf7cd085b`.
SHA-256 TX: `5b34bbe1a8e6d037d2d6d367a74e9eb84e8940db7da61600ffaa7f7497d4f600`.
После проверки CLKOUT обеих плат выключены успешно, процессы CLEAR,
ошибок cleanup нет. Больше аппаратных действий не выполнялось.
## Доказательства и STOP
- [План и описание геометрии](../data/processed/lab056/rx_preflight_plan.json).
- [Журнал фактических команд с временем и выводом](../data/processed/lab056/rx_preflight_commands.json).
- [Результат RX-only preflight](../data/processed/lab056/rx_preflight_result.json).
- Исходный RX и исходный результат: `data/raw/lab056/preflight_rx.cs8`,
`preflight_result.json`; маркер попытки: `preflight_attempt.json`.
Код и TX waveform не менялись. Последние программные проверки относятся
к этой же версии: 44 целевых теста, quick_gate 867/30/13 PASS и pytest
867 passed. Полные тесты повторно не запускались для записи аппаратного
результата. Git diff --check PASS. Lab054 NOT_A, 0/16 сохранён.
Verifier проверил только сохранённые файлы: 12 команд, три RX-start,
ни одной TX-команды; регистры, CLKIN, cleanup и параметры подтверждены
по исходному выводу. Целочисленный пересчёт мощности/клиппинга и SHA
полностью согласован с JSON. Воспроизведённых расхождений нет, PASS.
Дополнительных аппаратных команд verifier не выполнял.
HEAD `5add95ecb8865f59075bbbced08d97b183fb3b48`. Lab056 не коммитилась,
push не выполнялся. Технические условия preflight выполнены; отдельного
разрешения на единственный ABAB TX ещё нет. **STOP.**

View File

@@ -0,0 +1,359 @@
"""Lab054: подготовка короткого BPSK-контроля; RF только отдельной командой."""
from __future__ import annotations
import argparse
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 experiments import lab047_jpeg_object_transport as hardware
from experiments import lab049_bpsk_symbol_rate as phy
from experiments.lab053_packet_processing import bind
from experiments.lab053_streaming_receiver import StreamingReceiver
from tools.hackrf_process_check import check_processes, require_clear
clock = hardware.lab044.lab043
MODE = phy.PhyMode(500_000)
RF_HZ = 1_400_000_000
PACKETS = 16
RX_SECONDS = 3
RX_LEAD_SECONDS = 0.5
RX_SAMPLES = RX_SECONDS * MODE.sample_rate_hz
DIRECTORY = Path("data/raw/lab054")
OUTPUT = Path("data/processed/lab054")
def save(path: Path, value: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")
def plans():
"""Обычный бинарный объект, 16 фрагментов прежнего формата; не видеопоток."""
random_generator = np.random.default_rng(54054)
data = random_generator.integers(0, 256, PACKETS * phy.lab046.FRAGMENT_DATA_SIZE, dtype=np.uint8)
return phy.lab046.build_object_frames(data.tobytes(), object_id=54054)
def parameters() -> dict:
return {"rf_hz": RF_HZ, "sample_rate_hz": MODE.sample_rate_hz,
"symbol_rate": MODE.symbol_rate, "samples_per_symbol": MODE.samples_per_symbol,
"carrier_hz": phy.lab045.CARRIER_OFFSET_HZ,
"master_tx": clock.MASTER_SERIAL, "slave_rx": clock.SLAVE_SERIAL,
"tx_vga_db": 10, "rx_lna_db": 24, "rx_vga_db": 24,
"amplifier": False, "bias_tee": False,
"threshold": hardware.FIXED_HARDWARE_CANDIDATE_THRESHOLD}
def transfer_command(serial: str, path: Path | str, samples: int, *, transmit=False) -> list[str]:
command = [str(clock.HACKRF_TOOLS_DIR / "hackrf_transfer.exe"), "-d", serial,
"-t" if transmit else "-r", str(path), "-f", str(RF_HZ),
"-s", str(MODE.sample_rate_hz), "-n", str(samples)]
command += ["-x", "10"] if transmit else ["-l", "24", "-g", "24"]
return command + ["-a", "0", "-p", "0", "-B"]
def short_receive(serial, runner):
"""Штатный запуск приёмника для выбора опоры, без передающего режима."""
result = runner(transfer_command(serial, "NUL", clock.SHORT_RX_SAMPLE_COUNT))
if result.returncode:
raise RuntimeError(f"Короткий RX не выполнен: {result.stderr}")
def disable_clock_outputs(runner) -> list[str]:
"""Попытаться отключить обе платы даже при timeout первой команды."""
errors = []
for serial in (clock.MASTER_SERIAL, clock.SLAVE_SERIAL):
try:
clock._set_clkout(serial, False, runner)
except Exception as error:
errors.append(f"{serial}: {error}")
return errors
clock_preflight = bind(clock.common_clock_preflight, _short_rx=short_receive,
_disable_clock_outputs=disable_clock_outputs)
def preflight_only(directory: Path = OUTPUT, runner=None, process_checker=None) -> dict:
"""Реальный RX-only preflight; выходы тактирования выключаются в finally."""
runner = runner or clock._default_command_runner
process_checker = process_checker or check_processes
record = {"rf_tx_count": 0, "passed": False}
guard = clock.TransmitGuard()
clock_started = False
try:
process_state = process_checker()
record["process_check"] = process_state.to_dict()
require_clear(process_state)
record["inventory"] = phy.lab045._verify_device_inventory(runner)
clock_started = True
record["clock"] = clock_preflight(runner, guard)
record["passed"] = True
except Exception as error:
record["error"] = str(error)
raise
finally:
record["cleanup_errors"] = disable_clock_outputs(runner) if clock_started else []
record["cleanup_performed"] = clock_started
if clock_started:
try:
process_state = process_checker()
record["process_check_after_cleanup"] = process_state.to_dict()
require_clear(process_state)
except Exception as error:
record["cleanup_errors"].append(str(error))
record["passed"] = record["passed"] and not record["cleanup_errors"]
save(directory / "common_clock_preflight.json", record)
if not record["passed"]:
raise RuntimeError("Не удалось выключить CLKOUT")
return record
def prepare(directory: Path = DIRECTORY) -> dict:
directory.mkdir(parents=True, exist_ok=True)
path = directory / "tx_stream.cs8"
if path.exists():
raise FileExistsError("TX-файл уже существует; подготовка его не перезаписывает")
with path.open("xb") as stream:
for frame in plans():
stream.write(phy.build_parameterized_tx_cs8(frame.plan, MODE))
count = path.stat().st_size // 2
result = {"stage": "PREPARATION_ONLY", "rf_tx_count": 0,
"head": subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip(),
"parameters": parameters(), "packets": PACKETS, "tx_samples": count,
"tx_bytes": path.stat().st_size, "tx_seconds": count / MODE.sample_rate_hz,
"tx_sha256": hardware._sha256_file(path), "rx_seconds": RX_SECONDS,
"rx_samples": RX_SAMPLES, "rx_lead_seconds": RX_LEAD_SECONDS,
"rx_command": transfer_command(clock.SLAVE_SERIAL, directory / "rx_stream.cs8", RX_SAMPLES),
"tx_command": transfer_command(clock.MASTER_SERIAL, path, count, transmit=True)}
save(directory / "tx_metadata.json", result)
save(OUTPUT / "preparation.json", result)
return result
def validate_prepared(directory: Path) -> dict:
metadata = json.loads((directory / "tx_metadata.json").read_text(encoding="utf-8"))
path = directory / "tx_stream.cs8"
expected = hashlib.sha256()
count = 0
for frame in plans():
waveform = phy.build_parameterized_tx_cs8(frame.plan, MODE)
expected.update(waveform)
count += len(waveform)
if (metadata["parameters"] != parameters() or metadata["stage"] != "PREPARATION_ONLY"
or metadata["rf_tx_count"] != 0 or metadata["packets"] != PACKETS
or metadata["tx_samples"] * 2 != count or path.stat().st_size != count
or metadata["tx_sha256"] != expected.hexdigest()
or hardware._sha256_file(path) != expected.hexdigest()):
raise RuntimeError("TX-артефакты или параметры изменены")
return metadata
def decode_file(path: Path) -> tuple[dict, list]:
"""RX получает только CS8-файл; TX truth отсутствует в интерфейсе."""
observations = []
receiver = StreamingReceiver(packet_observer=lambda row, outcome: observations.append((row.copy(), outcome)))
received = []
clipping = 0
try:
with path.open("rb") as stream:
while block := stream.read(2 * 65536):
values = np.frombuffer(block, np.int8)
if len(values) % 2:
raise ValueError("Неполный IQ-отсчёт")
clipping += int(np.count_nonzero((values == -128) | (values == 127)))
samples = (values[::2].astype(np.float32) + 1j * values[1::2]).astype(np.complex64)
received.extend(receiver.push_iq_block(samples))
received.extend(receiver.finish())
finally:
if not receiver.closed:
receiver.finish()
rows = []
for row, outcome in observations:
frame = outcome.frame
rows.append({"accepted": outcome.accepted, "reason": outcome.reason,
"sync_pass": outcome.refinement_pass and outcome.timing_pass and outcome.carrier_pass,
"phy_header_pass": outcome.phy_header_pass, "fec_pass": outcome.fec_pass,
"crc": outcome.crc, "start": row["absolute_refined"],
"timing": frame.timing_offset_samples if frame else None,
"cfo_hz": frame.estimated_cfo_hz if frame else None,
"phase_rad": frame.estimated_phase_rad if frame else None,
"snr_db": frame.snr_db if frame else None,
"preamble_errors": frame.preamble_errors if frame else None})
return {"independent": True, "packets": len(received),
"frames_detected": len({r["start"] for r in rows if r["sync_pass"]}),
"frames_detected_definition": "Unique refined starts with refinement/timing/carrier PASS; may include false candidates",
"strict_candidates": len(rows), "phy_header_pass": sum(r["phy_header_pass"] for r in rows),
"fec_pass": sum(r["fec_pass"] for r in rows),
"crc_pass": sum(r["crc"] == phy.lab045.CRC_PASS for r in rows),
"crc_fail": sum(r["crc"] == phy.lab045.CRC_FAIL for r in rows),
"clipping_components": clipping, "rows": rows}, observations
def roundtrip(directory: Path = DIRECTORY) -> dict:
validate_prepared(directory)
result, observations = decode_file(directory / "tx_stream.cs8")
result.update(compare_after_verdict(observations))
result["passed"] = (result["packets"] == result["exact_packets"] == result["unique_exact_packets"] == PACKETS
and result["post_fec_errors"] == 0)
save(OUTPUT / "software_roundtrip.json", result)
if not result["passed"]:
raise RuntimeError("Программный roundtrip FAIL")
return result
def compare_after_verdict(observations) -> dict:
"""Сверка разрешена только после завершения независимого приёма."""
by_payload = {frame.plan.payload: frame.plan for frame in plans()}
comparisons = []
matched_payloads = set()
exact_count = 0
for _, outcome in observations:
if outcome.accepted and outcome.frame:
frame = outcome.frame
plan = by_payload.get(frame.reception.payload_bytes)
if plan is not None:
if frame.reception.packet_bytes == plan.packet:
matched_payloads.add(frame.reception.payload_bytes)
exact_count += 1
comparisons.append(phy.lab045.diagnostic_pre_and_post_fec(
frame.reception, frame.post_preamble_soft, plan))
return {"exact_packets": exact_count, "unique_exact_packets": len(matched_payloads),
"payload_matched_packets": len(comparisons),
"pre_fec_errors": sum(item["pre_fec_coded"]["bit_errors"] for item in comparisons) if comparisons else None,
"post_fec_errors": sum(item["post_fec_packet"]["bit_errors"] for item in comparisons) if comparisons else None,
"error_scope": "CRC accepted packets matched by payload AFTER independent verdict; unmatched packets UNKNOWN"}
def analyze_capture(directory: Path = DIRECTORY) -> dict:
"""Офлайн-анализ сохранённой записи, без повторной передачи."""
evidence = json.loads((directory / "capture_result.json").read_text(encoding="utf-8"))
path = directory / "rx_stream.cs8"
if evidence.get("rx_sha256") != hardware._sha256_file(path):
raise RuntimeError("Нет подтверждённого SHA записи")
result, observations = decode_file(path)
save(OUTPUT / "independent_result.json", result)
result.update(compare_after_verdict(observations))
result.update(frames_tx=PACKETS, rx_overruns="UNKNOWN_UNLESS_EXPLICIT_IN_LOG",
tx_underruns="UNKNOWN_UNLESS_EXPLICIT_IN_LOG")
result["result"] = "A" if (result["unique_exact_packets"] >= 15 and evidence["passed"]
and result["post_fec_errors"] == 0
and result["clipping_components"] == 0) else "NOT_A"
result["scope"] = "One short control only; no long-term stability or video RF claim"
save(OUTPUT / "hardware_result.json", result)
return result
def capture_once(directory: Path = DIRECTORY, *, authorization=False, runner=None,
process_starter=None, process_checker=None, sleep_function=time.sleep) -> dict:
"""Один конечный TX; эксклюзивный маркер запрещает повтор после любой ошибки."""
if authorization is not True:
raise PermissionError("Нужно отдельное разрешение пользователя на Lab054 RF")
metadata = validate_prepared(directory)
runner = runner or clock._default_command_runner
process_starter = process_starter or clock._default_process_starter
process_checker = process_checker or check_processes
with (directory / "hardware_attempt.json").open("x", encoding="utf-8") as stream:
json.dump({"authorized_once": True, "automatic_retry": False}, stream)
guard = clock.TransmitGuard()
process = None
clock_started = False
result = {"passed": False, "rf_tx_count": 0}
try:
process_state = process_checker()
result["process_check"] = process_state.to_dict()
require_clear(process_state)
result["inventory"] = phy.lab045._verify_device_inventory(runner)
clock_started = True
result["clock"] = clock_preflight(runner, guard)
receive_path = directory / "rx_stream.cs8"
process = process_starter(transfer_command(clock.SLAVE_SERIAL, receive_path, RX_SAMPLES),
directory / "rx_stdout.txt", directory / "rx_stderr.txt")
sleep_function(RX_LEAD_SECONDS)
if process.poll() is not None:
raise RuntimeError("RX завершился до TX")
guard.register_transmit()
result["rf_tx_count"] = guard.transmit_calls
transmitted = runner(transfer_command(clock.MASTER_SERIAL, directory / "tx_stream.cs8",
metadata["tx_samples"], transmit=True))
(directory / "tx_stdout.txt").write_text(transmitted.stdout or "", encoding="utf-8")
(directory / "tx_stderr.txt").write_text(transmitted.stderr or "", encoding="utf-8")
if transmitted.returncode or process.wait(RX_SECONDS + 10):
raise RuntimeError("Ошибка единственного TX/RX; повтор запрещён")
process.close_logs()
if receive_path.stat().st_size != RX_SAMPLES * 2:
raise RuntimeError("Неверный размер RX")
for role in ("rx", "tx"):
log = "".join((directory / f"{role}_{kind}.txt").read_text(encoding="utf-8") for kind in ("stdout", "stderr"))
if not clock._transfer_log_is_clean(log):
raise RuntimeError(f"{role}: overrun/underrun; STOP")
result.update(passed=True, rx_sha256=hardware._sha256_file(receive_path))
except Exception as error:
result["error"] = str(error)
raise
finally:
result["cleanup_errors"] = []
if process is not None:
try:
process.terminate()
except Exception as error:
result["cleanup_errors"].append(str(error))
if clock_started:
result["cleanup_errors"].extend(disable_clock_outputs(runner))
try:
process_state = process_checker()
result["process_check_after_cleanup"] = process_state.to_dict()
require_clear(process_state)
result["lingering_processes"] = []
except Exception as error:
result["cleanup_errors"].append(str(error))
result["lingering_processes"] = None
result["passed"] = result["passed"] and not result["cleanup_errors"] and not result["lingering_processes"]
save(directory / "capture_result.json", result)
if not result["passed"]:
raise RuntimeError("Очистка аппаратного сеанса не прошла; STOP")
return result
def run_functional_tests() -> None:
assert MODE.samples_per_symbol == 16
assert len(plans()) == PACKETS
assert "-t" not in transfer_command(clock.MASTER_SERIAL, "NUL", 65536)
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", "roundtrip", "preflight", "capture", "analyze"), required=True)
parser.add_argument("--authorize-one-rf", action="store_true")
args = parser.parse_args()
if args.stage == "prepare":
result = prepare()
elif args.stage == "roundtrip":
result = roundtrip()
elif args.stage == "preflight":
result = preflight_only()
elif args.stage == "analyze":
result = analyze_capture()
else:
result = capture_once(authorization=args.authorize_one_rf)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,312 @@
"""Lab056: программная подготовка ABAB и диагностика сохранённого RX."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import sys
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 lab043_pluto_to_rtlsdr as shaping
from experiments import lab055_energy_audit as energy
SAMPLE_RATE = 8_000_000
CARRIER = 50_000
GAP_SAMPLES = 800_000
RX_SAMPLES = 24_000_000
RX_LEAD_SECONDS = .5
RAW_DIRECTORY = energy.PROJECT_ROOT / "data/raw/lab056"
OUTPUT = energy.PROJECT_ROOT / "data/processed/lab056"
def save_new(path: Path, value: dict) -> None:
text = json.dumps(value, ensure_ascii=False, indent=2, allow_nan=False) + "\n"
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("x", encoding="utf-8") as stream:
stream.write(text)
def build_burst(symbol_rate: int) -> np.ndarray:
"""Прежние PRBS7/BPSK/RRC, 204,8 мс символов, RMS всего burst=44."""
if symbol_rate not in (20000, 500000):
raise ValueError("Разрешены только два исследуемых режима")
symbols = 2. * shaping.pilot_prbs7(4096 * (symbol_rate // 20000)) - 1.
samples_per_symbol = SAMPLE_RATE // symbol_rate
waveform = shaping.shape_frame_like_lab042(symbols, samples_per_symbol)
waveform *= np.exp(2j * np.pi * CARRIER * np.arange(len(waveform)) / SAMPLE_RATE)
waveform *= 44 / np.sqrt(np.mean(abs(waveform)**2))
values = np.empty(2 * len(waveform), dtype=np.int8)
values[::2] = np.clip(np.rint(waveform.real), -128, 127).astype(np.int8)
values[1::2] = np.clip(np.rint(waveform.imag), -128, 127).astype(np.int8)
return values
def prepare(directory: Path = RAW_DIRECTORY) -> dict:
"""Создать единственный конечный TX-файл; аппаратная частота не назначена."""
directory.mkdir(parents=True, exist_ok=True)
tx_path, metadata_path = directory / "tx_abab.cs8", directory / "tx_metadata.json"
if tx_path.exists() or metadata_path.exists():
raise FileExistsError("Артефакты Lab056 уже существуют")
bursts = {rate: build_burst(rate) for rate in (20000, 500000)}
segments = []
cursor = GAP_SAMPLES
with tx_path.open("xb") as stream:
stream.write(bytes(2 * GAP_SAMPLES))
for label, rate in (("A1", 20000), ("B1", 500000), ("A2", 20000), ("B2", 500000)):
values = bursts[rate]
count = len(values) // 2
complex_values = values[::2].astype(np.float32) + 1j * values[1::2].astype(np.float32)
whole, active = energy.Moments(), energy.Moments()
whole.add(complex_values)
trim = 21 * (SAMPLE_RATE // rate)
active.add(complex_values[trim:-trim])
segments.append({"label": label, "symbol_rate": rate, "start": cursor,
"samples": count, "active_start": cursor + trim, "active_samples": count - 2 * trim,
"whole": whole.result(rate), "active": active.result(rate)})
stream.write(values.tobytes())
stream.write(bytes(2 * GAP_SAMPLES))
cursor += count + GAP_SAMPLES
result = {"stage": "PREPARATION_ONLY", "rf_tx": 0, "rf_frequency_hz": None,
"physical_path": "OTA_REQUESTED_FREQUENCY_AND_GEOMETRY_PENDING", "sample_rate_hz": SAMPLE_RATE,
"carrier_hz": CARRIER, "tx_samples": cursor, "tx_bytes": 2 * cursor,
"tx_seconds": cursor / SAMPLE_RATE, "rx_samples": RX_SAMPLES,
"rx_seconds": RX_SAMPLES / SAMPLE_RATE, "rx_lead_seconds": RX_LEAD_SECONDS,
"gap_seconds": GAP_SAMPLES / SAMPLE_RATE, "gap_is_zero_digital_iq_not_rf_off": True,
"tx_sha256": energy.sha256(tx_path), "segments": segments,
"whole_power_difference_db": float(10 * np.log10(segments[1]["whole"]["power_code2"] / segments[0]["whole"]["power_code2"])),
"active_power_difference_db": float(10 * np.log10(segments[1]["active"]["power_code2"] / segments[0]["active"]["power_code2"]))}
if abs(result["whole_power_difference_db"]) > .01 or any(s["whole"]["clipped_samples_fraction"] for s in segments):
raise RuntimeError("Цифровая подготовка не прошла проверку мощности/клиппинга")
save_new(metadata_path, result)
return result
def locate_sequence(tx: energy.Cs8File, rx: energy.Cs8File) -> dict:
"""Диагностическое согласование полной ABAB; TX reference разрешён.
Грубая сетка 32 отсчёта использует всю последовательность, затем начало
каждого burst уточняется прежним методом Lab055 на обучающей плитке.
Прореживание без антиалиасинга здесь только для корреляционного поиска,
а не для измерений мощности или спектра.
"""
stride = 32
def decimated(source):
chunks = [source.read(start, min(262144, source.size - start))[::stride]
for start in range(0, source.size, 262144)]
return np.concatenate(chunks).astype(np.complex128)
reference, received = decimated(tx), decimated(rx)
if len(received) < len(reference):
raise ValueError("RX короче TX")
reference -= reference.mean()
received -= received.mean()
cumulative = np.concatenate(([0.], np.cumsum(abs(received)**2)))
window_energy = cumulative[len(reference):] - cumulative[:-len(reference)]
reference_energy = np.sum(abs(reference)**2)
time = np.arange(len(reference)) * stride / SAMPLE_RATE
best = (-1., 0, 0.)
for frequency in np.arange(-2., 2.01, .25):
rotated = reference * np.exp(2j * np.pi * frequency * time)
correlation = signal.fftconvolve(received, np.conj(rotated[::-1]), mode="valid")
scores = abs(correlation) / np.sqrt(np.maximum(reference_energy * window_energy, 1e-30))
index = int(np.argmax(scores))
if scores[index] > best[0]:
best = (float(scores[index]), index * stride, float(frequency))
# Это качество диагностического совпадения, не критерий PHY/CRC.
if best[0] < .05:
raise ValueError("ABAB не локализована достаточно уверенно; измерение UNKNOWN")
return {"start": best[1], "correlation": best[0], "coarse_cfo_hz": best[2], "stride": stride}
class OffsetSource:
def __init__(self, source: energy.Cs8File, offset: int):
self.source, self.offset = source, offset
def read(self, start: int, count: int) -> np.ndarray:
return self.source.read(self.offset + start, count)
def window_power(values: np.ndarray, symbol_rate: int) -> dict:
moments = energy.Moments()
moments.add(values)
result = moments.result(symbol_rate)
centered = values - values.mean()
frequencies, density = energy.spectrum(centered)
for width in (27000, 675000):
result[f"band_{width}_power"] = energy.band_power(frequencies, density, CARRIER - width / 2, CARRIER + width / 2)
size = 32000 # Неперекрывающиеся блоки по 4 мс для оценки разброса мощности.
powers = [float(np.mean(abs(centered[start:start + size])**2)) for start in range(0, len(centered) - size + 1, size)]
result["power_standard_error"] = float(np.std(powers, ddof=1) / np.sqrt(len(powers))) if len(powers) > 1 else None
return result
def difference_db(new: float | None, old: float | None) -> float | None:
if new is None or old is None or new <= 0 or old <= 0:
return None
return float(10 * np.log10(new / old))
def complex_gain(reference: np.ndarray, received: np.ndarray, cfo_hz: float) -> dict:
"""Вывести комплексный h прежней модели; те же чётные обучающие плитки."""
boundaries = np.linspace(0, len(reference), 9, dtype=int)
train = np.zeros(len(reference), dtype=bool)
for tile in range(0, 8, 2):
train[boundaries[tile]:boundaries[tile + 1]] = True
rotated = reference * np.exp(2j * np.pi * cfo_hz * np.arange(len(reference)) / SAMPLE_RATE)
x, y = rotated[train], received[train].astype(np.complex128)
x, y = x - x.mean(), y - y.mean()
gain = np.vdot(x, y) / max(float(np.sum(abs(x)**2)), 1e-30)
return {"real": float(gain.real), "imag": float(gain.imag),
"phase_rad_at_active_start": float(np.angle(gain))}
def comparison_summary(rows: list[dict]) -> dict:
"""Сравнить повторы отдельно; X/Z используют средние линейные мощности."""
by_label = {row["label"]: row for row in rows}
repeatability = []
for mode in ("A", "B"):
first, second = by_label[mode + "1"], by_label[mode + "2"]
repeatability.append({"comparison": mode + "2/" + mode + "1",
"raw_rx_power_difference_db": difference_db(second["raw_rx"]["power_code2"], first["raw_rx"]["power_code2"]),
"background_difference_db": difference_db(second["background_power"], first["background_power"]),
"received_power_difference_db": difference_db(second["background_subtracted_signal_power"], first["background_subtracted_signal_power"]),
"channel_gain_difference_db": difference_db(second["channel_gain_power"], first["channel_gain_power"]),
"matched_component_difference_db": difference_db(second["measurement"]["heldout_signal_power"], first["measurement"]["heldout_signal_power"])})
def mean(mode, key):
values = [by_label[mode + str(number)][key] for number in (1, 2)]
return None if any(value is None or value <= 0 for value in values) else float(np.mean(values))
a_power, b_power = mean("A", "background_subtracted_signal_power"), mean("B", "background_subtracted_signal_power")
return {"repeatability": repeatability,
"averaging": "RATIO_OF_MEAN_LINEAR_POWERS_B1_B2_OVER_A1_A2",
"received_power_difference_db": difference_db(b_power, a_power),
"channel_gain_difference_db": difference_db(mean("B", "channel_gain_power"), mean("A", "channel_gain_power")),
"energy_per_symbol_difference_db": difference_db(None if b_power is None else b_power / 500000,
None if a_power is None else a_power / 20000),
"energy_basis": "BACKGROUND_SUBTRACTED_RECEIVED_POWER_DIVIDED_BY_SYMBOL_RATE"}
def analyze(directory: Path, rx_path: Path) -> dict:
metadata = energy.read_json(directory / "tx_metadata.json")
tx_path = directory / "tx_abab.cs8"
if energy.sha256(tx_path) != metadata["tx_sha256"]:
raise ValueError("TX SHA не совпал")
tx, rx = energy.Cs8File(tx_path), energy.Cs8File(rx_path)
alignment = locate_sequence(tx, rx)
rows = []
for segment in metadata["segments"]:
rate, count = segment["symbol_rate"], segment["samples"]
start = alignment["start"] + segment["start"]
measurement = energy.analyze_rx(OffsetSource(tx, segment["start"]), rx, [start], count,
SAMPLE_RATE // rate)["frames"][0]
start = measurement["start"]
trim = 21 * (SAMPLE_RATE // rate)
received_active = rx.read(start + trim, count - 2 * trim)
raw = window_power(received_active, rate)
gain = complex_gain(tx.read(segment["start"] + trim, count - 2 * trim), received_active, measurement["cfo_hz"])
if not np.isclose(gain["real"]**2 + gain["imag"]**2, measurement["gain_abs"]**2, rtol=1e-10, atol=1e-15):
raise ValueError("Вывод комплексного h расходится с прежней моделью Lab055")
windows = [window_power(rx.read(begin, 480000), rate) for begin in
(start - 640000, start + count + 160000)]
background = float(np.mean([w["ac_power_code2"] for w in windows]))
excess = raw["ac_power_code2"] - background
uncertainty = float(np.sqrt(raw["power_standard_error"]**2 +
sum(w["power_standard_error"]**2 for w in windows) / 4 +
((windows[0]["ac_power_code2"] - windows[1]["ac_power_code2"]) / 2)**2))
stable = excess > 3 * uncertainty
rows.append({"label": segment["label"], "symbol_rate": rate, "raw_rx": raw,
"local_zero_iq_background_windows": windows, "background_power": background,
"background_subtracted_raw": excess, "background_subtracted_uncertainty": uncertainty,
"background_subtracted_stable": stable, "background_subtracted_signal_power": excess if stable else None,
"channel_gain_complex": gain, "channel_gain_power": measurement["gain_abs"]**2, "measurement": measurement})
by_label = {r["label"]: r for r in rows}
pairs = []
for number in (1, 2):
a, b = by_label[f"A{number}"], by_label[f"B{number}"]
pairs.append({"pair": number,
"received_power_difference_db": difference_db(b["background_subtracted_signal_power"], a["background_subtracted_signal_power"]),
"channel_gain_difference_db": difference_db(b["channel_gain_power"], a["channel_gain_power"]),
"matched_component_difference_db": difference_db(b["measurement"]["heldout_signal_power"], a["measurement"]["heldout_signal_power"]),
"matched_component_symbol_energy_difference_db": difference_db(b["measurement"]["heldout_signal_power"] / b["symbol_rate"], a["measurement"]["heldout_signal_power"] / a["symbol_rate"])})
# До начала и после конца всего цифрового файла: отдельные внешние окна.
outside = []
for begin in (alignment["start"] - 800000, alignment["start"] + tx.size + 400000):
if 0 <= begin and begin + 400000 <= rx.size:
outside.append({"start": begin, **window_power(rx.read(begin, 400000), 20000)})
return {"scope": "TX_REFERENCE_DIAGNOSTICS_NOT_INDEPENDENT_PACKET_RX", "rf_tx": 0,
"tx_sha256": metadata["tx_sha256"], "rx_sha256": energy.sha256(rx_path),
"alignment": alignment, "segments": rows, "pairs": pairs, "comparison_summary": comparison_summary(rows),
"outside_waveform_background": outside,
"background_note": "Внутренние gaps: нулевой цифровой IQ при работающем TX; это не RF OFF. Внешние окна также требуют сверки времён запуска/остановки по журналам.",
"power_units": "RX code squared, not calibrated watts/dBm",
"residual_note": "Шум и неописанные моделью искажения; RX не нормируется для выравнивания A/B"}
def software_capture(directory: Path, b_gain: float, name: str) -> dict:
"""Синтетический CS8 с известным усилением, DC и CFO; не аппаратная запись."""
metadata = energy.read_json(directory / "tx_metadata.json")
tx = energy.Cs8File(directory / "tx_abab.cs8")
path = directory / f"synthetic_{name}.cs8"
random_generator = np.random.default_rng(56056)
lead = 4_160_013
with path.open("xb") as stream:
for low in range(0, RX_SAMPLES, 262144):
count = min(262144, RX_SAMPLES - low)
values = (1 + 2j + .6 * (random_generator.normal(size=count) + 1j * random_generator.normal(size=count)))
first, last = max(low, lead), min(low + count, lead + tx.size)
if first < last:
source = tx.read(first - lead, last - first)
gains = np.full(len(source), .03)
indexes = np.arange(first - lead, last - lead)
for segment in metadata["segments"]:
if segment["label"].startswith("B"):
gains[(indexes >= segment["start"]) & (indexes < segment["start"] + segment["samples"])] = b_gain
values[first - low:last - low] += source * gains * np.exp(1j * (.7 + 2 * np.pi * .2 * np.arange(first, last) / SAMPLE_RATE))
packed = np.empty(2 * count, dtype=np.int8)
packed[::2] = np.clip(np.rint(values.real), -128, 127)
packed[1::2] = np.clip(np.rint(values.imag), -128, 127)
stream.write(packed.tobytes())
result = analyze(directory, path)
expected = 20 * np.log10(b_gain / .03)
errors = [abs(p["channel_gain_difference_db"] - expected) for p in result["pairs"]]
result.update(synthetic=True, expected_gain_difference_db=float(expected),
known_sequence_start=lead, passed=bool(max(errors) < .3))
if not result["passed"]:
raise RuntimeError(f"Синтетическая проверка не прошла: {errors}")
return result
def run_functional_tests() -> None:
assert difference_db(.25, 1) == 10 * np.log10(.25)
assert difference_db(None, 1) is None
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("stage", choices=("prepare", "software", "analyze"))
parser.add_argument("--rx", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
if args.stage == "prepare":
result = prepare()
save_new(OUTPUT / "preparation.json", result)
elif args.stage == "software":
for name, gain in (("equal", .03), ("minus6db", .015)):
result = software_capture(RAW_DIRECTORY, gain, name)
save_new(OUTPUT / f"software_{name}.json", result)
print(name, result["pairs"], flush=True)
return
else:
if args.rx is None or args.output is None:
parser.error("Для сохранённого RX нужны --rx и новый --output")
result = analyze(RAW_DIRECTORY, args.rx)
save_new(args.output, result)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,263 @@
"""Lab056: конечный RX-first runner; этот модуль не имеет аппаратного CLI."""
from __future__ import annotations
from dataclasses import asdict, dataclass
import json
from pathlib import Path
import re
import time
from experiments import lab054_hardware_control as previous
from experiments import lab056_abab_energy as waveform
@dataclass(frozen=True)
class OperatingPoint:
"""Частота пользователя и описание неподвижного эфирного тракта."""
frequency_hz: int | None = None
physical_path: str = "MASTER TX -> TX antenna -> free space -> RX antenna -> SLAVE RX"
path_confirmed: bool = False
tx_antenna: str = "UNKNOWN"
rx_antenna: str = "UNKNOWN"
distance_m: float | None = None
orientation: str = "UNKNOWN"
visibility: str = "UNKNOWN"
def validate(self) -> None:
# Допустимую лабораторную частоту определяет пользователь, не этот модуль.
if type(self.frequency_hz) is not int or self.frequency_hz <= 0:
raise PermissionError("Нужно конкретное целое RF_FREQ в Гц от пользователя")
def command(point: OperatingPoint, path: Path | str, samples: int, *, transmit: bool = False) -> list[str]:
point.validate()
if not isinstance(samples, int) or samples <= 0:
raise ValueError("Нужен положительный конечный счётчик отсчётов")
clock = previous.clock
result = [str(clock.HACKRF_TOOLS_DIR / "hackrf_transfer.exe"), "-d",
clock.MASTER_SERIAL if transmit else clock.SLAVE_SERIAL, "-t" if transmit else "-r",
str(path), "-f", str(point.frequency_hz), "-s", str(waveform.SAMPLE_RATE), "-n", str(samples)]
return result + (["-x", "10"] if transmit else ["-l", "24", "-g", "24"]) + ["-a", "0", "-p", "0", "-B"]
def common_clock(point: OperatingPoint, runner, guard, *, sleep_function=time.sleep) -> dict:
"""Прежняя логика регистров/CLKIN; короткий RX использует новую согласованную RF."""
point.validate()
def short_receive(serial, command_runner):
parameters = command(point, "NUL", previous.clock.SHORT_RX_SAMPLE_COUNT)
parameters[parameters.index("-d") + 1] = serial
result = command_runner(parameters)
if result.returncode:
raise RuntimeError("Короткий RX-start не выполнен")
preflight = previous.bind(previous.clock.common_clock_preflight, _short_rx=short_receive,
_disable_clock_outputs=previous.disable_clock_outputs)
return preflight(runner, guard, sleep_function=sleep_function)
def validated_metadata(directory: Path) -> dict:
metadata = waveform.energy.read_json(directory / "tx_metadata.json")
path = directory / "tx_abab.cs8"
if (metadata["stage"] != "PREPARATION_ONLY" or metadata["rf_tx"] != 0 or
metadata["sample_rate_hz"] != waveform.SAMPLE_RATE or metadata["carrier_hz"] != waveform.CARRIER or
metadata["tx_samples"] * 2 != path.stat().st_size or
waveform.energy.sha256(path) != metadata["tx_sha256"] or
metadata["rx_samples"] != waveform.RX_SAMPLES or
[(s["label"], s["symbol_rate"]) for s in metadata["segments"]] !=
[("A1", 20000), ("B1", 500000), ("A2", 20000), ("B2", 500000)] or
abs(metadata["whole_power_difference_db"]) > .01):
raise ValueError("Подготовленный TX/параметры изменились")
return metadata
def clean_counter_log(log: str, expected: str) -> bool:
"""Все упоминания потерь должны быть разобраны и иметь явный нулевой счётчик."""
pattern = re.compile(r"(?<![\w.+-])(\d+)\s+(?:(?:rx|tx)\s+)?(overruns?|underruns?)\b", re.I)
matches = list(pattern.finditer(log))
if not matches or any(int(match[1]) != 0 for match in matches):
return False
if not any(match[2].lower().rstrip("s") == expected for match in matches):
return False
return re.search(r"overrun|underrun", pattern.sub("", log), re.I) is None
def require_clock_pass(result: dict) -> None:
if (not result.get("passed") or result.get("master_reg29") != 0x26 or
result.get("slave_reg29") != 0x26 or result.get("slave_clkin_status") != "DETECTED"):
raise RuntimeError("Common clock FAIL")
def receive_level(path: Path, *, tail_samples: int | None = None) -> dict:
"""Уровень CS8 без изменения gain; rail-коды являются признаком клиппинга."""
source = waveform.energy.Cs8File(path)
if source.size < 65536:
raise RuntimeError("Недостаточно сохранённых RX-отсчётов для проверки уровня")
start = max(0, source.size - tail_samples) if tail_samples else 0
moments = waveform.energy.Moments()
for begin in range(start, source.size, 262144):
moments.add(source.read(begin, min(262144, source.size - begin)))
result = moments.result(20000)
result["saturation_detected"] = result["clipped_samples_fraction"] > 0
result["analog_compression"] = "UNKNOWN_WITHOUT_CALIBRATION"
return result
def preflight_only(directory: Path, point: OperatingPoint, *, runner=None,
process_checker=None, clock_checker=None, sleep_function=time.sleep) -> dict:
"""Только RX: опора и 0,1 с фона; сохранить результат и STOP до разрешения TX."""
point.validate()
metadata = validated_metadata(directory)
waveform.save_new(directory / "preflight_attempt.json", {"point": asdict(point), "rf_tx_count": 0})
actual_runner = runner or previous.clock._default_command_runner
process_checker = process_checker or previous.check_processes
clock_checker = clock_checker or common_clock
def receive_only_runner(parameters):
if "-t" in parameters:
raise PermissionError("TX запрещён внутри preflight_only")
return actual_runner(parameters)
record = {"passed": False, "rf_tx_count": 0, "point": asdict(point),
"tx_sha256": metadata["tx_sha256"], "stop_before_tx": True}
clock_started = False
try:
before = process_checker()
record["process_before"] = before.to_dict()
previous.require_clear(before)
record["inventory"] = previous.phy.lab045._verify_device_inventory(receive_only_runner)
clock_started = True
record["clock"] = clock_checker(point, receive_only_runner, previous.clock.TransmitGuard(),
sleep_function=sleep_function)
require_clock_pass(record["clock"])
path = directory / "preflight_rx.cs8"
if path.exists():
raise FileExistsError("RX preflight уже существует")
captured = receive_only_runner(command(point, path, 800000))
record["rx_process"] = {"returncode": captured.returncode,
"stdout": captured.stdout, "stderr": captured.stderr}
if captured.returncode or not clean_counter_log(captured.stdout + captured.stderr, "overrun"):
raise RuntimeError("RX preflight: ошибка процесса или счётчиков")
if path.stat().st_size != 1600000:
raise RuntimeError("Неверный размер RX preflight")
record["rx_level"] = receive_level(path)
record["rx_sha256"] = waveform.energy.sha256(path)
if record["rx_level"]["saturation_detected"]:
raise RuntimeError("RX saturation до TX; STOP без изменения gains")
record["passed"] = True
except Exception as error:
record["error"] = str(error)
raise
finally:
errors = previous.disable_clock_outputs(receive_only_runner) if clock_started else []
try:
after = process_checker()
record["process_after"] = after.to_dict()
previous.require_clear(after)
except Exception as error:
errors.append(str(error))
record["cleanup_errors"] = errors
record["passed"] = record["passed"] and not errors
waveform.save_new(directory / "preflight_result.json", record)
if not record["passed"]:
raise RuntimeError("Preflight cleanup FAIL")
return record
def require_preflight(directory: Path, point: OperatingPoint, metadata: dict) -> None:
record = waveform.energy.read_json(directory / "preflight_result.json")
if (record.get("passed") is not True or record.get("rf_tx_count") != 0 or
record.get("point") != asdict(point) or record.get("tx_sha256") != metadata["tx_sha256"]):
raise PermissionError("Нужен успешный RX-only preflight именно этой конфигурации")
require_clock_pass(record.get("clock", {}))
if record.get("rx_level", {}).get("saturation_detected") is not False:
raise PermissionError("Нет подтверждённой проверки уровня RX до TX")
def capture_once(directory: Path, point: OperatingPoint, *, authorization: bool = False,
runner=None, process_starter=None, process_checker=None,
clock_checker=None, sleep_function=time.sleep) -> dict:
"""Один RX и один TX; ошибка расходует попытку, автоматического повтора нет."""
if authorization is not True:
raise PermissionError("Нужно отдельное разрешение на конкретный RF-сеанс Lab056")
point.validate()
metadata = validated_metadata(directory)
require_preflight(directory, point, metadata)
receive_path = directory / "rx_stream.cs8"
if receive_path.exists() or (directory / "capture_result.json").exists():
raise FileExistsError("Аппаратные результаты уже существуют")
runner = runner or previous.clock._default_command_runner
process_starter = process_starter or previous.clock._default_process_starter
process_checker = process_checker or previous.check_processes
clock_checker = clock_checker or common_clock
waveform.save_new(directory / "hardware_attempt.json", {"authorized_once": True,
"point": asdict(point), "tx_sha256": metadata["tx_sha256"], "automatic_retry": False})
result = {"passed": False, "rf_tx_count": 0, "point": asdict(point)}
guard = previous.clock.TransmitGuard()
process, clock_started = None, False
try:
before = process_checker()
result["process_before"] = before.to_dict()
previous.require_clear(before)
result["inventory"] = previous.phy.lab045._verify_device_inventory(runner)
clock_started = True
result["clock"] = clock_checker(point, runner, guard, sleep_function=sleep_function)
require_clock_pass(result["clock"])
process = process_starter(command(point, receive_path, waveform.RX_SAMPLES),
directory / "rx_stdout.txt", directory / "rx_stderr.txt")
sleep_function(waveform.RX_LEAD_SECONDS)
if process.poll() is not None:
raise RuntimeError("RX завершился до TX")
result["rx_level_before_tx"] = receive_level(receive_path, tail_samples=65536)
if result["rx_level_before_tx"]["saturation_detected"]:
raise RuntimeError("RX saturation до TX; STOP без изменения gains")
guard.register_transmit()
result["rf_tx_count"] = guard.transmit_calls
transmitted = runner(command(point, directory / "tx_abab.cs8", metadata["tx_samples"], transmit=True))
waveform.save_new(directory / "tx_process_result.json", {"returncode": transmitted.returncode,
"stdout": transmitted.stdout, "stderr": transmitted.stderr})
if transmitted.returncode or process.wait(13):
raise RuntimeError("Единственный TX/RX завершился ошибкой")
process.close_logs()
if receive_path.stat().st_size != waveform.RX_SAMPLES * 2:
raise RuntimeError("Неверный размер RX")
rx_log = "".join((directory / f"rx_{kind}.txt").read_text(encoding="utf-8") for kind in ("stdout", "stderr"))
for role, log, counter in (("RX", rx_log, "overrun"),
("TX", transmitted.stdout + transmitted.stderr, "underrun")):
if not clean_counter_log(log, counter):
raise RuntimeError(f"{role}: нет явного нулевого счётчика потерь")
result.update(rx_sha256=waveform.energy.sha256(receive_path), rx_level=receive_level(receive_path))
if result["rx_level"]["saturation_detected"]:
raise RuntimeError("RX saturation в сохранённом сеансе; TX уже был, повтор запрещён")
result["passed"] = True
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(previous.disable_clock_outputs(runner))
try:
after = process_checker()
result["process_after"] = after.to_dict()
previous.require_clear(after)
except Exception as error:
errors.append(str(error))
result["cleanup_errors"] = errors
result["passed"] = result["passed"] and not errors
waveform.save_new(directory / "capture_result.json", result)
if not result["passed"]:
raise RuntimeError("Cleanup FAIL; повтор запрещён")
return result
def run_functional_tests() -> None:
try:
capture_once(Path("UNUSED"), OperatingPoint())
except PermissionError:
pass
else:
raise AssertionError("Разрешение проверяется раньше любых действий")

View File

@@ -0,0 +1,159 @@
"""Достоверность перечисления процессов и запрет аппаратных действий при FAIL."""
from __future__ import annotations
import json
import subprocess
from types import SimpleNamespace
import pytest
from experiments import lab054_hardware_control as lab
from tools import hackrf_process_check as check
def snapshot(names=("python.exe",), errors=()):
return check.classify_snapshot("fixture", [check.ProcessIdentity(index + 1, name)
for index, name in enumerate(names)], errors)
def completed(stdout="", stderr="", code=0):
return lambda command: subprocess.CompletedProcess(command, code, stdout, stderr)
def test_clear_full_snapshot():
result = check.check_processes([lambda: snapshot()])
assert result.state is check.ProcessState.CLEAR
check.require_clear(result)
@pytest.mark.parametrize("name", ["hackrf_transfer.exe", "hackrf_clock.exe", "HACKRF_DEBUG.EXE",
"hackrf_info.exe", "sdrangel.exe", "SDRSharp", "gqrx"])
def test_busy_known_sdr_clients(name):
result = check.check_processes([lambda: snapshot((name,), errors=("partial snapshot",)),
lambda: pytest.fail("BUSY запрещает fallback")])
assert result.state is check.ProcessState.BUSY
with pytest.raises(RuntimeError):
check.require_clear(result)
@pytest.mark.parametrize("names,errors", [((), ()), (("python",), ("Access denied",)), (("",), ())])
def test_unknown_never_becomes_clear(names, errors):
result = check.check_processes([lambda: snapshot(names, errors)])
assert result.state is check.ProcessState.UNKNOWN
with pytest.raises(RuntimeError):
check.require_clear(result)
def test_tasklist_access_denied_is_unknown():
result = check.scan_tasklist(completed(stderr="ERROR: Access denied", code=1))
assert result.state is check.ProcessState.UNKNOWN
assert "Access denied" in str(result.errors)
def test_fallback_to_powershell_after_psutil_unavailable(monkeypatch):
powershell = check.scan_powershell
monkeypatch.setattr(check, "scan_psutil", lambda: snapshot((), ("ImportError: psutil",)))
monkeypatch.setattr(check, "scan_powershell", lambda: powershell(
completed('[{"Id":12,"ProcessName":"python"}]')))
monkeypatch.setattr(check, "scan_tasklist", lambda: pytest.fail("Полный CLEAR уже получен"))
result = check.check_processes()
assert result.state is check.ProcessState.CLEAR
assert [item.state for item in result.attempts] == [check.ProcessState.UNKNOWN, check.ProcessState.CLEAR]
def test_tasklist_is_last_fallback(monkeypatch):
tasklist = check.scan_tasklist
monkeypatch.setattr(check, "scan_psutil", lambda: snapshot((), ("unavailable",)))
monkeypatch.setattr(check, "scan_powershell", lambda: snapshot((), ("denied",)))
monkeypatch.setattr(check, "scan_tasklist", lambda: tasklist(
completed('"python.exe","123","Console","1","123 K"\n')))
result = check.check_processes()
assert result.state is check.ProcessState.CLEAR
assert len(result.attempts) == 3
@pytest.mark.parametrize("output", ["", "[]", "null", "not json", '[{"Id":4,"ProcessName":null}]'])
def test_powershell_malformed_not_clear(output):
assert check.scan_powershell(completed(output)).state is check.ProcessState.UNKNOWN
@pytest.mark.parametrize("output", ["", "INFO: No tasks are running", '"python.exe","bad pid","a","b","c"'])
def test_tasklist_malformed_not_clear(output):
assert check.scan_tasklist(completed(output)).state is check.ProcessState.UNKNOWN
def test_provider_exception_and_empty_provider_list_unknown():
def failed():
raise PermissionError("denied")
assert check.check_processes([failed]).state is check.ProcessState.UNKNOWN
assert check.check_processes([]).state is check.ProcessState.UNKNOWN
@pytest.mark.parametrize("mechanism", ["powershell", "tasklist"])
@pytest.mark.parametrize("malformed_first", [False, True])
def test_busy_survives_malformed_neighbor(mechanism, malformed_first):
if mechanism == "powershell":
rows = [{"Id":42,"ProcessName":"hackrf_transfer"}, {}]
if malformed_first:
rows.reverse()
scan = check.scan_powershell(completed(json.dumps(rows)))
else:
rows = ['"hackrf_transfer.exe","42","Console","1","100 K"', "BROKEN"]
if malformed_first:
rows.reverse()
scan = check.scan_tasklist(completed("\n".join(rows)))
assert scan.errors
result = check.check_processes([lambda: scan, lambda: pytest.fail("Найденный BUSY нельзя потерять")])
assert result.state is check.ProcessState.BUSY
@pytest.mark.parametrize("disappeared", [False, True])
def test_psutil_access_denied_or_exited_process(monkeypatch, disappeared):
class NoSuchProcess(Exception):
pass
def unreadable():
raise NoSuchProcess() if disappeared else PermissionError("Access denied")
module = SimpleNamespace(NoSuchProcess=NoSuchProcess, process_iter=lambda: iter([
SimpleNamespace(pid=1, name=lambda: "python.exe"),
SimpleNamespace(pid=2, name=unreadable)]))
monkeypatch.setattr(check.importlib, "import_module", lambda name: module)
result = check.scan_psutil()
assert result.state is (check.ProcessState.CLEAR if disappeared else check.ProcessState.UNKNOWN)
@pytest.mark.parametrize("state", list(check.ProcessState))
def test_preflight_only_clear_reaches_hardware(tmp_path, monkeypatch, state):
actions = []
scan = snapshot(("hackrf_transfer.exe",)) if state is check.ProcessState.BUSY else snapshot()
if state is check.ProcessState.UNKNOWN:
scan = snapshot((), ("tasklist Access denied",))
result = check.ProcessCheck(state, (scan,))
monkeypatch.setattr(lab.phy.lab045, "_verify_device_inventory", lambda runner: actions.append("inventory") or {})
monkeypatch.setattr(lab, "clock_preflight", lambda runner, guard: actions.append("clock") or {"passed": True})
monkeypatch.setattr(lab, "disable_clock_outputs", lambda runner: actions.append("cleanup") or [])
if state is check.ProcessState.CLEAR:
assert lab.preflight_only(tmp_path, runner=lambda _: None, process_checker=lambda: result)["passed"]
assert actions == ["inventory", "clock", "cleanup"]
else:
with pytest.raises(RuntimeError):
lab.preflight_only(tmp_path, runner=lambda _: None, process_checker=lambda: result)
assert actions == []
report = json.loads((tmp_path / "common_clock_preflight.json").read_text(encoding="utf-8"))
assert report["process_check"]["state"] == state.value
assert report["rf_tx_count"] == 0
def test_unknown_after_cleanup_rejects_preflight(tmp_path, monkeypatch):
states = iter([check.ProcessCheck(check.ProcessState.CLEAR, (snapshot(),)),
check.ProcessCheck(check.ProcessState.UNKNOWN, (snapshot((), ("denied",)),))])
monkeypatch.setattr(lab.phy.lab045, "_verify_device_inventory", lambda runner: {})
monkeypatch.setattr(lab, "clock_preflight", lambda runner, guard: {"passed": True})
monkeypatch.setattr(lab, "disable_clock_outputs", lambda runner: [])
with pytest.raises(RuntimeError):
lab.preflight_only(tmp_path, runner=lambda _: None, process_checker=lambda: next(states))
report = json.loads((tmp_path / "common_clock_preflight.json").read_text(encoding="utf-8"))
assert not report["passed"]
assert report["process_check_after_cleanup"]["state"] == "UNKNOWN"

View File

@@ -0,0 +1,140 @@
"""Lab054: фиксированные RF-команды и отказы runner без оборудования."""
from __future__ import annotations
import json
from pathlib import Path
import subprocess
import pytest
from experiments import lab054_hardware_control as lab
from tools.hackrf_process_check import ProcessCheck, ProcessState
@pytest.mark.parametrize("failure", [None, "master", "slave", "clkin", "receive"])
def test_clock_gates_never_transmit(failure):
commands = []
def runner(command):
commands.append(command)
output, code = "", 0
if "--si5351c" in command:
serial = command[command.index("-d") + 1]
bad = ((failure == "master" and serial == lab.clock.MASTER_SERIAL)
or (failure == "slave" and serial == lab.clock.SLAVE_SERIAL))
output = "0x0e" if bad else "0x26"
elif "-i" in command:
output = "UNKNOWN" if failure == "clkin" else "CLKIN status: clock signal detected"
elif "-r" in command:
code = int(failure == "receive")
return subprocess.CompletedProcess(command, code, output, "")
guard = lab.clock.TransmitGuard()
if failure:
with pytest.raises(RuntimeError):
lab.clock_preflight(runner, guard, sleep_function=lambda _: None)
assert not guard.tx_allowed
else:
assert lab.clock_preflight(runner, guard, sleep_function=lambda _: None)["passed"]
assert guard.tx_allowed
assert not any("-t" in command for command in commands)
for command in commands:
if Path(command[0]).name == "hackrf_transfer.exe":
assert command[command.index("-f") + 1] == "1400000000"
assert command[command.index("-l") + 1] == command[command.index("-g") + 1] == "24"
def test_authorization_precedes_all_side_effects(tmp_path):
with pytest.raises(PermissionError):
lab.capture_once(tmp_path, runner=lambda _: pytest.fail("hardware"))
assert list(tmp_path.iterdir()) == []
@pytest.mark.parametrize("failure", [None, "clock", "early_rx", "tx", "cleanup"])
def test_one_shot_order_and_no_retry(tmp_path, monkeypatch, failure):
events = []
monkeypatch.setattr(lab, "validate_prepared", lambda directory: {"tx_samples": 123})
monkeypatch.setattr(lab.phy.lab045, "_verify_device_inventory", lambda runner: {})
def preflight(runner, guard):
if failure == "clock":
raise RuntimeError("clock")
guard.allow()
return {"passed": True}
monkeypatch.setattr(lab, "clock_preflight", preflight)
monkeypatch.setattr(lab, "disable_clock_outputs", lambda runner: events.append("clocks_off") or [])
class Process:
def poll(self):
return 1 if failure == "early_rx" else None
def wait(self, timeout):
with (tmp_path / "rx_stream.cs8").open("wb") as stream:
stream.truncate(lab.RX_SAMPLES * 2)
return 0
def close_logs(self):
pass
def terminate(self):
events.append("terminate")
if failure == "cleanup":
raise RuntimeError("cleanup")
def starter(command, stdout, stderr):
events.append("rx")
stdout.write_text("")
stderr.write_text("")
return Process()
def runner(command):
assert "-t" in command and "-R" not in command
events.append("tx")
return subprocess.CompletedProcess(command, int(failure == "tx"), "", "")
kwargs = dict(authorization=True, runner=runner, process_starter=starter,
process_checker=lambda: ProcessCheck(ProcessState.CLEAR, ()),
sleep_function=lambda _: events.append("lead"))
if failure:
with pytest.raises(RuntimeError):
lab.capture_once(tmp_path, **kwargs)
else:
assert lab.capture_once(tmp_path, **kwargs)["passed"]
assert events.count("tx") == (0 if failure in ("clock", "early_rx") else 1)
if "tx" in events:
assert events.index("rx") < events.index("lead") < events.index("tx")
assert "clocks_off" in events
assert json.loads((tmp_path / "capture_result.json").read_text())["passed"] == (failure is None)
before = events.copy()
with pytest.raises(FileExistsError):
lab.capture_once(tmp_path, **kwargs)
assert events == before
def test_clock_cleanup_continues_after_timeout():
commands = []
def runner(command):
commands.append(command)
raise subprocess.TimeoutExpired(command, 1)
errors = lab.disable_clock_outputs(runner)
assert len(commands) == len(errors) == 2
assert [command[2] for command in commands] == [lab.clock.MASTER_SERIAL, lab.clock.SLAVE_SERIAL]
def test_payload_match_does_not_hide_changed_packet():
from types import SimpleNamespace
plan = lab.plans()[0].plan
packet = bytes([plan.packet[0] ^ 1]) + plan.packet[1:]
reception = SimpleNamespace(payload_bytes=plan.payload, packet_bytes=packet)
frame = SimpleNamespace(reception=reception,
post_preamble_soft=lab.phy.lab045.modulate_bits_to_ideal_soft(
__import__("numpy").concatenate((plan.phy_header_bits, plan.interleaved_bits))))
result = lab.compare_after_verdict([(None, SimpleNamespace(accepted=True, frame=frame))])
assert result["payload_matched_packets"] == 1
assert result["exact_packets"] == result["unique_exact_packets"] == 0
assert result["post_fec_errors"] == 1

View File

@@ -0,0 +1,248 @@
"""Lab056: цифровые условия и отказы runner без оборудования."""
from __future__ import annotations
import json
from dataclasses import asdict
from pathlib import Path
import subprocess
import numpy as np
import pytest
from experiments import lab056_abab_energy as lab
from experiments import lab056_capture_runner as capture
from tools.hackrf_process_check import ProcessCheck, ProcessState
def point():
# Только синтетическая конфигурация теста; это не разрешение на эфир.
return capture.OperatingPoint(915000000, physical_path="SIMULATED", path_confirmed=True)
def test_no_authorization_has_no_side_effects(tmp_path):
with pytest.raises(PermissionError):
capture.capture_once(tmp_path, point(), runner=lambda _: pytest.fail("hardware"))
assert list(tmp_path.iterdir()) == []
@pytest.mark.parametrize("configuration", [capture.OperatingPoint(),
capture.OperatingPoint(0), capture.OperatingPoint(True), capture.OperatingPoint(915e6)])
def test_missing_or_invalid_frequency_precedes_io(tmp_path, configuration):
with pytest.raises(PermissionError):
capture.capture_once(tmp_path, configuration, authorization=True)
with pytest.raises(PermissionError):
capture.preflight_only(tmp_path, configuration)
assert list(tmp_path.iterdir()) == []
def test_fixed_gain_and_finite_nonrepeating_commands():
rx = capture.command(point(), "rx.cs8", 24000000)
tx = capture.command(point(), "tx.cs8", 1000000, transmit=True)
assert rx[rx.index("-l") + 1] == rx[rx.index("-g") + 1] == "24"
assert tx[tx.index("-x") + 1] == "10"
for command in (rx, tx):
assert "-R" not in command
assert command[command.index("-a") + 1] == command[command.index("-p") + 1] == "0"
assert command[command.index("-f") + 1] == "915000000"
assert command[command.index("-s") + 1] == "8000000"
@pytest.mark.parametrize("failure", [None, "master", "slave", "clkin", "receive"])
def test_common_clock_logic_uses_agreed_frequency(failure):
commands = []
def runner(command):
commands.append(command)
output, code = "", 0
if "--si5351c" in command:
serial = command[command.index("-d") + 1]
bad = ((failure == "master" and serial == capture.previous.clock.MASTER_SERIAL) or
(failure == "slave" and serial == capture.previous.clock.SLAVE_SERIAL))
output = "0x0e" if bad else "0x26"
elif "-i" in command:
output = "UNKNOWN" if failure == "clkin" else "CLKIN status: clock signal detected"
elif "-r" in command:
code = int(failure == "receive")
assert command[command.index("-f") + 1] == "915000000"
return subprocess.CompletedProcess(command, code, output, "")
guard = capture.previous.clock.TransmitGuard()
if failure:
with pytest.raises(RuntimeError):
capture.common_clock(point(), runner, guard, sleep_function=lambda _: None)
assert not guard.tx_allowed
else:
assert capture.common_clock(point(), runner, guard, sleep_function=lambda _: None)["passed"]
assert not any("-t" in command for command in commands)
@pytest.mark.parametrize("failure", [None, "busy", "clock", "early_rx", "tx", "counters", "cleanup", "rx_role_loss", "tx_role_loss", "clipping_before", "clipping_during"])
def test_one_shot_rx_first_and_failures(tmp_path, monkeypatch, failure):
events = []
monkeypatch.setattr(capture, "validated_metadata", lambda _: {"tx_sha256": "TEST", "tx_samples": 200})
monkeypatch.setattr(capture.previous.phy.lab045, "_verify_device_inventory", lambda _: {"passed": True})
monkeypatch.setattr(lab, "RX_SAMPLES", 65536)
approved = {"passed": True, "rf_tx_count": 0, "point": asdict(point()), "tx_sha256": "TEST",
"clock": {"passed": True, "master_reg29": 38, "slave_reg29": 38, "slave_clkin_status": "DETECTED"},
"rx_level": {"saturation_detected": False}}
lab.save_new(tmp_path / "preflight_result.json", approved)
def clock_checker(point, runner, guard, **kwargs):
events.append("clock")
if failure == "clock":
raise RuntimeError("clock")
guard.allow()
return {"passed": True, "master_reg29": 38, "slave_reg29": 38, "slave_clkin_status": "DETECTED"}
class Process:
def poll(self):
return 1 if failure == "early_rx" else None
def wait(self, timeout):
(tmp_path / "rx_stream.cs8").write_bytes(bytes([127 if failure == "clipping_during" else 1]) * 131072)
return 0
def close_logs(self):
pass
def terminate(self):
events.append("terminate")
if failure == "cleanup":
raise RuntimeError("cleanup")
def starter(command, stdout, stderr):
events.append("rx")
(tmp_path / "rx_stream.cs8").write_bytes(bytes([127 if failure == "clipping_before" else 1]) * 131072)
stdout.write_text("")
stderr.write_text("1 RX overruns\n0 overruns" if failure == "rx_role_loss" else "0 overruns")
return Process()
def runner(command):
if "-t" in command:
events.append("tx")
log = "" if failure == "counters" else ("1 TX underruns\n0 underruns" if failure == "tx_role_loss" else "0 underruns")
return subprocess.CompletedProcess(command, int(failure == "tx"), "", log)
events.append("cleanup")
return subprocess.CompletedProcess(command, 0, "", "")
kwargs = dict(authorization=True, runner=runner, process_starter=starter,
process_checker=lambda: ProcessCheck(ProcessState.BUSY if failure == "busy" else ProcessState.CLEAR, ()),
clock_checker=clock_checker, sleep_function=lambda _: events.append("lead"))
if failure:
with pytest.raises(RuntimeError):
capture.capture_once(tmp_path, point(), **kwargs)
else:
assert capture.capture_once(tmp_path, point(), **kwargs)["passed"]
assert events.count("tx") == (0 if failure in ("busy", "clock", "early_rx", "clipping_before") else 1)
if "tx" in events:
assert events.index("rx") < events.index("lead") < events.index("tx")
before = events.copy()
with pytest.raises(FileExistsError):
capture.capture_once(tmp_path, point(), **kwargs)
assert events == before
result = json.loads((tmp_path / "capture_result.json").read_text())
assert result["passed"] == (failure is None)
assert result["rf_tx_count"] == events.count("tx")
@pytest.mark.parametrize("failure", [None, "clock", "clipping", "tx_attempt", "cleanup"])
def test_rx_only_preflight_stops_before_tx(tmp_path, monkeypatch, failure):
calls = []
monkeypatch.setattr(capture, "validated_metadata", lambda _: {"tx_sha256": "TEST"})
monkeypatch.setattr(capture.previous.phy.lab045, "_verify_device_inventory", lambda _: {"passed": True})
def runner(command):
calls.append(command)
assert "-t" not in command
if "-r" in command:
Path(command[command.index("-r") + 1]).write_bytes(bytes([127 if failure == "clipping" else 1]) * 1600000)
if failure == "cleanup":
raise RuntimeError("cleanup")
return subprocess.CompletedProcess(command, 0, "0 RX overruns", "")
def clock_checker(point, runner, guard, **kwargs):
if failure == "tx_attempt":
runner(["hackrf_transfer", "-t", "forbidden"])
return {"passed": True, "master_reg29": 14 if failure == "clock" else 38,
"slave_reg29": 38, "slave_clkin_status": "DETECTED"}
kwargs = dict(runner=runner, clock_checker=clock_checker,
process_checker=lambda: ProcessCheck(ProcessState.CLEAR, ()))
if failure:
with pytest.raises((RuntimeError, PermissionError)):
capture.preflight_only(tmp_path, point(), **kwargs)
else:
assert capture.preflight_only(tmp_path, point(), **kwargs)["passed"]
result = json.loads((tmp_path / "preflight_result.json").read_text())
assert result["rf_tx_count"] == 0
assert result["passed"] == (failure is None)
assert not (tmp_path / "hardware_attempt.json").exists()
with pytest.raises(FileExistsError):
capture.preflight_only(tmp_path, point(), **kwargs)
def test_preflight_cannot_authorize_another_frequency(tmp_path):
approved = {"passed": True, "rf_tx_count": 0, "point": asdict(point()), "tx_sha256": "TEST",
"clock": {"passed": True, "master_reg29": 38, "slave_reg29": 38, "slave_clkin_status": "DETECTED"},
"rx_level": {"saturation_detected": False}}
lab.save_new(tmp_path / "preflight_result.json", approved)
capture.require_preflight(tmp_path, point(), {"tx_sha256": "TEST"})
with pytest.raises(PermissionError):
capture.require_preflight(tmp_path, capture.OperatingPoint(916000000), {"tx_sha256": "TEST"})
def test_external_frequency_needs_no_country_or_band_fields():
configuration = capture.OperatingPoint(916000000)
command = capture.command(configuration, "NUL", 65536)
assert command[command.index("-f") + 1] == "916000000"
def test_complex_gain_and_repeatability_are_not_rx_normalization():
random_generator = np.random.default_rng(56)
reference = random_generator.normal(size=8000).astype(complex)
expected = .3 + .4j
received = 1 + 2j + expected * reference
gain = lab.complex_gain(reference, received, 0)
assert complex(gain["real"], gain["imag"]) == pytest.approx(expected)
rows = []
for label, power in (("A1", 4), ("B1", 1), ("A2", 8), ("B2", 2)):
rows.append({"label": label, "background_power": 1, "raw_rx": {"power_code2": power + 1},
"background_subtracted_signal_power": power, "channel_gain_power": power,
"measurement": {"heldout_signal_power": power}})
result = lab.comparison_summary(rows)
assert result["received_power_difference_db"] == pytest.approx(-6.020599913)
assert result["energy_per_symbol_difference_db"] == pytest.approx(-20)
assert [r["channel_gain_difference_db"] for r in result["repeatability"]] == pytest.approx([3.010299957, 3.010299957])
rows[-1]["background_subtracted_signal_power"] = None
assert lab.comparison_summary(rows)["received_power_difference_db"] is None
def test_nonpositive_excess_is_not_a_power_ratio():
assert lab.difference_db(-1, 1) is None
assert lab.difference_db(None, 1) is None
assert lab.difference_db(.25, 1) == pytest.approx(-6.020599913)
def test_invalid_result_does_not_leave_partial_json(tmp_path):
path = tmp_path / "result.json"
with pytest.raises(ValueError):
lab.save_new(path, {"invalid": float("nan")})
assert not path.exists()
lab.save_new(path, {"passed": True})
with pytest.raises(FileExistsError):
lab.save_new(path, {"passed": False})
assert json.loads(path.read_text())["passed"] is True
@pytest.mark.parametrize("log,expected,passed", [
("0 overruns", "overrun", True), ("0 RX overrun", "overrun", True),
("0 TX underruns", "underrun", True), ("1 RX overruns\n0 overruns", "overrun", False),
("1 TX underrun\n0 underruns", "underrun", False),
("unknown overruns\n0 overruns", "overrun", False),
("0 overruns\nunderrun detected", "overrun", False),
("0.0 overruns", "overrun", False), ("-1 overruns", "overrun", False),
("0 underruns", "overrun", False), ("", "overrun", False)])
def test_all_loss_mentions_must_be_explicit_zero(log, expected, passed):
assert capture.clean_counter_log(log, expected) is passed
def test_deterministic_ab_rms_and_no_clipping():
powers = []
for rate in (20000, 500000):
raw = lab.build_burst(rate)
values = raw[::2].astype(float) + 1j * raw[1::2].astype(float)
assert not np.any((raw == -128) | (raw == 127))
powers.append(float(np.mean(abs(values)**2)))
np.testing.assert_array_equal(raw, lab.build_burst(rate))
assert abs(lab.difference_db(powers[1], powers[0])) < .01

View File

@@ -0,0 +1,178 @@
"""Проверка конфликтующих SDR-процессов без обращения к оборудованию."""
from __future__ import annotations
import csv
from dataclasses import asdict, dataclass
from enum import Enum
import importlib
import io
import json
from pathlib import PureWindowsPath
import subprocess
from typing import Callable, Iterable
class ProcessState(str, Enum):
CLEAR = "CLEAR"
BUSY = "BUSY"
UNKNOWN = "UNKNOWN"
@dataclass(frozen=True)
class ProcessIdentity:
pid: int
name: str
@dataclass(frozen=True)
class ProcessScan:
state: ProcessState
mechanism: str
matches: tuple[ProcessIdentity, ...] = ()
inspected: int = 0
errors: tuple[str, ...] = ()
@dataclass(frozen=True)
class ProcessCheck:
state: ProcessState
attempts: tuple[ProcessScan, ...]
def to_dict(self) -> dict:
return asdict(self)
# Эти программы способны открыть SDR. Их наличие консервативно даёт BUSY,
# даже если конкретный экземпляр пока не открыл устройство. Это проверка имён,
# а не доказательство отсутствия произвольного переименованного USB-клиента.
SDR_PROCESS_NAMES = frozenset({
"gqrx", "sdrangel", "sdrsharp", "sdrpp", "sdr++", "cubicsdr", "hdsdr",
"sdrconnect", "gnuradio-companion", "osmocom_fft", "osmocom_siggen",
"soapysdrserver", "soapyremote", "soapy_sdr_server", "urh",
})
def is_conflicting(name: str) -> bool:
normalized = PureWindowsPath(name).name.casefold()
if normalized.endswith(".exe"):
normalized = normalized[:-4]
return normalized.startswith("hackrf_") or normalized in SDR_PROCESS_NAMES
def classify_snapshot(mechanism: str, rows: Iterable[ProcessIdentity],
errors: Iterable[str] = ()) -> ProcessScan:
"""Пустое, неполное или неразобранное перечисление не означает CLEAR."""
failures = list(errors)
matches = []
count = 0
try:
for row in rows:
if (not isinstance(row, ProcessIdentity) or type(row.pid) is not int or row.pid < 0
or not isinstance(row.name, str) or not row.name.strip()):
failures.append("Некорректная запись процесса")
continue
count += 1
if is_conflicting(row.name):
matches.append(row)
except Exception as error:
failures.append(f"{type(error).__name__}: {error}")
if count == 0:
failures.append("Не получено ни одного процесса")
state = ProcessState.BUSY if matches else (ProcessState.UNKNOWN if failures else ProcessState.CLEAR)
return ProcessScan(state, mechanism, tuple(matches), count, tuple(failures))
def scan_psutil() -> ProcessScan:
"""NoSuchProcess — обычное завершение; отказ доступа сохраняет UNKNOWN."""
rows, errors = [], []
try:
psutil = importlib.import_module("psutil")
clear_cache = getattr(psutil.process_iter, "cache_clear", None)
if clear_cache is not None:
clear_cache()
for process in psutil.process_iter():
try:
rows.append(ProcessIdentity(process.pid, process.name()))
except psutil.NoSuchProcess:
continue
except Exception as error:
errors.append(f"pid={process.pid}: {type(error).__name__}: {error}")
except Exception as error:
errors.append(f"{type(error).__name__}: {error}")
return classify_snapshot("psutil", rows, errors)
def run_command(command: list[str]) -> subprocess.CompletedProcess:
return subprocess.run(command, capture_output=True, text=True, encoding="utf-8",
errors="strict", timeout=10,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
def scan_powershell(runner: Callable = run_command) -> ProcessScan:
"""Get-Process завершает перечисление целиком или возвращает ошибку."""
script = ("$ErrorActionPreference='Stop'; "
"[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new(); "
"try { $items=@(Get-Process -ErrorAction Stop | "
"Select-Object Id,ProcessName); "
"ConvertTo-Json -InputObject $items -Compress } "
"catch { [Console]::Error.WriteLine($_.Exception.Message); exit 1 }")
rows, errors = [], []
try:
result = runner(["powershell.exe", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script])
if result.returncode != 0 or result.stderr.strip():
raise RuntimeError(f"exit={result.returncode}: {result.stderr.strip()}")
data = json.loads(result.stdout)
if not isinstance(data, list):
raise ValueError("Ожидался полный JSON-массив процессов")
for row in data:
try:
rows.append(ProcessIdentity(row["Id"], row["ProcessName"]))
except Exception as error:
errors.append(f"{type(error).__name__}: {error}")
except Exception as error:
errors.append(f"{type(error).__name__}: {error}")
return classify_snapshot("powershell_get_process", rows, errors)
def scan_tasklist(runner: Callable = run_command) -> ProcessScan:
"""Резервный полный CSV; текст ошибки или пустой вывод не равен CLEAR."""
rows, errors = [], []
try:
result = runner(["tasklist.exe", "/FO", "CSV", "/NH"])
if result.returncode != 0 or result.stderr.strip():
raise RuntimeError(f"exit={result.returncode}: {result.stderr.strip()}")
for row in csv.reader(io.StringIO(result.stdout), strict=True):
try:
if len(row) != 5:
raise ValueError("Некорректная строка tasklist CSV")
rows.append(ProcessIdentity(int(row[1]), row[0]))
except Exception as error:
errors.append(f"{type(error).__name__}: {error}")
except Exception as error:
errors.append(f"{type(error).__name__}: {error}")
return classify_snapshot("tasklist", rows, errors)
def check_processes(providers: Iterable[Callable[[], ProcessScan]] | None = None) -> ProcessCheck:
"""UNKNOWN разрешает другой источник; только полноценный CLEAR открывает gate."""
attempts = []
sources = (scan_psutil, scan_powershell, scan_tasklist) if providers is None else providers
for provider in sources:
try:
scan = provider()
if not isinstance(scan, ProcessScan) or not isinstance(scan.state, ProcessState):
raise TypeError("Некорректный ответ проверки процессов")
except Exception as error:
scan = ProcessScan(ProcessState.UNKNOWN, getattr(provider, "__name__", "provider"),
errors=(f"{type(error).__name__}: {error}",))
attempts.append(scan)
if scan.state in (ProcessState.CLEAR, ProcessState.BUSY):
return ProcessCheck(scan.state, tuple(attempts))
return ProcessCheck(ProcessState.UNKNOWN, tuple(attempts))
def require_clear(result: ProcessCheck) -> None:
if not isinstance(result, ProcessCheck) or result.state is not ProcessState.CLEAR:
detail = result.to_dict() if isinstance(result, ProcessCheck) else repr(result)
raise RuntimeError(f"Проверка процессов не дала CLEAR: {detail}")