Compare commits
19 Commits
e18200225b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0fa7e8a46 | ||
|
|
fa2e473c6d | ||
|
|
fce14f038e | ||
|
|
13f183f7c7 | ||
|
|
a37ad1f7b3 | ||
|
|
46b10b1243 | ||
|
|
0ae04ae4a8 | ||
|
|
92289bdf7c | ||
|
|
2da80d0908 | ||
|
|
c486039053 | ||
|
|
c9569164e0 | ||
|
|
1e4adc0a98 | ||
|
|
2a7a380875 | ||
|
|
49c7009ca5 | ||
|
|
9f7387ec2c | ||
|
|
923e6bec78 | ||
|
|
3d14c75365 | ||
|
|
28227e4228 | ||
|
|
7d122b53ed |
73
.claude/agents/adversary.md
Normal file
73
.claude/agents/adversary.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: adversary
|
||||||
|
description: Ищет состояния, в которых протокол ровера ведёт себя небезопасно — потери связи, перезапуски, гонки, старые и поддельные пакеты. Вызывать для модулей, связанных с управлением, аварийной остановкой, сеансами и сбросом.
|
||||||
|
tools: Read, Grep, Glob, Bash
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
|
||||||
|
Ты ищешь способы довести протокол SDR Rover Link до небезопасного состояния.
|
||||||
|
|
||||||
|
Ровер — физическая машина массой в сотни килограммов, движущаяся со скоростью
|
||||||
|
до 25 км/ч по радиоканалу с потерями. Небезопасное состояние — это не «некрасивый
|
||||||
|
код». Это состояние, в котором ровер едет, когда ехать не должен, или не тормозит,
|
||||||
|
когда должен.
|
||||||
|
|
||||||
|
## Ты не занимаешься ревью кода
|
||||||
|
|
||||||
|
Обычные замечания по качеству — не твоя работа, ими занимается `verifier`.
|
||||||
|
Ты работаешь по брифу на атаку. Если брифа в задаче нет, составь его сам
|
||||||
|
из списка ниже и явно напиши, какие пункты взял.
|
||||||
|
|
||||||
|
## Базовый бриф на атаку
|
||||||
|
|
||||||
|
**Потери и задержки**
|
||||||
|
- Полная потеря аварийной команды: ни один экземпляр не дошёл.
|
||||||
|
- Потеря подтверждения при дошедшей команде.
|
||||||
|
- Пакет, дошедший после того, как состояние уже сменилось.
|
||||||
|
- Задержка, превышающая период управления, но меньше порога failsafe.
|
||||||
|
|
||||||
|
**Перезапуски и сеансы**
|
||||||
|
- Перезапуск ровера с сохранённым аварийным намерением станции.
|
||||||
|
- Перезапуск станции при живом сеансе ровера.
|
||||||
|
- Пакеты предыдущего сеанса с валидной контрольной суммой.
|
||||||
|
- Идентификаторы сеанса, совпавшие случайно после перезапуска.
|
||||||
|
|
||||||
|
**Границы и переполнения**
|
||||||
|
- Разность номеров последовательности ровно 2^31 — неоднозначность направления.
|
||||||
|
- Переполнение счётчика с переходом через ноль.
|
||||||
|
- Нулевая, отрицательная и максимальная скорость.
|
||||||
|
- Порог failsafe, совпавший с периодом управления с точностью до тика.
|
||||||
|
|
||||||
|
**Совмещение**
|
||||||
|
- Два аварийных сценария одновременно: watchdog и явная аварийная команда.
|
||||||
|
- Запрос сброса, пришедший в момент торможения.
|
||||||
|
- Разрешение движения, пришедшее одновременно с потерей связи.
|
||||||
|
|
||||||
|
**Намеренная подделка**
|
||||||
|
- Повтор перехваченного валидного пакета.
|
||||||
|
- Подмена поля при сохранении корректной контрольной суммы.
|
||||||
|
|
||||||
|
## Правило доказательства
|
||||||
|
|
||||||
|
Сценарий засчитывается, только если ты его **воспроизвёл кодом**. Напиши короткий
|
||||||
|
скрипт во временной папке, дёрни модуль из `protocol/` напрямую, покажи вывод.
|
||||||
|
Ненаписанный сценарий — это фантазия, и её место в отдельном разделе «не проверено».
|
||||||
|
|
||||||
|
## Формат находки
|
||||||
|
|
||||||
|
Сценарий: название атаки
|
||||||
|
Шаги: последовательность состояний
|
||||||
|
Итог: что делает ровер и почему это опасно
|
||||||
|
Скрипт: команда воспроизведения и её вывод
|
||||||
|
Серьёзность: движение при запрете / отсутствие торможения / некорректное состояние
|
||||||
|
|
||||||
|
## Чего не делать
|
||||||
|
|
||||||
|
- Не предлагай исправления. Твоя работа — найти, чинит другой.
|
||||||
|
- Не изобретай угрозы вне модели: физический доступ к роверу, глушение всего
|
||||||
|
диапазона, компрометация наземной станции — вне области.
|
||||||
|
- Не растягивай отчёт. Три воспроизведённых сценария лучше пятнадцати описанных.
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
Заверши отчёт строкой: `АТАКА: N воспроизведено, M не проверено`.
|
||||||
58
.claude/agents/verifier.md
Normal file
58
.claude/agents/verifier.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
name: verifier
|
||||||
|
description: Независимая проверка готовой работы по лабе или модулю protocol — запускает код, пересчитывает числа вторым способом, оформляет находки как воспроизводимые сценарии отказа. Вызывать после того, как реализация написана.
|
||||||
|
tools: Read, Grep, Glob, Bash
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
|
||||||
|
Ты проверяешь чужую работу в проекте SDR Rover Link: учебно-исследовательский
|
||||||
|
цифровой радиоканал для наземного ровера. Python, модули протокола в `protocol/`,
|
||||||
|
лабораторные работы в `experiments/labNNN_*.py`, журнал проекта в `PROJECT_LOG.md`.
|
||||||
|
|
||||||
|
Ты не автор кода. Твоя ценность именно в том, что ты не участвовал в его написании.
|
||||||
|
|
||||||
|
## Порядок работы
|
||||||
|
|
||||||
|
1. **Сначала прочитай требование из первоисточника** — из `README.md`, из `PROJECT_LOG.md`,
|
||||||
|
из постановки задачи в шапке файла лабы. Не из пересказа того, кто писал код.
|
||||||
|
Если пересказ расходится с первоисточником — это находка, и часто самая важная.
|
||||||
|
|
||||||
|
2. **Запусти сам.** Быстрая проверка: `python tools/quick_gate.py`.
|
||||||
|
Полный прогон лабы: `python experiments/labNNN_имя.py`. Не верь чужому «тесты проходят».
|
||||||
|
|
||||||
|
3. **Пересчитай ключевые числа независимым способом.** Это главный инструмент.
|
||||||
|
Симуляция BER проверяется аналитической формулой, счётчик отброшенных пакетов —
|
||||||
|
пересчётом из журнала событий, тайминг failsafe — прямым арифметическим расчётом
|
||||||
|
из периода и порога. Если второй способ дал другое число, разбирайся, какой врёт.
|
||||||
|
|
||||||
|
4. **Проверь края.** Ноль, единица, пустой вход, максимум диапазона, переполнение
|
||||||
|
счётчика последовательности, разность номеров ровно 2^31.
|
||||||
|
|
||||||
|
5. **Проверь соответствие стилю проекта.** Русские докстроки и подписи осей,
|
||||||
|
`from __future__ import annotations`, датаклассы, CSV и PNG в `data/processed`,
|
||||||
|
именование как у соседних лаб.
|
||||||
|
|
||||||
|
## Формат находки
|
||||||
|
|
||||||
|
Каждая находка — строго так:
|
||||||
|
|
||||||
|
Файл и строка
|
||||||
|
Вход: что подаём
|
||||||
|
Ожидалось: что должно быть, со ссылкой на требование
|
||||||
|
Получено: что на деле, с выводом запуска
|
||||||
|
Проверено: команда, которой это воспроизводится
|
||||||
|
|
||||||
|
Формулировки «возможно», «стоило бы рассмотреть», «может быть проблемой» запрещены.
|
||||||
|
Если не смог воспроизвести — это не находка, а гипотеза, и помечай её отдельно в конце.
|
||||||
|
|
||||||
|
## Чего не делать
|
||||||
|
|
||||||
|
- Не редактируй файлы. У тебя нет на это инструментов и не должно быть.
|
||||||
|
- Не пиши замечания по стилю кода ради объёма. Одна подтверждённая находка
|
||||||
|
ценнее десяти рассуждений.
|
||||||
|
- Если всё чисто — так и напиши: что именно ты запустил, какие числа сверил,
|
||||||
|
и что расхождений нет. Пустой честный отчёт лучше выдуманного.
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
Заверши отчёт одной строкой: `ВЕРДИКТ: чисто` либо `ВЕРДИКТ: N подтверждённых находок`.
|
||||||
35
.claude/commands/check.md
Normal file
35
.claude/commands/check.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
description: Независимая проверка уже сделанной работы — verifier и, при необходимости, adversary
|
||||||
|
argument-hint: [что проверяем: номер лабы, модуль или «текущие изменения»]
|
||||||
|
---
|
||||||
|
|
||||||
|
Проверяем: $ARGUMENTS
|
||||||
|
|
||||||
|
Если аргумент не задан, проверяем незакоммиченные изменения — возьми их из
|
||||||
|
`git status --porcelain` и `git diff`.
|
||||||
|
|
||||||
|
## Что сделать
|
||||||
|
|
||||||
|
1. Собери фактуру: какие файлы затронуты, какая лаба или модуль за это отвечает,
|
||||||
|
какой командой прогоняется полный тест.
|
||||||
|
|
||||||
|
2. Запусти `python tools/quick_gate.py`. Если шлюз не пройден — дальше не иди,
|
||||||
|
сначала почини, агентов дёргать бессмысленно.
|
||||||
|
|
||||||
|
3. Запусти агента `verifier`. Передай ему:
|
||||||
|
- список затронутых файлов,
|
||||||
|
- команду полного прогона,
|
||||||
|
- указание брать требование из `README.md` и `PROJECT_LOG.md`, а не из твоих слов.
|
||||||
|
|
||||||
|
4. Агента `adversary` запускай **только** если затронуто что-то из списка:
|
||||||
|
управление, failsafe, аварийная остановка, сеансы, перезапуск, сброс,
|
||||||
|
подлинность сообщений. Для видео, сжатия, FEC и модуляции он бесполезен —
|
||||||
|
не трать лимит.
|
||||||
|
|
||||||
|
5. Разбери находки как арбитр: воспроизведи, отклони невоспроизводимые вслух,
|
||||||
|
почини подтверждённые.
|
||||||
|
|
||||||
|
## Отчёт пользователю
|
||||||
|
|
||||||
|
Коротко: что запускалось, что нашли, что починили, что отклонили и почему.
|
||||||
|
Отчёты агентов пользователь не видит — пересказывай сам, не отсылай к ним.
|
||||||
68
.claude/commands/lab.md
Normal file
68
.claude/commands/lab.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
description: Полный цикл работы над лабой — аудит, план, реализация, независимая проверка
|
||||||
|
argument-hint: <номер лабы> [краткая постановка]
|
||||||
|
---
|
||||||
|
|
||||||
|
Работаем над лабой: $ARGUMENTS
|
||||||
|
|
||||||
|
Порядок обязательный. Не перескакивай через этапы и не начинай писать код,
|
||||||
|
пока пользователь не согласовал план.
|
||||||
|
|
||||||
|
## Этап 1. Аудит
|
||||||
|
|
||||||
|
Запусти агента `Explore` с запросом: что в `protocol/` уже относится к этой задаче,
|
||||||
|
какие модули придётся трогать, где есть пересечение или дублирование, как устроены
|
||||||
|
две-три ближайшие по смыслу лабы в `experiments/`.
|
||||||
|
|
||||||
|
Прочитай сам последние записи в `PROJECT_LOG.md` — там указано, чем должен
|
||||||
|
заниматься следующий этап.
|
||||||
|
|
||||||
|
Выдай карту: что есть, чего нет, что придётся изменить. Только карта, без решений.
|
||||||
|
|
||||||
|
## Этап 2. План
|
||||||
|
|
||||||
|
На основе карты предложи план: какие модули создаются, какие меняются, что именно
|
||||||
|
измеряет лаба, какие CSV и PNG появятся в `data/processed`, какие функциональные
|
||||||
|
проверки будут в `run_functional_tests`.
|
||||||
|
|
||||||
|
**Остановись и дождись согласования.** Это прямое требование пользователя.
|
||||||
|
|
||||||
|
## Этап 3. Реализация
|
||||||
|
|
||||||
|
Пишешь ты, в этом же треде. Отдельного агента-кодера не запускай — у тебя уже есть
|
||||||
|
весь контекст, а он начнёт с нуля и потратит лимит впустую.
|
||||||
|
|
||||||
|
Держи стиль проекта: русские докстроки и подписи осей, `from __future__ import annotations`,
|
||||||
|
датаклассы для метрик, CSV и PNG в `data/processed`, `run_functional_tests` и `main`
|
||||||
|
как в соседних лабах.
|
||||||
|
|
||||||
|
После каждой заметной правки: `python tools/quick_gate.py`.
|
||||||
|
|
||||||
|
## Этап 4. Проверка
|
||||||
|
|
||||||
|
Запусти агента `verifier`. В задании передай:
|
||||||
|
- номер и постановку лабы своими словами,
|
||||||
|
- список созданных и изменённых файлов,
|
||||||
|
- команду полного прогона,
|
||||||
|
- прямое указание читать требование из `README.md` и `PROJECT_LOG.md`, а не из твоего пересказа.
|
||||||
|
|
||||||
|
Если лаба касается управления, аварийной остановки, сеансов, сброса или подлинности
|
||||||
|
сообщений — параллельно запусти `adversary` с брифом на атаку под эту тему.
|
||||||
|
|
||||||
|
Оба агента read-only. Правки вносишь ты.
|
||||||
|
|
||||||
|
## Этап 5. Разбор находок
|
||||||
|
|
||||||
|
Ты арбитр. По каждой находке:
|
||||||
|
- воспроизведи её сам, прежде чем чинить;
|
||||||
|
- невоспроизводимую отклони и скажи об этом вслух;
|
||||||
|
- подтверждённую почини и перезапусти проверку.
|
||||||
|
|
||||||
|
Не чини всё подряд из вежливости к агенту.
|
||||||
|
|
||||||
|
## Этап 6. Завершение
|
||||||
|
|
||||||
|
Полный прогон лабы, затем `python tools/quick_gate.py`.
|
||||||
|
Запись в `PROJECT_LOG.md` в том же формате, что предыдущие записи.
|
||||||
|
Коммит в стиле проекта: `LabNNN: краткое описание на английском`.
|
||||||
|
Коммить только после явного согласия пользователя.
|
||||||
59
.claude/hooks/quick_gate_hook.py
Normal file
59
.claude/hooks/quick_gate_hook.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
Обёртка быстрого шлюза для хука PostToolUse.
|
||||||
|
|
||||||
|
Читает JSON события со стандартного ввода. Если правка затронула файл Python
|
||||||
|
в protocol или tests, запускает tools/quick_gate.py.
|
||||||
|
|
||||||
|
Коды возврата:
|
||||||
|
0 — правка не по теме либо шлюз пройден, вывода нет;
|
||||||
|
2 — шлюз не пройден, текст ошибки уходит в stderr и возвращается модели.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
WATCHED = re.compile(r"[\\/](?:protocol|experiments|tests|tools)[\\/][^\\/]+\.py$")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
try:
|
||||||
|
event = json.load(sys.stdin)
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
tool_input = event.get("tool_input") or {}
|
||||||
|
tool_response = event.get("tool_response") or {}
|
||||||
|
file_path = tool_response.get("filePath") or tool_input.get("file_path") or ""
|
||||||
|
|
||||||
|
if not WATCHED.search(str(file_path)):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
environment = dict(os.environ, PYTHONIOENCODING="utf-8")
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[sys.executable, str(PROJECT_ROOT / "tools" / "quick_gate.py")],
|
||||||
|
cwd=PROJECT_ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
env=environment,
|
||||||
|
)
|
||||||
|
|
||||||
|
if completed.returncode == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
output = (completed.stdout + completed.stderr).strip()
|
||||||
|
print(output, file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
18
.claude/settings.json
Normal file
18
.claude/settings.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write|MultiEdit",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python .claude/hooks/quick_gate_hook.py",
|
||||||
|
"timeout": 60,
|
||||||
|
"statusMessage": "Быстрый шлюз"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -29,9 +29,24 @@ __pycache__/
|
|||||||
venv/
|
venv/
|
||||||
env/
|
env/
|
||||||
|
|
||||||
# Необработанные данные и IQ-записи
|
# Необработанные данные и IQ-записи.
|
||||||
|
# Правило закрывает крупные записи отсчётов и видео: они воспроизводятся
|
||||||
|
# заново и в историю не помещаются.
|
||||||
data/raw/
|
data/raw/
|
||||||
|
|
||||||
|
# Исключения: исходные изображения — это входные данные лабораторных, а не
|
||||||
|
# результат. Без них Lab007, Lab009, Lab011 и Lab012 не запускаются, поэтому
|
||||||
|
# они хранятся в репозитории. Суммарно около 1 МБ.
|
||||||
|
!data/raw/lab007_source.jpg
|
||||||
|
!data/raw/lab009_source.jpg
|
||||||
|
!data/raw/lab011_scene.jpg
|
||||||
|
!data/raw/lab012_safety_scene.jpg
|
||||||
|
|
||||||
|
# Метаданные и спектр записи Lab024a: сама запись отсчётов не хранится,
|
||||||
|
# поэтому они оставлены как след проведённого измерения.
|
||||||
|
!data/raw/lab024a_pluto_rx_100mhz.json
|
||||||
|
!data/raw/lab024a_pluto_rx_100mhz_spectrum.png
|
||||||
|
|
||||||
# Временные файлы редакторов
|
# Временные файлы редакторов
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|||||||
60
.gitlab-ci.yml
Normal file
60
.gitlab-ci.yml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Непрерывная проверка SDR Rover Link.
|
||||||
|
#
|
||||||
|
# Здесь выполняются только быстрые проверки: они должны укладываться в
|
||||||
|
# секунды. Лабораторные эксперименты сюда не помещаются — они длятся
|
||||||
|
# минутами, требуют исходных данных из data/raw и в части случаев
|
||||||
|
# подключённого PlutoSDR. Их запуск остаётся ручным.
|
||||||
|
#
|
||||||
|
# Оборудование конвейеру не нужно: устанавливается только расчётное
|
||||||
|
# окружение, без pyadi-iio и libiio.
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- gate
|
||||||
|
- test
|
||||||
|
- lint
|
||||||
|
|
||||||
|
default:
|
||||||
|
image: python:3.13
|
||||||
|
before_script:
|
||||||
|
- python --version
|
||||||
|
- python -m pip install --quiet --upgrade pip
|
||||||
|
- python -m pip install --quiet -r requirements.txt
|
||||||
|
cache:
|
||||||
|
key: "python-$CI_COMMIT_REF_SLUG"
|
||||||
|
paths:
|
||||||
|
- .cache/pip
|
||||||
|
|
||||||
|
variables:
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
||||||
|
PYTHONIOENCODING: "utf-8"
|
||||||
|
|
||||||
|
# Дешёвый шлюз: синтаксис всех файлов, импорт protocol и функциональные
|
||||||
|
# проверки тех лабораторных, которым не нужны данные эксперимента.
|
||||||
|
# Выполняется первым, потому что ловит самые грубые поломки за секунду.
|
||||||
|
быстрый-шлюз:
|
||||||
|
stage: gate
|
||||||
|
script:
|
||||||
|
- python tools/quick_gate.py
|
||||||
|
|
||||||
|
# Быстрые проверки ядра протокола.
|
||||||
|
тесты-ядра:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- python -m pytest tests/ -q --junitxml=report.xml
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
reports:
|
||||||
|
junit: report.xml
|
||||||
|
expire_in: 1 week
|
||||||
|
|
||||||
|
# Проверка стиля. Пока не блокирует конвейер: в лабораторных Lab001–Lab041
|
||||||
|
# принято своеобразное оформление с переносами аргументов, и приводить их
|
||||||
|
# к единому стилю сейчас означало бы тронуть 50 файлов с зафиксированными
|
||||||
|
# результатами. Ограничение снимается, когда договоримся о правилах.
|
||||||
|
стиль:
|
||||||
|
stage: lint
|
||||||
|
allow_failure: true
|
||||||
|
before_script:
|
||||||
|
- python -m pip install --quiet ruff
|
||||||
|
script:
|
||||||
|
- ruff check protocol/ tests/ tools/
|
||||||
206
PROJECT_LOG.md
206
PROJECT_LOG.md
@@ -385,3 +385,209 @@ git version 2.51.1.windows.1
|
|||||||
- Аварийное намерение наземной станции сохраняется после перезапуска.
|
- Аварийное намерение наземной станции сохраняется после перезапуска.
|
||||||
- Неоднозначная разность номеров 2^31 отклоняется.
|
- Неоднозначная разность номеров 2^31 отклоняется.
|
||||||
- Следующий этап посвящён криптографической проверке подлинности управляющих сообщений и защите от намеренной подделки.
|
- Следующий этап посвящён криптографической проверке подлинности управляющих сообщений и защите от намеренной подделки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Запись 017
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
7 августа 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Аудит достоверности Lab041 и удаление показателей, не выводимых из модели. Начало этапа стабилизации после Lab041.
|
||||||
|
|
||||||
|
## Причина записи
|
||||||
|
|
||||||
|
Запись 016 не отменяется, но нуждается в уточнении. Машинный аудит репозитория показал, что часть числовых результатов Lab041 не выводилась ни из модели этой лабораторной, ни из указанного источника. Согласно правилу журнала прежняя запись сохранена, а изменение зафиксировано отдельно.
|
||||||
|
|
||||||
|
## Что обнаружено
|
||||||
|
|
||||||
|
- В функции `simulate_trial` доли публикации видео, доставки телеметрии и доставки управления, а также базовая длина очереди задавались вписанными таблицами, а не вычислялись.
|
||||||
|
- Значение доли видео 0,910 для канала 260 кбит/с не соответствовало заявленному источнику: Lab040 даёт 0,9603, Lab037 — разброс 0,9026...0,9174.
|
||||||
|
- Базовая длина очереди 31 для того же канала не соответствовала Lab040, где получено 36...38 пакетов.
|
||||||
|
- Коэффициенты влияния нагрузки 0,15, 0,10 и 0,05 не выводились ни из какой модели.
|
||||||
|
- Параметры торможения были зашиты литералами `0.250` и делителем `6.0` без связи с профилем `nominal` Lab039, откуда они происходят.
|
||||||
|
- Пять из четырнадцати инвариантов безопасности не измерялись: два вычислялись выражением с умножением на ноль, один был литеральным нулём, ещё два опирались на поля, которые никогда не заполнялись. Проверка `validate_outputs` подтверждала эти константы, то есть проверяла тождество.
|
||||||
|
- Формулировка «все 2807 введённых старых пакетов были отклонены» описывает перебор таблицы сценариев, а не измеренную вероятность отклонения. Число раскладывается как семь сценариев на 401 повтор.
|
||||||
|
|
||||||
|
## Выполнено
|
||||||
|
|
||||||
|
- Из Lab041 удалены четыре показателя: доли доставки видео, телеметрии и управления и максимальная длина очереди. Они относятся к Lab037 и Lab040 и в Lab041 не моделируются.
|
||||||
|
- Удалён график `lab041_traffic_impact.png`, потерявший основание. Осталось семь графиков.
|
||||||
|
- Удалены поля `negative_speed_cases` и `speed_limit_exceeded_cases`, которые никогда не вычислялись, и тождественные проверки в `validate_outputs`.
|
||||||
|
- Параметры торможения связаны с профилем `nominal` Lab039 через именованные константы `LAB039_NOMINAL_STAGE2_MPS2` и `LAB039_STAGE2_THRESHOLD_SECONDS`; горизонт незащищённого движения выделен как явно принятое допущение.
|
||||||
|
- Перенесённые величины `VIDEO_OFFERED_LOAD_KBPS` и `TELEMETRY_OFFERED_LOAD_KBPS` снабжены указанием источника: Lab038, поля `video_load_kbps` и `telemetry_load_kbps`.
|
||||||
|
- Секция инвариантов переписана: девять свойств, следующих из архитектуры, снабжены ссылками на подтверждающие функциональные проверки; пять свойств, которые матрица не может измерить, вынесены в отдельный раздел со ссылками на проверки 04, 06, 07, 10, 16 и 19.
|
||||||
|
- В отчёт добавлено разграничение того, что моделируется розыгрышем, что задано архитектурой и что не моделируется вовсе.
|
||||||
|
- Проверка 04 расширена отрицательной скоростью и границей 15 м/с, поэтому соответствующее свойство теперь подтверждается реально, а не константой.
|
||||||
|
- Оценка пути после перезапуска помечена как расчёт по замкнутой формуле, одинаковый во всех повторах, а не как результат розыгрыша.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
- Lab041 перезапущена полностью: 180 сочетаний, 24 из 24 функциональных проверок пройдены, пять CSV и семь PNG.
|
||||||
|
- Сверка с прежними файлами показала, что удалено ровно шесть столбцов, а все сохранённые значения совпали до последнего знака. Расхождений в общих столбцах ноль.
|
||||||
|
- Научные выводы Lab041 не изменились. Максимальный путь после перезапуска остался 9,7737 м, постоянная нагрузка управления 10,88000 кбит/с, полный предложенный поток 262,076 кбит/с.
|
||||||
|
- Выводы Записи 016 о сеансах, безопасном запуске и отклонении старых пакетов остаются в силе. Уточняется их основание: доказательством служат 24 функциональные проверки, работающие с настоящими автоматами, а не счётчики матрицы из 180 сочетаний.
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
Криптографическая аутентификация отложена. Сначала выполняется этап стабилизации: быстрые тесты `protocol/` вместо единственной точки входа через лабораторную, разделение быстрых проверок и экспериментов, воспроизводимое описание окружения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Запись 018
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
10 августа 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Выбор радиочастотной архитектуры: рабочий диапазон, скачки по частоте и меры против подавления.
|
||||||
|
|
||||||
|
## Исходные данные
|
||||||
|
|
||||||
|
- Устройство опрошено напрямую: `Analog Devices PlutoSDR Rev.C (Z7010-AD9361)`. Приёмник перестраивается от 70 МГц до 6 ГГц, передатчик от 46,9 МГц. Минимальная частота дискретизации 2 083 333 Гц.
|
||||||
|
- Ограничений по разрешённым частотам у проекта нет.
|
||||||
|
- Усилителя мощности нет, покупка отложена до первых испытаний.
|
||||||
|
|
||||||
|
## Отвергнутые варианты и причины
|
||||||
|
|
||||||
|
**Выбор редкой фиксированной частоты.** Рассматривался как защита от подавления. Отклонён: одиночная несущая в пустом эфире обнаруживается сканирующим приёмником тривиально. Уязвимость создаёт не значение частоты, а её неизменность.
|
||||||
|
|
||||||
|
**Скачки по всему диапазону 70 МГц – 6 ГГц.** Теоретически требуют от заградительной помехи на 42 дБ больше мощности, но выигрыш недостижим. Отношение крайних частот 86:1, одна антенна такой полосы не перекрывает; разброс потерь на 10 км составляет 39 дБ, проектировать пришлось бы по худшему краю; выше примерно 1 ГГц дифракция исчезает и для наземного ровера без прямой видимости эти каналы мертвы. Кроме того, подавление направлено на диапазон, в котором связь действительно работает, а не на тот, который способно настроить оборудование.
|
||||||
|
|
||||||
|
**Диапазоны 330–433 МГц.** Отклонены как плотно занятые и общеизвестные.
|
||||||
|
|
||||||
|
## Принято
|
||||||
|
|
||||||
|
Рабочий диапазон **200–250 МГц**. Обоснование: четвертьволновая антенна 33 см пригодна для установки на ровер, дифракция ещё эффективна, участок заметно свободнее любительского 144 МГц и УКВ выше 300 МГц. Потери на 10 км составляют около 99,5 дБ.
|
||||||
|
|
||||||
|
Совокупность мер против подавления:
|
||||||
|
|
||||||
|
1. **Скачки по частоте в пределах 200–250 МГц.** Требуют от заградительной помехи на 22 дБ больше мощности при неизменной антенне и механике ровера.
|
||||||
|
2. **Направленная антенна наземной станции.** Даёт 10–20 дБ усиления и пространственно подавляет помеху, приходящую с иного направления.
|
||||||
|
3. **Расширение спектра для команд управления.** Поток команд составляет 10,88 кбит/с, поэтому расширение для него реализуемо в узкой полосе, в отличие от видеопотока.
|
||||||
|
|
||||||
|
Видео остаётся некритичным классом: при подавлении управление сохраняется в узкой полосе, а ровер переходит к безопасному поведению по уже реализованной логике.
|
||||||
|
|
||||||
|
## Запрещённые участки
|
||||||
|
|
||||||
|
- 108–137 МГц — авиационная связь.
|
||||||
|
- 243 МГц — международная аварийная частота.
|
||||||
|
|
||||||
|
## Ограничения решения
|
||||||
|
|
||||||
|
- Нижняя граница 70 МГц определяется приёмником Pluto и без смены оборудования не преодолевается.
|
||||||
|
- Скачки требуют согласованной последовательности у обеих сторон, то есть зависят от криптографической аутентификации, которая пока не реализована.
|
||||||
|
- Расчёты выполнены для свободного пространства. Потери на препятствиях 20–40 дБ учтены оценочно и подлежат измерению.
|
||||||
|
- Дальность 10 км без усилителя мощности не достигается.
|
||||||
|
|
||||||
|
## Влияние на ближайшие работы
|
||||||
|
|
||||||
|
На Lab042 не влияет: там кабельная петля, несущая 435 МГц выбрана произвольно. Диапазон 200–250 МГц вступает в силу начиная с эфирных испытаний.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Запись 019
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
10 августа 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Lab042: первая передача изображения через реальный радиотракт. Эфирная проверка на паре антенн.
|
||||||
|
|
||||||
|
## Причина эфирного прогона
|
||||||
|
|
||||||
|
Кабельная петля требует переходника SMA папа-папа, которого пока нет: пигтейлы к антеннам оканчиваются папой, а разъёмы Pluto — мамы. Чтобы не простаивать, выполнена вспомогательная эфирная проверка на двух антеннах, разнесённых на 60 см. Она не заменяет кабельный опыт: уровни в помещении не контролируются строго, возможны переотражения. Её задача — снять последнюю неизвестность в коде, а именно обвязку вокруг устройства, которая в моделях не проверяется.
|
||||||
|
|
||||||
|
## Условия
|
||||||
|
|
||||||
|
- Устройство Analog Devices PlutoSDR Rev.C, чип AD9361, прошивка v0.32.
|
||||||
|
- Несущая 225 МГц, внутри целевого диапазона Записи 018.
|
||||||
|
- Антенны, рассчитанные на 108 МГц, работают на несущей как полуволновые. Частота передачи и частота, на которую рассчитана антенна, различны, поэтому в авиационный диапазон 108–137 МГц опыт не заходит.
|
||||||
|
- Изображение 320x180 в градациях серого, JPEG 4756 байт при качестве 35, десять фрагментов.
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
|
||||||
|
- Изображение передано и собрано побайтово идентично исходному. Контрольная сумма MD5 переданного и принятого файла совпала.
|
||||||
|
- Десять фрагментов из десяти, ни одной несошедшейся контрольной суммы.
|
||||||
|
- Средняя нормированная корреляция при поиске кадра 0,99951.
|
||||||
|
- Средняя оценка ухода несущей минус 0,368 Гц, что отражает общий опорный генератор передатчика и приёмника.
|
||||||
|
- Канальная скорость 18,404 кбит/с при длительности сигнала в эфире около 2,07 секунды.
|
||||||
|
- Результат воспроизведён тремя запусками подряд с одинаковой контрольной суммой.
|
||||||
|
|
||||||
|
## Дефекты, вскрытые железом
|
||||||
|
|
||||||
|
Программная модель проходила чисто, но на устройстве обнаружились четыре дефекта, каждый воспроизведён и устранён.
|
||||||
|
|
||||||
|
- Кадр длиной 566 528 отсчётов не помещался в приёмный буфер 262 144. Между отдельными чтениями поток отсчётов не является непрерывным, и кадр на стыке разрушался. Приём переведён на один буфер, размер которого рассчитывается по длине кадра.
|
||||||
|
- Частотная коррекция применялась всегда, тогда как при общем опорном генераторе истинный уход близок к нулю. Оценка по маркеру длиной 80 символов состояла из шума, и коррекция им поворачивала фазу вдоль всего кадра: измерено 65 процентов ошибочных битов. Перенесена защита should_apply_cfo_correction из Lab023, разрешающая коррекцию только вне мёртвой зоны.
|
||||||
|
- Усиление приёмника 20 дБ давало сигнал около 0,0003 от шкалы АЦП. Рабочие уровни подобраны измерением: приёмник 50 дБ, передатчик минус 10 дБ дают ноль ошибочных битов с запасом.
|
||||||
|
- Величина вектора ошибки при неудачном приёме возвращала ноль, то есть показывала идеальный сигнал там, где сигнала не было. Это тот же класс ошибки, что вписанные показатели Lab041: метрика при отказе рапортует лучшее значение. Заменена на явный признак невычислимости.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Эфирный прогон является проверкой тракта, а не измерением: уровни не контролируются как в кабельной сборке.
|
||||||
|
- Потолок усиления передатчика в эфирном режиме рассчитывается из бюджета линии по расстоянию между антеннами, а не задан константой.
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
Кабельная петля через аттенюатор 30 дБ после получения переходника папа-папа. Это и есть заявленный опыт Lab042 с контролируемыми уровнями, его результат станет основным для лабораторной. Затем Lab043 — разведение на два устройства.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Запись 020
|
||||||
|
|
||||||
|
## Дата
|
||||||
|
|
||||||
|
11 августа 2026 года
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
Lab042 завершена: кабельная петля через аттенюатор, зависимость доставки от мощности, устранение трёх дефектов отчётности.
|
||||||
|
|
||||||
|
## Схема опыта
|
||||||
|
|
||||||
|
Выход передатчика PlutoSDR через кабель SMA, аттенюатор AT30S 30 дБ, переходник и второй кабель на вход приёмника того же устройства. Антенны сняты. Это заявленный основной опыт лабораторной с контролируемыми уровнями, в отличие от вспомогательной эфирной проверки Записи 019.
|
||||||
|
|
||||||
|
## Зависимость доставки от мощности передатчика
|
||||||
|
|
||||||
|
Кабельный тракт при неизменной схеме исследован по усилению передатчика. Получен отчётливый переход от отказа к устойчивой передаче:
|
||||||
|
|
||||||
|
- TX минус 30 дБ: 0 из 10 фрагментов, контрольная сумма не сходится ни разу, средняя корреляция 0,936. Сигнал ниже порога уверенного приёма.
|
||||||
|
- TX минус 20 дБ: 8 из 10 фрагментов, средняя корреляция 0,992. Пограничный режим: часть кадров проходит.
|
||||||
|
- TX минус 10 дБ: 10 из 10 фрагментов, изображение собрано побайтово. Устойчивая передача.
|
||||||
|
|
||||||
|
Значения TX минус 30 и минус 20 дБ — исследование порога, повторять их не требуется. Рабочая точка кабельной петли: TX минус 10 дБ, RX плюс 20 дБ, аттенюатор 30 дБ, несущая 435 МГц.
|
||||||
|
|
||||||
|
Начальные умолчания кабельного режима TX минус 30 и RX плюс 20 дБ на железе прежде не проверялись; отказ первого прогона объяснялся именно этим, а не дефектом обработки.
|
||||||
|
|
||||||
|
## Результат рабочей точки
|
||||||
|
|
||||||
|
- Найдено 10 кадров из 10, контрольная сумма сошлась у всех десяти, восстановлены все фрагменты.
|
||||||
|
- Изображение собрано и совпадает с переданным. Совпадение подтверждено независимо: SHA-256 принятого файла сверен с SHA-256 переданного JPEG, пересобранного из исходника отдельно от прогона. Оба равны 751d7ea0d8711e2ef54fc5cf209a809cfee5bbb14916dd20aa201d5285b40286.
|
||||||
|
- Размер JPEG 4756 байт, качество 30, десять фрагментов.
|
||||||
|
- Длительность сигнала около 2,07 секунды, канальная скорость 18,404 кбит/с.
|
||||||
|
- Кадр 8 прошёл контрольную сумму при пониженной корреляции поиска 0,866 против 0,998 у остальных: низкая корреляция маркера не означает испорченных данных, фаза дискретизации была подобрана и кадр демодулировался чисто.
|
||||||
|
|
||||||
|
## Устранённые дефекты отчётности
|
||||||
|
|
||||||
|
Разбор кабельного прогона вскрыл три дефекта, при которых провал мог быть принят за успех. Все три устранены, каждый закрыт быстрым тестом в tests/test_lab042_reporting.py.
|
||||||
|
|
||||||
|
- Величина вектора ошибки нормировалась на единичный эталон, из-за чего при слабом сигнале становилась искусственно малой: провалившийся прогон показывал EVM меньше, чем успешный. Нормировка исправлена на мощность подогнанного сигнала. При невозможности вычислить величину возвращается признак невычислимости, а не ноль.
|
||||||
|
- Отчёт перечислял lab042_received.jpg среди созданных файлов безусловно, даже когда изображение не собрано и файла нет. Теперь файл указывается только при фактической сборке.
|
||||||
|
- Программа завершалась кодом ноль при полном отказе передачи. Введён критерий приёмки: при несобранном изображении код завершения ненулевой, чтобы автоматика не приняла провал за успех.
|
||||||
|
|
||||||
|
## Проверки
|
||||||
|
|
||||||
|
- Быстрые тесты ядра расширены с 125 до 135. Добавленные проверяют неуспешный путь: провал даёт ненулевой код, отсутствующий файл не объявляется созданным, невычислимая метрика не превращается в ноль.
|
||||||
|
- Окончательный кабельный прогон рабочей точки выполнен уже исправленной версией программы, поэтому итоговый отчёт сформирован ею.
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
Lab043: передаёт PlutoSDR, принимает RTL-SDR. Общий опорный генератор исчезает, впервые появляется реальное рассогласование несущей и частоты дискретизации двух независимых устройств. Здесь понадобится защищённая частотная коррекция, перенесённая в Записи 019.
|
||||||
|
|||||||
364
README.md
364
README.md
@@ -4,330 +4,170 @@
|
|||||||
|
|
||||||
**SDR Rover Link** — учебно-исследовательский проект по разработке цифрового радиоканала для наземного робототехнического комплекса с использованием программно-определяемого радио — SDR, Software Defined Radio.
|
**SDR Rover Link** — учебно-исследовательский проект по разработке цифрового радиоканала для наземного робототехнического комплекса с использованием программно-определяемого радио — SDR, Software Defined Radio.
|
||||||
|
|
||||||
Проект должен пройти полный путь от передачи простого текстового пакета до передачи телеметрии и отдельных JPEG-изображений через реальный радиоканал.
|
Проект должен пройти полный путь от передачи простого текстового пакета до передачи телеметрии и изображений через реальный радиоканал.
|
||||||
|
|
||||||
Основная задача проекта — не только получить работающую систему, но и последовательно изучить все этапы цифровой радиосвязи:
|
Основная задача — не только получить работающую систему, но и последовательно изучить все этапы цифровой радиосвязи: формирование данных, разбиение на пакеты, контроль целостности, исправление ошибок, цифровую модуляцию, формирование IQ-сигнала, передачу через SDR, приём, демодуляцию и восстановление исходных данных.
|
||||||
|
|
||||||
- формирование данных;
|
|
||||||
- разбиение данных на пакеты;
|
|
||||||
- контроль целостности;
|
|
||||||
- исправление ошибок;
|
|
||||||
- цифровую модуляцию;
|
|
||||||
- формирование IQ-сигнала;
|
|
||||||
- передачу через SDR;
|
|
||||||
- приём и демодуляцию;
|
|
||||||
- восстановление исходных данных.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Целевой сценарий
|
## 2. Целевой сценарий
|
||||||
|
|
||||||
Разрабатываемая система предназначена для исследовательского наземного ровера.
|
|
||||||
|
|
||||||
Целевые требования:
|
|
||||||
|
|
||||||
| Параметр | Требование |
|
| Параметр | Требование |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Дальность связи | до 10 км |
|
| Дальность связи | до 10 км |
|
||||||
| Скорость ровера | до 25 км/ч |
|
| Скорость ровера | до 25 км/ч |
|
||||||
| Прямая радиовидимость | может отсутствовать |
|
| Прямая радиовидимость | может отсутствовать |
|
||||||
| Рабочие частоты | ниже 350 МГц |
|
| Рабочий диапазон | 200–250 МГц, см. Запись 018 журнала |
|
||||||
| Основной тип данных | телеметрия и отдельные изображения |
|
| Основной тип данных | телеметрия, команды и изображения |
|
||||||
| Формат изображения | JPEG |
|
| Формат изображения | JPEG |
|
||||||
| Частота передачи изображений | отдельные кадры, не видеопоток |
|
|
||||||
| Действие при потере связи | безопасная остановка ровера |
|
| Действие при потере связи | безопасная остановка ровера |
|
||||||
|
|
||||||
Дальность 10 км без прямой видимости является целевым исследовательским требованием, а не гарантированной характеристикой первой версии системы.
|
Дальность 10 км без прямой видимости — целевое исследовательское требование, а не гарантированная характеристика первой версии.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Почему не используется обычное FPV-видео
|
## 3. Почему не используется обычное FPV-видео
|
||||||
|
|
||||||
Типовые FPV-системы работают преимущественно в диапазонах 2,4 и 5,8 ГГц.
|
Типовые FPV-системы работают в диапазонах 2,4 и 5,8 ГГц. Для наземного робота это плохо: велики потери у поверхности земли, сильное затенение рельефом и растительностью, а главное — на таких частотах практически нет дифракции, поэтому за препятствием связь пропадает.
|
||||||
|
|
||||||
Для наземного робота такие диапазоны имеют ряд ограничений:
|
В этом проекте вместо непрерывного видеопотока передаются отдельные сжатые изображения, что резко снижает требуемую скорость канала.
|
||||||
|
|
||||||
- высокие потери при работе у поверхности земли;
|
---
|
||||||
- затенение рельефом, зданиями и растительностью;
|
|
||||||
- быстрое ухудшение изображения при слабом сигнале;
|
|
||||||
- высокая загруженность распространённых диапазонов;
|
|
||||||
- зависимость от постоянного широкополосного видеопотока.
|
|
||||||
|
|
||||||
В этом проекте вместо непрерывного видео предполагается передавать отдельные сжатые изображения.
|
## 4. Текущее состояние
|
||||||
|
|
||||||
Пример:
|
**Выполнено:** лабораторные Lab001–Lab042. Пройден путь от структуры пакета до первой передачи изображения через реальный радиотракт.
|
||||||
|
|
||||||
```text
|
Что реализовано и проверено:
|
||||||
Камера
|
|
||||||
↓
|
- пакетный протокол с CRC32, фрагментация и сборка изображений;
|
||||||
JPEG-изображение
|
- модуляция и демодуляция BPSK, формирующий фильтр, кадровая синхронизация, оценка и коррекция ухода несущей;
|
||||||
↓
|
- блочное исправление стираний над GF(256), перемежение, подбор параметров;
|
||||||
Разбиение на пакеты
|
- приоритетное обслуживание команд, телеметрии и видео;
|
||||||
↓
|
- двухступенчатая безопасная остановка, сторожевой таймер, постоянное аварийное намерение, безопасный сброс;
|
||||||
Передача через SDR
|
- идентификаторы сеанса, безопасный запуск, отклонение пакетов предыдущего сеанса;
|
||||||
↓
|
- передача JPEG через настоящий радиотракт на одном PlutoSDR: сначала по эфиру на паре антенн, затем по кабелю через аттенюатор 30 дБ. Принятое изображение совпало с переданным побайтово (Записи 019 и 020).
|
||||||
Сборка изображения на наземной станции
|
|
||||||
|
**Что ещё не сделано:** два независимых устройства пока не разведены. Все передачи выполнены в петле одного PlutoSDR, где передатчик и приёмник делят опорный генератор, поэтому рассогласования несущей и частоты дискретизации между сторонами почти нет.
|
||||||
|
|
||||||
|
**Ближайшая работа:** Lab043 — передача с PlutoSDR на приём RTL-SDR. Общий опорный генератор исчезает, впервые появляется реальное рассогласование двух устройств. Техническое задание на завершённую Lab042: [docs/lab042_hardware_loopback_spec.md](docs/lab042_hardware_loopback_spec.md).
|
||||||
|
|
||||||
|
Хронология решений и результатов: [PROJECT_LOG.md](PROJECT_LOG.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Оборудование
|
||||||
|
|
||||||
|
| Позиция | Состояние |
|
||||||
|
|---|---|
|
||||||
|
| **Pluto+** — приёмопередатчик, чип AD9361, 70 МГц – 6 ГГц | есть |
|
||||||
|
| RTL-SDR — приёмный донгл | есть |
|
||||||
|
| Аттенюатор AT30S, 30 дБ, SMA | есть |
|
||||||
|
| Кабели SMA, штыревые антенны | есть |
|
||||||
|
| Усилитель мощности около 1 Вт | планируется после первых испытаний |
|
||||||
|
| Raspberry Pi как бортовой вычислитель | планируется |
|
||||||
|
|
||||||
|
Устройство подключается по USB и появляется в системе как сетевой адаптер с адресом `192.168.2.1`.
|
||||||
|
|
||||||
|
**Проверять связь с Pluto командой `ping` бесполезно.** При активном VPN трафик уходит в туннель, и «успешный» отклик приходит даже от несуществующего адреса. Достоверна только успешная установка контекста `iio.Context("ip:192.168.2.1")`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Установка окружения
|
||||||
|
|
||||||
|
Расчётная часть работает на компьютере без SDR:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Такой подход позволяет существенно снизить требуемую скорость радиоканала.
|
Для лабораторных, работающих с устройством, дополнительно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -r requirements-hardware.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Версии зафиксированы по проверенному окружению, Python 3.13.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Текущее оборудование
|
## 7. Как запускать
|
||||||
|
|
||||||
### SDR-оборудование
|
**Быстрые проверки ядра** — секунды, оборудование не нужно:
|
||||||
|
|
||||||
- RTL-SDR — приёмник;
|
```bash
|
||||||
- ADALM-PlutoSDR — приёмопередатчик, ожидается поступление;
|
python -m pytest tests/ -q
|
||||||
- HackRF One — может временно использоваться при необходимости.
|
```
|
||||||
|
|
||||||
### Вычислительное оборудование
|
**Дешёвый шлюз** — синтаксис, импорт `protocol`, быстрые проверки ядра и функциональные проверки лабораторных. Отвечает на один вопрос: не сломана ли кодовая база прямо сейчас:
|
||||||
|
|
||||||
- компьютер под управлением Windows;
|
```bash
|
||||||
- Raspberry Pi — планируется для установки на ровер;
|
python tools/quick_gate.py
|
||||||
- бортовой вычислитель ровера — будет выбран позднее.
|
```
|
||||||
|
|
||||||
### Программное обеспечение
|
**Лабораторный эксперимент** — минуты, пишет результаты в `data/processed/`:
|
||||||
|
|
||||||
- Python 3.12;
|
```bash
|
||||||
- Anaconda;
|
python -m experiments.lab041_session_restart_safety
|
||||||
- Spyder;
|
```
|
||||||
- Git;
|
|
||||||
- GNU Radio — будет установлен на следующем этапе;
|
|
||||||
- libiio и драйверы PlutoSDR — будут установлены после получения устройства.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Предварительно рассматриваемые диапазоны
|
## 8. Структура репозитория
|
||||||
|
|
||||||
В рамках проекта рассматриваются:
|
|
||||||
|
|
||||||
- 27 МГц;
|
|
||||||
- 50 МГц;
|
|
||||||
- 144 МГц;
|
|
||||||
- другие разрешённые для лабораторных испытаний частоты ниже 350 МГц.
|
|
||||||
|
|
||||||
Конкретный диапазон будет выбран после анализа:
|
|
||||||
|
|
||||||
- доступной полосы;
|
|
||||||
- требований законодательства;
|
|
||||||
- размеров антенны;
|
|
||||||
- потерь распространения;
|
|
||||||
- уровня внешних помех;
|
|
||||||
- возможностей выходного радиочастотного тракта;
|
|
||||||
- допустимой мощности передачи.
|
|
||||||
|
|
||||||
Передача по эфиру должна выполняться только на разрешённых частотах и с соблюдением допустимой мощности. Первые опыты следует проводить через аттенюатор, эквивалент нагрузки или в экранированной лабораторной конфигурации.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Основные этапы проекта
|
|
||||||
|
|
||||||
### Этап 0. Подготовка среды
|
|
||||||
|
|
||||||
- создание репозитория;
|
|
||||||
- настройка Git;
|
|
||||||
- настройка Python и Spyder;
|
|
||||||
- подготовка структуры каталогов;
|
|
||||||
- ведение документации.
|
|
||||||
|
|
||||||
### Этап 1. Пакетный протокол
|
|
||||||
|
|
||||||
- структура пакета;
|
|
||||||
- типы сообщений;
|
|
||||||
- порядковый номер пакета;
|
|
||||||
- длина данных;
|
|
||||||
- CRC;
|
|
||||||
- кодирование и декодирование.
|
|
||||||
|
|
||||||
### Этап 2. Виртуальный канал
|
|
||||||
|
|
||||||
- передача пакетов между Python-программами;
|
|
||||||
- имитация потерь;
|
|
||||||
- имитация повреждения битов;
|
|
||||||
- измерение числа ошибочных пакетов.
|
|
||||||
|
|
||||||
### Этап 3. Передача файлов
|
|
||||||
|
|
||||||
- разбиение файла на фрагменты;
|
|
||||||
- нумерация фрагментов;
|
|
||||||
- подтверждение приёма;
|
|
||||||
- повторная передача;
|
|
||||||
- сборка исходного файла.
|
|
||||||
|
|
||||||
### Этап 4. Передача изображений
|
|
||||||
|
|
||||||
- загрузка JPEG;
|
|
||||||
- разбиение JPEG на пакеты;
|
|
||||||
- передача через виртуальный канал;
|
|
||||||
- восстановление изображения;
|
|
||||||
- обработка потерянных фрагментов.
|
|
||||||
|
|
||||||
### Этап 5. Физический уровень
|
|
||||||
|
|
||||||
- BPSK;
|
|
||||||
- QPSK;
|
|
||||||
- формирование символов;
|
|
||||||
- IQ-сэмплы;
|
|
||||||
- синхронизация;
|
|
||||||
- фильтрация;
|
|
||||||
- оценка BER и PER.
|
|
||||||
|
|
||||||
### Этап 6. PlutoSDR
|
|
||||||
|
|
||||||
- подключение устройства;
|
|
||||||
- передача несущей;
|
|
||||||
- передача тестовой последовательности;
|
|
||||||
- передача цифрового пакета;
|
|
||||||
- приём сигнала RTL-SDR или вторым SDR.
|
|
||||||
|
|
||||||
### Этап 7. Радиоканал передачи изображений
|
|
||||||
|
|
||||||
- передача JPEG через SDR;
|
|
||||||
- измерение времени доставки;
|
|
||||||
- испытания при различных уровнях сигнала;
|
|
||||||
- испытания при наличии помех;
|
|
||||||
- подбор скорости и модуляции.
|
|
||||||
|
|
||||||
### Этап 8. Полевые испытания
|
|
||||||
|
|
||||||
- испытания на малой мощности;
|
|
||||||
- испытания на малой дистанции;
|
|
||||||
- постепенное увеличение расстояния;
|
|
||||||
- испытания с ретранслятором;
|
|
||||||
- испытания на наземном ровере.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Структура репозитория
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SDR_Rover
|
SDR_Rover
|
||||||
│
|
├── protocol/ реализация протокола и радиотракта
|
||||||
├── README.md
|
├── experiments/ лабораторные Lab001–Lab041
|
||||||
├── docs
|
├── tests/ быстрые автоматические проверки
|
||||||
├── protocol
|
├── tools/ вспомогательные программы и шлюз
|
||||||
├── rover
|
├── docs/ технические задания и исследования
|
||||||
├── ground_station
|
├── data/ исходные данные и результаты
|
||||||
├── gnuradio
|
├── rover/ пока пусто
|
||||||
├── tests
|
├── ground_station/ пока пусто
|
||||||
└── tools
|
└── gnuradio/ пока пусто
|
||||||
```
|
```
|
||||||
|
|
||||||
### `docs`
|
|
||||||
|
|
||||||
Документация, лабораторные работы, схемы и отчёты.
|
|
||||||
|
|
||||||
### `protocol`
|
### `protocol`
|
||||||
|
|
||||||
Реализация пакетного протокола:
|
Ядро системы. Пакет с CRC32, фрагментация изображений, кадр канала, исправление стираний, приоритеты, аварийная логика, сеансы связи, примитивы BPSK-радиотракта.
|
||||||
|
|
||||||
- структура пакета;
|
### `experiments`
|
||||||
- CRC;
|
|
||||||
- кодирование;
|
|
||||||
- декодирование;
|
|
||||||
- типы сообщений.
|
|
||||||
|
|
||||||
### `rover`
|
Лабораторные работы. Это исследовательские программы: они моделируют канал, выполняют сотни повторов, строят CSV, графики и отчёты. **Быстрыми тестами они не являются.** При импорте создают каталоги и пишут файлы, поэтому импортировать их не следует — для повторного использования кода служит `protocol/`.
|
||||||
|
|
||||||
Программы, которые будут работать на борту ровера:
|
|
||||||
|
|
||||||
- получение изображения;
|
|
||||||
- сжатие JPEG;
|
|
||||||
- передача телеметрии;
|
|
||||||
- формирование радиопакетов.
|
|
||||||
|
|
||||||
### `ground_station`
|
|
||||||
|
|
||||||
Программы наземной станции:
|
|
||||||
|
|
||||||
- приём пакетов;
|
|
||||||
- сборка изображений;
|
|
||||||
- отображение телеметрии;
|
|
||||||
- управление передачей.
|
|
||||||
|
|
||||||
### `gnuradio`
|
|
||||||
|
|
||||||
Схемы GNU Radio Companion:
|
|
||||||
|
|
||||||
- передатчик;
|
|
||||||
- приёмник;
|
|
||||||
- BPSK-модем;
|
|
||||||
- QPSK-модем;
|
|
||||||
- тестовые генераторы.
|
|
||||||
|
|
||||||
### `tests`
|
### `tests`
|
||||||
|
|
||||||
Автоматические и лабораторные тесты.
|
Настоящие быстрые проверки `protocol/`. Выполняются за доли секунды, оборудования не требуют, запускаются через `pytest`.
|
||||||
|
|
||||||
### `tools`
|
### `rover`, `ground_station`, `gnuradio`
|
||||||
|
|
||||||
Вспомогательные программы:
|
Каталоги созданы под будущую прикладную часть и пока пусты. Бортовая программа ровера, программа наземной станции и схемы GNU Radio появятся после того, как радиоканал заработает в эфире.
|
||||||
|
|
||||||
- анализ пакетов;
|
|
||||||
- генераторы тестовых данных;
|
|
||||||
- построение графиков;
|
|
||||||
- анализ журналов.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Принципы разработки
|
## 9. Данные
|
||||||
|
|
||||||
В проекте применяются следующие правила:
|
- `data/raw` — исходные данные. Крупные записи отсчётов и видео в репозиторий не помещаются, исходные изображения лабораторных хранятся.
|
||||||
|
- `data/processed` — результаты лабораторных: CSV, отчёты и графики.
|
||||||
|
|
||||||
|
**Ограничение воспроизводимости.** Исходное видео `data/raw/lab026_rover_source.mp4` (около 7,8 МБ) в репозитории не хранится. Без него Lab026 и Lab027 не запускаются, файл нужно получить отдельно.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Принципы разработки
|
||||||
|
|
||||||
1. Каждый новый термин сначала объясняется, затем используется.
|
1. Каждый новый термин сначала объясняется, затем используется.
|
||||||
2. Каждый этап должен завершаться работающим результатом.
|
2. Каждый этап завершается работающим результатом.
|
||||||
3. Радиоканал и пакетный протокол разрабатываются отдельно.
|
3. Радиоканал и пакетный протокол разрабатываются отдельно.
|
||||||
4. Код проверяется автоматическими тестами.
|
4. Код проверяется автоматическими тестами.
|
||||||
5. Все существенные решения фиксируются в документации.
|
5. Все существенные решения фиксируются в журнале.
|
||||||
6. Сложность увеличивается постепенно.
|
6. Сложность увеличивается постепенно.
|
||||||
7. Испытания по эфиру начинаются только после проверки через виртуальный канал.
|
7. Испытания по эфиру начинаются только после проверки через модель.
|
||||||
8. Передатчик сначала проверяется через аттенюатор или эквивалент нагрузки.
|
8. Передатчик сначала проверяется через аттенюатор или эквивалент нагрузки.
|
||||||
|
9. **Каждое число в отчёте должно вычисляться из измерений текущего запуска.** Перенесённые из других лабораторных величины сопровождаются указанием источника. Вписанные вручную результаты недопустимы.
|
||||||
|
|
||||||
---
|
Девятое правило добавлено после аудита достоверности, см. Запись 017 журнала.
|
||||||
|
|
||||||
## 9. Текущий статус
|
|
||||||
|
|
||||||
Выполнено:
|
|
||||||
|
|
||||||
- установлен Python;
|
|
||||||
- установлена Anaconda;
|
|
||||||
- выбран Spyder;
|
|
||||||
- установлен Git;
|
|
||||||
- создан локальный Git-репозиторий;
|
|
||||||
- создана базовая структура каталогов.
|
|
||||||
|
|
||||||
Следующая задача:
|
|
||||||
|
|
||||||
> Создать первый Python-модуль пакетного протокола и передать сообщение через виртуальный канал без SDR-оборудования.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Ближайший контрольный результат
|
|
||||||
|
|
||||||
Первая рабочая версия должна выполнять следующий цикл:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Текстовое сообщение
|
|
||||||
↓
|
|
||||||
Преобразование в байты
|
|
||||||
↓
|
|
||||||
Формирование пакета
|
|
||||||
↓
|
|
||||||
Добавление CRC
|
|
||||||
↓
|
|
||||||
Имитация радиоканала
|
|
||||||
↓
|
|
||||||
Проверка CRC
|
|
||||||
↓
|
|
||||||
Восстановление сообщения
|
|
||||||
```
|
|
||||||
|
|
||||||
Первое тестовое сообщение:
|
|
||||||
|
|
||||||
```text
|
|
||||||
HELLO SDR
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -337,4 +177,4 @@ HELLO SDR
|
|||||||
|
|
||||||
НИЯУ МИФИ
|
НИЯУ МИФИ
|
||||||
|
|
||||||
2026
|
2026
|
||||||
|
|||||||
@@ -1,181 +1,181 @@
|
|||||||
architecture,channel_condition,scenario,repetitions,control_state_packet_bytes,control_state_messages_per_second,control_state_load_kbps,application_header_fraction,lab040_control_load_kbps,control_load_increase_kbps,session_load_kbps,authorization_load_kbps,emergency_load_kbps,retry_load_kbps,episodic_service_load_kbps,video_offered_load_kbps,telemetry_offered_load_kbps,full_offered_load_kbps,control_delivered_fraction,telemetry_delivered_fraction,video_published_fraction,maximum_queue_packets
|
architecture,channel_condition,scenario,repetitions,control_state_packet_bytes,control_state_messages_per_second,control_state_load_kbps,application_header_fraction,lab040_control_load_kbps,control_load_increase_kbps,session_load_kbps,authorization_load_kbps,emergency_load_kbps,retry_load_kbps,episodic_service_load_kbps,video_offered_load_kbps,telemetry_offered_load_kbps,full_offered_load_kbps
|
||||||
no_session_ids,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.0528,0.1056,243.39286666666666,7.68,262.0584666666667,0.9987914666666666,0.9993957333333333,0.9981872,22
|
no_session_ids,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.0528,0.1056,243.39286666666666,7.68,262.0584666666667
|
||||||
no_session_ids,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.062,0.124,243.39286666666666,7.68,262.0768666666667,0.9987853333333333,0.9993926666666667,0.998178,22
|
no_session_ids,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.062,0.124,243.39286666666666,7.68,262.0768666666667
|
||||||
no_session_ids,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.057288,0.110088,243.39286666666666,7.68,262.0629546666667,0.9786038123076922,0.981301906153846,0.9079057184615386,36
|
no_session_ids,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.057288,0.110088,243.39286666666666,7.68,262.0629546666667
|
||||||
no_session_ids,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.065318,0.12731800000000001,243.39286666666666,7.68,262.0801846666667,0.978597185384615,0.9812985926923079,0.9078957780769232,36
|
no_session_ids,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.065318,0.12731800000000001,243.39286666666666,7.68,262.0801846666667
|
||||||
no_session_ids,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.11536800000000001,0.168168,243.39286666666666,7.68,262.1210346666667,0.978396448695652,0.9791982243478259,0.8605946730434781,46
|
no_session_ids,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0528,0.11536800000000001,0.168168,243.39286666666666,7.68,262.1210346666667
|
||||||
no_session_ids,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.075272,0.137272,243.39286666666666,7.68,262.0901386666667,0.9784098817391305,0.9792049408695651,0.8606148226086957,46
|
no_session_ids,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.062,0.075272,0.137272,243.39286666666666,7.68,262.0901386666667
|
||||||
no_session_ids,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
no_session_ids,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.0,0.0,0.0,0.0,0.0,243.39286666666666,7.68,261.9528666666667
|
||||||
session_safe_boot,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
session_safe_boot,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
session_safe_boot,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,21
|
session_safe_boot,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
session_safe_boot,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,22
|
session_safe_boot,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
session_safe_boot,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667,0.9987732,0.9993866,0.9981598,22
|
session_safe_boot,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667
|
||||||
session_safe_boot,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.0528,0.21280000000000002,243.39286666666666,7.68,262.1656666666667,0.9987557333333333,0.9993778666666666,0.9981336,22
|
session_safe_boot,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.0528,0.21280000000000002,243.39286666666666,7.68,262.1656666666667
|
||||||
session_safe_boot,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.062,0.2312,243.39286666666666,7.68,262.1840666666667,0.9987496,0.9993748,0.9981244,22
|
session_safe_boot,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.062,0.2312,243.39286666666666,7.68,262.1840666666667
|
||||||
session_safe_boot,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0013199999999999998,0.10852,243.39286666666666,7.68,262.0613866666667,0.9786044153846152,0.9813022076923079,0.9079066230769232,36
|
session_safe_boot,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0013199999999999998,0.10852,243.39286666666666,7.68,262.0613866666667
|
||||||
session_safe_boot,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.002904,0.110104,243.39286666666666,7.68,262.0629706666667,0.9786038061538459,0.9813019030769234,0.9079057092307693,36
|
session_safe_boot,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.002904,0.110104,243.39286666666666,7.68,262.0629706666667
|
||||||
session_safe_boot,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
session_safe_boot,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
session_safe_boot,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0062759999999999995,0.113476,243.39286666666666,7.68,262.0663426666667,0.9786025092307691,0.9813012546153849,0.907903763846154,36
|
session_safe_boot,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0062759999999999995,0.113476,243.39286666666666,7.68,262.0663426666667
|
||||||
session_safe_boot,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0007920000000000001,0.107992,243.39286666666666,7.68,262.0608586666667,0.9786046184615382,0.9813023092307697,0.9079069276923077,35
|
session_safe_boot,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0007920000000000001,0.107992,243.39286666666666,7.68,262.0608586666667
|
||||||
session_safe_boot,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000528,0.10772799999999999,243.39286666666666,7.68,262.0605946666667,0.9786047199999998,0.9813023600000003,0.9079070800000001,34
|
session_safe_boot,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000528,0.10772799999999999,243.39286666666666,7.68,262.0605946666667
|
||||||
session_safe_boot,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0027719999999999997,0.10997200000000001,243.39286666666666,7.68,262.0628386666667,0.9786038569230766,0.9813019284615387,0.9079057853846154,36
|
session_safe_boot,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0027719999999999997,0.10997200000000001,243.39286666666666,7.68,262.0628386666667
|
||||||
session_safe_boot,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9785841538461537,0.9812920769230772,0.907876230769231,31
|
session_safe_boot,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
session_safe_boot,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.055056,0.162256,243.39286666666666,7.68,262.1151226666667,0.9785837476923075,0.9812918738461539,0.9078756215384618,36
|
session_safe_boot,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.055056,0.162256,243.39286666666666,7.68,262.1151226666667
|
||||||
session_safe_boot,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667,0.9785844615384616,0.981292230769231,0.9078766923076921,32
|
session_safe_boot,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667
|
||||||
session_safe_boot,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.058476,0.21847600000000003,243.39286666666666,7.68,262.1713426666667,0.9785621246153846,0.9812810623076923,0.9078431869230771,36
|
session_safe_boot,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.058476,0.21847600000000003,243.39286666666666,7.68,262.1713426666667
|
||||||
session_safe_boot,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.06626599999999999,0.23546599999999998,243.39286666666666,7.68,262.18833266666667,0.9785555899999997,0.9812777950000001,0.907833385,36
|
session_safe_boot,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.06626599999999999,0.23546599999999998,243.39286666666666,7.68,262.18833266666667
|
||||||
session_safe_boot,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0007920000000000001,0.107992,243.39286666666666,7.68,262.0608586666667,0.9786046184615382,0.9813023092307697,0.9079069276923077,36
|
session_safe_boot,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0007920000000000001,0.107992,243.39286666666666,7.68,262.0608586666667
|
||||||
session_safe_boot,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0026399999999999996,0.10984000000000001,243.39286666666666,7.68,262.0627066666667,0.9786039076923074,0.9813019538461542,0.9079058615384616,36
|
session_safe_boot,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0026399999999999996,0.10984000000000001,243.39286666666666,7.68,262.0627066666667
|
||||||
session_safe_boot,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000132,0.107332,243.39286666666666,7.68,262.0601986666667,0.9786048723076921,0.9813024361538464,0.9079073084615384,32
|
session_safe_boot,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000132,0.107332,243.39286666666666,7.68,262.0601986666667
|
||||||
session_safe_boot,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0063360000000000005,0.11353599999999998,243.39286666666666,7.68,262.0664026666667,0.9784202017391302,0.9792101008695654,0.860630302608696,46
|
session_safe_boot,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0063360000000000005,0.11353599999999998,243.39286666666666,7.68,262.0664026666667
|
||||||
session_safe_boot,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
session_safe_boot,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
session_safe_boot,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
session_safe_boot,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.022174,0.129374,243.39286666666666,7.68,262.0822406666667,0.9784133156521737,0.9792066578260871,0.860619973478261,46
|
session_safe_boot,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.022174,0.129374,243.39286666666666,7.68,262.0822406666667
|
||||||
session_safe_boot,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001452,0.108652,243.39286666666666,7.68,262.0615186666667,0.978422325217391,0.9792111626086958,0.8606334878260873,46
|
session_safe_boot,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001452,0.108652,243.39286666666666,7.68,262.0615186666667
|
||||||
session_safe_boot,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,41
|
session_safe_boot,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
session_safe_boot,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,42
|
session_safe_boot,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
session_safe_boot,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667,0.9783998260869565,0.9791999130434783,0.8605997391304347,42
|
session_safe_boot,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667
|
||||||
session_safe_boot,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.07378799999999999,0.23378800000000002,243.39286666666666,7.68,262.1866546666667,0.9783679182608697,0.979183959130435,0.8605518773913041,46
|
session_safe_boot,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.07378799999999999,0.23378800000000002,243.39286666666666,7.68,262.1866546666667
|
||||||
session_safe_boot,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.077852,0.247052,243.39286666666666,7.68,262.1999186666667,0.9783621513043473,0.9791810756521738,0.8605432269565216,46
|
session_safe_boot,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.077852,0.247052,243.39286666666666,7.68,262.1999186666667
|
||||||
session_safe_boot,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
session_safe_boot,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,normal_start,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,ground_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,rover_restart_moving,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,both_restart,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
persistent_emergency,no_loss,ground_restart_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
persistent_emergency,no_loss,rover_restart_latched,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,long_link_break,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_ground_session_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_rover_boot_control,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_control_epoch,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,sequence_reset_new_session,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,sequence_wrap,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,21
|
persistent_emergency,no_loss,duplicate_hello,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
persistent_emergency,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,22
|
persistent_emergency,no_loss,lost_session_status,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
persistent_emergency,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667,0.9987732,0.9993866,0.9981598,22
|
persistent_emergency,no_loss,lost_authorize_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667
|
||||||
persistent_emergency,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.0528,0.21280000000000002,243.39286666666666,7.68,262.1656666666667,0.9987557333333333,0.9993778666666666,0.9981336,22
|
persistent_emergency,no_loss,lost_emergency_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.0528,0.21280000000000002,243.39286666666666,7.68,262.1656666666667
|
||||||
persistent_emergency,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.062,0.2312,243.39286666666666,7.68,262.1840666666667,0.9987496,0.9993748,0.9981244,22
|
persistent_emergency,no_loss,lost_reset_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.062,0.2312,243.39286666666666,7.68,262.1840666666667
|
||||||
persistent_emergency,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_reset_request,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,late_positive_after_emergency,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,restart_after_reset_before_ack,1,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000132,0.107332,243.39286666666666,7.68,262.0601986666667,0.9786048723076921,0.9813024361538464,0.9079073084615384,32
|
persistent_emergency,260_200,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000132,0.107332,243.39286666666666,7.68,262.0601986666667
|
||||||
persistent_emergency,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001056,0.108256,243.39286666666666,7.68,262.0611226666667,0.9786045169230766,0.9813022584615387,0.9079067753846155,35
|
persistent_emergency,260_200,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001056,0.108256,243.39286666666666,7.68,262.0611226666667
|
||||||
persistent_emergency,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001716,0.10891600000000001,243.39286666666666,7.68,262.0617826666667,0.9786042630769228,0.9813021315384617,0.9079063946153846,36
|
persistent_emergency,260_200,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001716,0.10891600000000001,243.39286666666666,7.68,262.0617826666667
|
||||||
persistent_emergency,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000528,0.10772799999999999,243.39286666666666,7.68,262.0605946666667,0.9786047199999998,0.9813023600000003,0.9079070800000001,33
|
persistent_emergency,260_200,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000528,0.10772799999999999,243.39286666666666,7.68,262.0605946666667
|
||||||
persistent_emergency,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
persistent_emergency,260_200,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
persistent_emergency,260_200,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.004794,0.111994,243.39286666666666,7.68,262.0648606666667,0.9786030792307691,0.981301539615385,0.9079046188461538,36
|
persistent_emergency,260_200,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.004794,0.111994,243.39286666666666,7.68,262.0648606666667
|
||||||
persistent_emergency,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.00039600000000000003,0.107596,243.39286666666666,7.68,262.0604626666667,0.9786047707692305,0.9813023853846157,0.9079071561538461,33
|
persistent_emergency,260_200,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.00039600000000000003,0.107596,243.39286666666666,7.68,262.0604626666667
|
||||||
persistent_emergency,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0006599999999999999,0.10785999999999998,243.39286666666666,7.68,262.0607266666667,0.9786046692307689,0.981302334615385,0.9079070038461539,34
|
persistent_emergency,260_200,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0006599999999999999,0.10785999999999998,243.39286666666666,7.68,262.0607266666667
|
||||||
persistent_emergency,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000924,0.108124,243.39286666666666,7.68,262.06099066666667,0.9786045676923075,0.981302283846154,0.9079068515384615,36
|
persistent_emergency,260_200,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000924,0.108124,243.39286666666666,7.68,262.06099066666667
|
||||||
persistent_emergency,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0037,0.11090000000000001,243.39286666666666,7.68,262.0637666666667,0.9786034999999998,0.9813017500000004,0.90790525,36
|
persistent_emergency,260_200,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0037,0.11090000000000001,243.39286666666666,7.68,262.0637666666667
|
||||||
persistent_emergency,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0013199999999999998,0.10852,243.39286666666666,7.68,262.0613866666667,0.9786044153846152,0.981302207692308,0.9079066230769232,36
|
persistent_emergency,260_200,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0013199999999999998,0.10852,243.39286666666666,7.68,262.0613866666667
|
||||||
persistent_emergency,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9785841538461537,0.9812920769230772,0.907876230769231,31
|
persistent_emergency,260_200,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
persistent_emergency,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054268,0.16146800000000003,243.39286666666666,7.68,262.1143346666667,0.9785840507692305,0.9812920253846156,0.9078760761538465,34
|
persistent_emergency,260_200,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054268,0.16146800000000003,243.39286666666666,7.68,262.1143346666667
|
||||||
persistent_emergency,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.058744000000000005,0.165944,243.39286666666666,7.68,262.1188106666667,0.9785823292307694,0.9812911646153849,0.9078734938461537,36
|
persistent_emergency,260_200,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.058744000000000005,0.165944,243.39286666666666,7.68,262.1188106666667
|
||||||
persistent_emergency,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.059267999999999994,0.21926799999999996,243.39286666666666,7.68,262.1721346666667,0.9785618199999999,0.9812809099999998,0.9078427300000002,36
|
persistent_emergency,260_200,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.059267999999999994,0.21926799999999996,243.39286666666666,7.68,262.1721346666667
|
||||||
persistent_emergency,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.067428,0.236628,243.39286666666666,7.68,262.1894946666667,0.9785551430769229,0.9812775715384617,0.9078327146153846,36
|
persistent_emergency,260_200,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.067428,0.236628,243.39286666666666,7.68,262.1894946666667
|
||||||
persistent_emergency,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001452,0.108652,243.39286666666666,7.68,262.0615186666667,0.9786043646153842,0.9813021823076926,0.9079065469230769,36
|
persistent_emergency,260_200,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001452,0.108652,243.39286666666666,7.68,262.0615186666667
|
||||||
persistent_emergency,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0015840000000000001,0.10878399999999999,243.39286666666666,7.68,262.0616506666667,0.9786043138461537,0.9813021569230773,0.9079064707692308,36
|
persistent_emergency,260_200,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0015840000000000001,0.10878399999999999,243.39286666666666,7.68,262.0616506666667
|
||||||
persistent_emergency,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000264,0.10746400000000002,243.39286666666666,7.68,262.0603306666667,0.9786048215384614,0.9813024107692311,0.9079072323076923,33
|
persistent_emergency,260_200,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.000264,0.10746400000000002,243.39286666666666,7.68,262.0603306666667
|
||||||
persistent_emergency,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,normal_start,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.003432,0.110632,243.39286666666666,7.68,262.0634986666667,0.978421464347826,0.9792107321739132,0.8606321965217394,46
|
persistent_emergency,230_1000,ground_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.003432,0.110632,243.39286666666666,7.68,262.0634986666667
|
||||||
persistent_emergency,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.004488000000000001,0.11168800000000002,243.39286666666666,7.68,262.0645546666667,0.9784210052173912,0.9792105026086958,0.8606315078260871,46
|
persistent_emergency,230_1000,rover_restart_moving,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.004488000000000001,0.11168800000000002,243.39286666666666,7.68,262.0645546666667
|
||||||
persistent_emergency,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.002112,0.10931199999999998,243.39286666666666,7.68,262.0621786666667,0.9784220382608695,0.979211019130435,0.8606330573913047,46
|
persistent_emergency,230_1000,both_restart,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.002112,0.10931199999999998,243.39286666666666,7.68,262.0621786666667
|
||||||
persistent_emergency,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
persistent_emergency,230_1000,ground_restart_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
persistent_emergency,230_1000,rover_restart_latched,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.0,0.0,0.0,0.054,243.39286666666666,7.68,262.00686666666667
|
||||||
persistent_emergency,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.009782,0.116982,243.39286666666666,7.68,262.0698486666667,0.9784187034782608,0.9792093517391307,0.8606280552173916,46
|
persistent_emergency,230_1000,long_link_break,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.009782,0.116982,243.39286666666666,7.68,262.0698486666667
|
||||||
persistent_emergency,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_ground_session_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_rover_boot_control,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.005412,0.11261199999999999,243.39286666666666,7.68,262.0654786666667,0.9784206034782608,0.9792103017391307,0.8606309052173916,46
|
persistent_emergency,230_1000,old_control_epoch,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.005412,0.11261199999999999,243.39286666666666,7.68,262.0654786666667
|
||||||
persistent_emergency,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001206,0.10840599999999999,243.39286666666666,7.68,262.0612726666667,0.9784224321739129,0.9792112160869567,0.8606336482608699,46
|
persistent_emergency,230_1000,sequence_reset_new_session,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.001206,0.10840599999999999,243.39286666666666,7.68,262.0612726666667
|
||||||
persistent_emergency,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.003564,0.11076400000000002,243.39286666666666,7.68,262.0636306666667,0.9784214069565215,0.979210703478261,0.8606321104347829,46
|
persistent_emergency,230_1000,sequence_wrap,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.003564,0.11076400000000002,243.39286666666666,7.68,262.0636306666667
|
||||||
persistent_emergency,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,41
|
persistent_emergency,230_1000,duplicate_hello,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
persistent_emergency,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,42
|
persistent_emergency,230_1000,lost_session_status,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.054,0.16119999999999998,243.39286666666666,7.68,262.1140666666667
|
||||||
persistent_emergency,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667,0.9783998260869565,0.9791999130434783,0.8605997391304347,42
|
persistent_emergency,230_1000,lost_authorize_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.053200000000000004,0.16040000000000001,243.39286666666666,7.68,262.1132666666667
|
||||||
persistent_emergency,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.059664,0.219664,243.39286666666666,7.68,262.1725306666667,0.9783740591304348,0.9791870295652177,0.860561088695652,46
|
persistent_emergency,230_1000,lost_emergency_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0528,0.059664,0.219664,243.39286666666666,7.68,262.1725306666667
|
||||||
persistent_emergency,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.08226599999999999,0.25146599999999997,243.39286666666666,7.68,262.2043326666667,0.9783602321739128,0.9791801160869563,0.8605403482608694,46
|
persistent_emergency,230_1000,lost_reset_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.062,0.08226599999999999,0.25146599999999997,243.39286666666666,7.68,262.2043326666667
|
||||||
persistent_emergency,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_reset_request,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,late_positive_after_emergency,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
persistent_emergency,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,restart_after_reset_before_ack,200,68,20.0,10.88,0.7222222222222222,7.36,3.5200000000000005,0.054,0.053200000000000004,0.0,0.0,0.1072,243.39286666666666,7.68,262.06006666666667
|
||||||
|
|||||||
|
@@ -57,6 +57,10 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
Ограничение модели
|
Ограничение модели
|
||||||
- Исследуются задержки, дубликаты, перестановка, старые пакеты и потеря оперативного состояния при перезапуске.
|
- Исследуются задержки, дубликаты, перестановка, старые пакеты и потеря оперативного состояния при перезапуске.
|
||||||
- Намеренная подделка не моделируется. Идентификаторы сеанса не доказывают отправителя, поэтому эта защита не заменяет криптографическую аутентификацию.
|
- Намеренная подделка не моделируется. Идентификаторы сеанса не доказывают отправителя, поэтому эта защита не заменяет криптографическую аутентификацию.
|
||||||
|
- Что именно моделируется розыгрышем: моменты потерь по экспоненциальной модели Good-Bad, доставка и повтор служебных сообщений сеанса, время до безопасного согласования и до разрешения движения, объём эпизодической служебной нагрузки. Только эти величины являются результатом моделирования Lab041.
|
||||||
|
- Что задано архитектурой, а не разыгрывается: исход обработки старого пакета, разрешено ли движение после перезапуска и сохраняется ли аварийное намерение. Матрица перебирает эти случаи, а поведение реализации проверяется отдельно функциональными проверками.
|
||||||
|
- Что не моделируется вовсе: транспорт видео, планировщик очередей, доставка телеметрии и доставка команд по радиоканалу. Соответствующие показатели относятся к Lab037 и Lab040 и в Lab041 не пересчитываются.
|
||||||
|
- Оценка пути после перезапуска получена по замкнутой формуле при параметрах профиля nominal Lab039 и одинакова во всех повторах; это расчёт, а не результат розыгрыша.
|
||||||
|
|
||||||
Матрица 180 сочетаний
|
Матрица 180 сочетаний
|
||||||
архитектура | канал | сценарий | повторы | безопасный сеанс, % | разрешение, % | нарушения, % | старые пакеты приняты, % | потеря намерения, % | управление, кбит/с | эпизодическая служебная, кбит/с | полный поток, кбит/с
|
архитектура | канал | сценарий | повторы | безопасный сеанс, % | разрешение, % | нарушения, % | старые пакеты приняты, % | потеря намерения, % | управление, кбит/с | эпизодическая служебная, кбит/с | полный поток, кбит/с
|
||||||
@@ -244,10 +248,13 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
Перезапуски и безопасное состояние
|
Перезапуски и безопасное состояние
|
||||||
- В основном режиме движение до синхронизации: 0; до подтверждения разрешения: 0; без новой команды оператора: 0.
|
- В основном режиме движение до синхронизации: 0; до подтверждения разрешения: 0; без новой команды оператора: 0.
|
||||||
- Максимальный путь после перезапуска в основном режиме: 9.7737 м; это путь безопасного торможения, а не продолжение старой команды.
|
- Максимальный путь после перезапуска в основном режиме: 9.7737 м; это путь безопасного торможения, а не продолжение старой команды.
|
||||||
|
- Величина рассчитана по формуле v*t + v^2/(2a) при v = 6.9444 м/с, t = 0.250 с и a = 3.0 м/с^2 из профиля nominal Lab039. Оценка одноступенчатая и не учитывает предварительное замедление Stage1, поэтому она консервативнее двухступенчатой модели Lab039.
|
||||||
- Продолжение движения после перезапуска: 0; запуск по старой команде: 0.
|
- Продолжение движения после перезапуска: 0; запуск по старой команде: 0.
|
||||||
|
|
||||||
Пакеты предыдущего сеанса
|
Пакеты предыдущего сеанса
|
||||||
- Основной режим: введено 2807, отклонено 2807, ошибочно принято 0.
|
- Основной режим: введено 2807, отклонено 2807, ошибочно принято 0.
|
||||||
|
- Эти числа получены перебором таблицы сценариев: семь сценариев со старым пакетом умножаются на число повторов, а исход перебора задан архитектурой, а не розыгрышем помех. Они показывают охват перебора, а не измеренную вероятность отклонения.
|
||||||
|
- Что старый пакет действительно отклоняется реализацией, показывают проверки 06, 07, 08, 10 и 19: они вызывают receive_control и receive_reset настоящих автоматов и сверяют причину отказа.
|
||||||
- Отрицательный уровень без идентификаторов: ошибочно принято 2807 старых пакетов.
|
- Отрицательный уровень без идентификаторов: ошибочно принято 2807 старых пакетов.
|
||||||
- Причины отказа основного режима: сеанс станции=401, запуск ровера=401, период управления=401, номер сообщения=802, событие аварии=401, запрос сброса=401.
|
- Причины отказа основного режима: сеанс станции=401, запуск ровера=401, период управления=401, номер сообщения=802, событие аварии=401, запрос сброса=401.
|
||||||
- Неоднозначных delta=2^31 отклонено: 401.
|
- Неоднозначных delta=2^31 отклонено: 401.
|
||||||
@@ -259,20 +266,24 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
- Потеря EMERGENCY_ACK не снимает аварийное намерение; потеря RESET_ACK вызывает повтор того же идемпотентного запроса.
|
- Потеря EMERGENCY_ACK не снимает аварийное намерение; потеря RESET_ACK вызывает повтор того же идемпотентного запроса.
|
||||||
|
|
||||||
Инварианты безопасности основного режима
|
Инварианты безопасности основного режима
|
||||||
- Движение до синхронизации: 0.
|
- Что означают числа ниже. Счётчики матрицы показывают, сколько сочетаний сценария и канала описывают небезопасный переход. В основном режиме безопасный запуск и постоянное аварийное намерение исключают такие переходы по построению архитектуры, поэтому счётчик равен нулю как следствие выбранной архитектуры, а не как результат розыгрыша помех.
|
||||||
- Движение до подтверждения разрешения: 0.
|
- Поведение самой реализации подтверждается функциональными проверками; их номера указаны рядом с каждым свойством. Именно проверки, а не матрица, являются доказательством для перечисленных свойств.
|
||||||
- Движение без новой команды оператора: 0.
|
- Движение до синхронизации: 0 (проверки 11, 15).
|
||||||
- Принятие команды старого сеанса станции: 0.
|
- Движение до подтверждения разрешения: 0 (проверки 12, 13).
|
||||||
- Принятие команды старого запуска ровера: 0.
|
- Движение без новой команды оператора: 0 (проверка 14).
|
||||||
- Использование старого периода управления: 0.
|
- Использование старого периода управления: 0 (проверка 08).
|
||||||
- Снятие аварийной фиксации обычной командой: 0.
|
- Снятие аварийной фиксации обычной командой: 0 (проверка 22).
|
||||||
- Потеря аварийного намерения после перезапуска станции: 0.
|
- Потеря аварийного намерения после перезапуска станции: 0 (проверки 17, 18).
|
||||||
- Движение до подтверждения сброса: 0.
|
- Движение до подтверждения сброса: 0 (проверки 20, 21).
|
||||||
- Автоматическое восстановление старой команды после сброса: 0.
|
- Автоматическое восстановление старой команды после сброса: 0 (проверки 13, 14).
|
||||||
- Принятие старого запроса сброса: 0.
|
- Принятие старого запроса сброса: 0 (проверка 19).
|
||||||
- Принятие неоднозначного номера: 0.
|
|
||||||
- Отрицательная скорость: 0.
|
Свойства, которые матрица не измеряет
|
||||||
- Превышение разрешённой скорости: 0.
|
- Следующие свойства зависят от разбора конкретного сообщения, а не от розыгрыша сценария, поэтому счётчика по 180 сочетаниям для них не существует. Ранее они выводились в списке инвариантов как нули; это было ошибкой представления, и такие строки удалены.
|
||||||
|
- Принятие команды старого сеанса станции: подтверждается проверками 06 и 16.
|
||||||
|
- Принятие команды старого запуска ровера: подтверждается проверкой 07.
|
||||||
|
- Принятие неоднозначной разности номеров 2^31: подтверждается проверкой 10.
|
||||||
|
- Отрицательная скорость и превышение разрешённой скорости: подтверждаются проверкой 04; диапазон 0...15 м/с проверяется при кодировании в protocol/control_messages.py.
|
||||||
|
|
||||||
Учёт нагрузки
|
Учёт нагрузки
|
||||||
- Постоянное управление: 10.88000 кбит/с во всех сочетаниях.
|
- Постоянное управление: 10.88000 кбит/с во всех сочетаниях.
|
||||||
@@ -280,15 +291,15 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
- Аварийные сообщения и сброс: в среднем 0.00574 кбит/с; повторы и повторные подтверждения: 0.01536 кбит/с.
|
- Аварийные сообщения и сброс: в среднем 0.00574 кбит/с; повторы и повторные подтверждения: 0.01536 кбит/с.
|
||||||
- Эпизодическая служебная нагрузка основного режима: средняя 0.12298 кбит/с, максимум 0.25147 кбит/с. Именно к этой величине относились прежние значения 0,12298–0,25147 кбит/с.
|
- Эпизодическая служебная нагрузка основного режима: средняя 0.12298 кбит/с, максимум 0.25147 кбит/с. Именно к этой величине относились прежние значения 0,12298–0,25147 кбит/с.
|
||||||
- Постоянные исходные потоки: видео 243.39287, телеметрия 7.68000, управление 10.88000 кбит/с.
|
- Постоянные исходные потоки: видео 243.39287, телеметрия 7.68000, управление 10.88000 кбит/с.
|
||||||
|
- Видео и телеметрия здесь не моделируются: значения перенесены из Lab038, поля video_load_kbps и telemetry_load_kbps файла data/processed/lab038/lab038_summary.csv. Управление вычислено по размеру пакета и частоте.
|
||||||
- Полный предложенный поток основного режима: средний 262.07584 кбит/с, максимум 262.20433 кбит/с.
|
- Полный предложенный поток основного режима: средний 262.07584 кбит/с, максимум 262.20433 кбит/с.
|
||||||
- Доставка управления не ниже 97.836%, телеметрии не ниже 97.918%, публикация видео не ниже 86.054%.
|
- Доли доставки видео, телеметрии и управления, а также длина очереди в Lab041 не определяются. Модель этой лабораторной воспроизводит обмен служебными сообщениями сеанса, но не планировщик очередей и не транспорт видео. Соответствующие показатели следует брать из Lab037 и Lab040.
|
||||||
- Максимальная очередь: 46 пакетов.
|
|
||||||
|
|
||||||
Функциональные проверки
|
Функциональные проверки
|
||||||
- ПРОЙДЕНО — 01. Сериализация всех сообщений: все десять типов имеют однозначное двоичное представление
|
- ПРОЙДЕНО — 01. Сериализация всех сообщений: все десять типов имеют однозначное двоичное представление
|
||||||
- ПРОЙДЕНО — 02. Отклонение неверной версии: неверная версия отклонена
|
- ПРОЙДЕНО — 02. Отклонение неверной версии: неверная версия отклонена
|
||||||
- ПРОЙДЕНО — 03. Отклонение неизвестного типа: неизвестный тип отклонён
|
- ПРОЙДЕНО — 03. Отклонение неизвестного типа: неизвестный тип отклонён
|
||||||
- ПРОЙДЕНО — 04. Допустимые диапазоны управления: диапазоны и сочетания полей управления проверены
|
- ПРОЙДЕНО — 04. Допустимые диапазоны управления: диапазоны и сочетания полей управления проверены, включая отрицательную скорость и границу 15 м/с
|
||||||
- ПРОЙДЕНО — 05. Номер ноль в новом сеансе: новый сеанс принимает начальный номер ноль
|
- ПРОЙДЕНО — 05. Номер ноль в новом сеансе: новый сеанс принимает начальный номер ноль
|
||||||
- ПРОЙДЕНО — 06. Старый сеанс станции: старый сеанс наземной станции отклонён
|
- ПРОЙДЕНО — 06. Старый сеанс станции: старый сеанс наземной станции отклонён
|
||||||
- ПРОЙДЕНО — 07. Старый запуск ровера: старый идентификатор запуска ровера отклонён
|
- ПРОЙДЕНО — 07. Старый запуск ровера: старый идентификатор запуска ровера отклонён
|
||||||
@@ -313,7 +324,7 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
Созданные файлы
|
Созданные файлы
|
||||||
- protocol/control_messages.py
|
- protocol/control_messages.py
|
||||||
- protocol/session_state.py
|
- protocol/session_state.py
|
||||||
- tests/lab041_session_restart_safety.py
|
- experiments/lab041_session_restart_safety.py
|
||||||
- data/processed/lab041/lab041_summary.csv
|
- data/processed/lab041/lab041_summary.csv
|
||||||
- data/processed/lab041/lab041_session_metrics.csv
|
- data/processed/lab041/lab041_session_metrics.csv
|
||||||
- data/processed/lab041/lab041_rejection_metrics.csv
|
- data/processed/lab041/lab041_rejection_metrics.csv
|
||||||
@@ -327,7 +338,6 @@ Lab041 — сеансы связи, безопасный запуск и защ
|
|||||||
- data/processed/lab041/lab041_emergency_intent.png
|
- data/processed/lab041/lab041_emergency_intent.png
|
||||||
- data/processed/lab041/lab041_safety_violations.png
|
- data/processed/lab041/lab041_safety_violations.png
|
||||||
- data/processed/lab041/lab041_protection_load.png
|
- data/processed/lab041/lab041_protection_load.png
|
||||||
- data/processed/lab041/lab041_traffic_impact.png
|
|
||||||
|
|
||||||
Ограничения
|
Ограничения
|
||||||
- Модель не включает криптографическую аутентификацию и не рассматривает намеренного нарушителя.
|
- Модель не включает криптографическую аутентификацию и не рассматривает намеренного нарушителя.
|
||||||
|
|||||||
@@ -1,181 +1,181 @@
|
|||||||
architecture,channel_condition,scenario,repetitions,restart_cases,mean_restart_to_safe_ms,p95_restart_to_safe_ms,mean_distance_after_restart_m,max_distance_after_restart_m,continued_movement_after_restart,startup_from_old_command,emergency_latch_removed,emergency_intent_lost,unsafe_movement_authorizations,emergency_intent_restored,zero_commands_continued,mean_emergency_refixed_ms,emergency_event_misclassified,old_reset_erroneously_accepted,movement_before_reset_ack,automatic_motion_after_reset,negative_speed_cases,speed_limit_exceeded_cases
|
architecture,channel_condition,scenario,repetitions,restart_cases,mean_restart_to_safe_ms,p95_restart_to_safe_ms,mean_distance_after_restart_m,max_distance_after_restart_m,continued_movement_after_restart,startup_from_old_command,emergency_latch_removed,emergency_intent_lost,unsafe_movement_authorizations,emergency_intent_restored,zero_commands_continued,mean_emergency_refixed_ms,emergency_event_misclassified,old_reset_erroneously_accepted,movement_before_reset_ack,automatic_motion_after_reset
|
||||||
no_session_ids,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,ground_restart_moving,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,ground_restart_moving,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,rover_restart_moving,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,rover_restart_moving,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,both_restart,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,both_restart,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,ground_restart_emergency,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,1,1,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,ground_restart_emergency,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,1,1,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,rover_restart_latched,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,1,0,1,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,rover_restart_latched,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,1,0,1,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,1,1,0,0
|
no_session_ids,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,1,1
|
||||||
no_session_ids,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,1,1,0,0,0,0
|
no_session_ids,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,1,1,0,0
|
||||||
no_session_ids,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,1,0,0,0,0,0.0,1,0,0,0,0,0
|
no_session_ids,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,1,0,0,0,0,0.0,1,0,0,0
|
||||||
no_session_ids,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,1,1,0,0
|
no_session_ids,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,416.6666666666667,416.6666666666667,1,1,0,0,1,0,0,0.0,0,0,1,1
|
||||||
no_session_ids,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,ground_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,ground_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,rover_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,rover_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,both_restart,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,both_restart,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,ground_restart_emergency,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,200,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,ground_restart_emergency,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,200,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,rover_restart_latched,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,200,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,rover_restart_latched,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,200,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,200,200,0,0
|
no_session_ids,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,200,200
|
||||||
no_session_ids,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,200,200,0,0,0,0
|
no_session_ids,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,200,200,0,0
|
||||||
no_session_ids,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,200,0,0,0,0,0.0,200,0,0,0,0,0
|
no_session_ids,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,200,0,0,0,0,0.0,200,0,0,0
|
||||||
no_session_ids,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,200,200,0,0
|
no_session_ids,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,200,200
|
||||||
no_session_ids,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,ground_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,ground_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,rover_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,rover_restart_moving,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,both_restart,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,both_restart,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,ground_restart_emergency,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,200,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,ground_restart_emergency,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,200,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,rover_restart_latched,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,200,0,200,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,rover_restart_latched,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,200,0,200,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
no_session_ids,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
no_session_ids,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,200,200,0,0
|
no_session_ids,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,200,200
|
||||||
no_session_ids,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,200,200,0,0,0,0
|
no_session_ids,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,200,200,0,0
|
||||||
no_session_ids,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,200,0,0,0,0,0.0,200,0,0,0,0,0
|
no_session_ids,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,200,0,0,0,0,0.0,200,0,0,0
|
||||||
no_session_ids,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,200,200,0,0
|
no_session_ids,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,416.66666666666674,416.6666666666667,200,200,0,0,200,0,0,0.0,0,0,200,200
|
||||||
session_safe_boot,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,ground_restart_moving,1,1,3.6,3.6,9.77366255144033,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,ground_restart_moving,1,1,3.6,3.6,9.77366255144033,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,rover_restart_moving,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,rover_restart_moving,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,both_restart,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,both_restart,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,ground_restart_emergency,1,1,0.0,0.0,0.0,0.0,0,0,0,1,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,ground_restart_emergency,1,1,0.0,0.0,0.0,0.0,0,0,0,1,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,rover_restart_latched,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,rover_restart_latched,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,ground_restart_moving,200,200,4.153846153846154,4.153846153846154,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,ground_restart_moving,200,200,4.153846153846154,4.153846153846154,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,200,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,200,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,ground_restart_moving,200,200,4.695652173913045,4.695652173913044,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,ground_restart_moving,200,200,4.695652173913045,4.695652173913044,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,200,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,200,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
session_safe_boot,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
session_safe_boot,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,normal_start,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,ground_restart_moving,1,1,3.6,3.6,9.77366255144033,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,ground_restart_moving,1,1,3.6,3.6,9.77366255144033,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,rover_restart_moving,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,rover_restart_moving,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,both_restart,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,both_restart,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,ground_restart_emergency,1,1,0.0,0.0,0.0,0.0,0,0,0,0,0,1,1,3.6,0,0,0,0,0,0
|
persistent_emergency,no_loss,ground_restart_emergency,1,1,0.0,0.0,0.0,0.0,0,0,0,0,0,1,1,3.6,0,0,0,0
|
||||||
persistent_emergency,no_loss,rover_restart_latched,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,rover_restart_latched,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,long_link_break,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,old_ground_session_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,old_rover_boot_control,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,old_control_epoch,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,sequence_reset_new_session,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,sequence_wrap,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,duplicate_hello,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,lost_session_status,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,lost_authorize_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,lost_emergency_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,lost_reset_ack,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,old_reset_request,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,late_positive_after_emergency,1,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,no_loss,restart_after_reset_before_ack,1,1,0.0,0.0,8.037551440329219,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,ground_restart_moving,200,200,4.153846153846154,4.153846153846154,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,ground_restart_moving,200,200,4.153846153846154,4.153846153846154,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,0,0,200,200,4.153846153846154,0,0,0,0,0,0
|
persistent_emergency,260_200,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,0,0,200,200,4.153846153846154,0,0,0,0
|
||||||
persistent_emergency,260_200,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,260_200,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,normal_start,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,ground_restart_moving,200,200,4.695652173913045,4.695652173913044,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,ground_restart_moving,200,200,4.695652173913045,4.695652173913044,9.773662551440328,9.77366255144033,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,rover_restart_moving,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,both_restart,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,0,0,200,200,4.695652173913045,0,0,0,0,0,0
|
persistent_emergency,230_1000,ground_restart_emergency,200,200,0.0,0.0,0.0,0.0,0,0,0,0,0,200,200,4.695652173913045,0,0,0,0
|
||||||
persistent_emergency,230_1000,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,rover_restart_latched,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,long_link_break,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,old_ground_session_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,old_rover_boot_control,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,old_control_epoch,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,sequence_reset_new_session,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,sequence_wrap,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,duplicate_hello,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,lost_session_status,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,lost_authorize_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,lost_emergency_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,lost_reset_ack,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,old_reset_request,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,late_positive_after_emergency,200,0,0.0,0.0,0.0,0.0,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
persistent_emergency,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0,0,0
|
persistent_emergency,230_1000,restart_after_reset_before_ack,200,200,0.0,0.0,8.03755144032922,8.037551440329219,0,0,0,0,0,0,0,0.0,0,0,0,0
|
||||||
|
|||||||
|
@@ -1,181 +1,181 @@
|
|||||||
architecture,channel_condition,scenario,repetitions,safe_session_fraction,authorization_fraction,unsafe_event_fraction,old_packet_acceptance_fraction,emergency_intent_loss_fraction,control_state_load_kbps,episodic_service_load_kbps,full_offered_load_kbps,control_delivered_fraction,telemetry_delivered_fraction,video_published_fraction,maximum_queue_packets
|
architecture,channel_condition,scenario,repetitions,safe_session_fraction,authorization_fraction,unsafe_event_fraction,old_packet_acceptance_fraction,emergency_intent_loss_fraction,control_state_load_kbps,episodic_service_load_kbps,full_offered_load_kbps
|
||||||
no_session_ids,no_loss,normal_start,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,normal_start,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,ground_restart_moving,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,ground_restart_moving,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,rover_restart_moving,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,rover_restart_moving,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,both_restart,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,both_restart,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,ground_restart_emergency,1,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,ground_restart_emergency,1,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,rover_restart_latched,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,rover_restart_latched,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,long_link_break,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,long_link_break,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,old_ground_session_control,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_ground_session_control,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,old_rover_boot_control,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_rover_boot_control,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,old_control_epoch,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_control_epoch,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,sequence_reset_new_session,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,sequence_reset_new_session,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,sequence_wrap,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,sequence_wrap,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,duplicate_hello,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,duplicate_hello,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_session_status,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,lost_session_status,1,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_authorize_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,lost_authorize_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,lost_emergency_ack,1,0.0,1.0,0.0,0.0,0.0,10.88,0.1056,262.0584666666667,0.9987914666666666,0.9993957333333333,0.9981872,22
|
no_session_ids,no_loss,lost_emergency_ack,1,0.0,1.0,0.0,0.0,0.0,10.88,0.1056,262.0584666666667
|
||||||
no_session_ids,no_loss,lost_reset_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.124,262.0768666666667,0.9987853333333333,0.9993926666666667,0.998178,22
|
no_session_ids,no_loss,lost_reset_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.124,262.0768666666667
|
||||||
no_session_ids,no_loss,old_reset_request,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,old_reset_request,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,late_positive_after_emergency,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,late_positive_after_emergency,1,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,no_loss,restart_after_reset_before_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9988266666666666,0.9994133333333334,0.99824,21
|
no_session_ids,no_loss,restart_after_reset_before_ack,1,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,normal_start,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,normal_start,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,ground_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,ground_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,rover_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,rover_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,both_restart,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,both_restart,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,ground_restart_emergency,200,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,ground_restart_emergency,200,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,rover_restart_latched,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,rover_restart_latched,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,long_link_break,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,long_link_break,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,old_ground_session_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_ground_session_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,old_rover_boot_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_rover_boot_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,old_control_epoch,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_control_epoch,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,sequence_reset_new_session,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,sequence_reset_new_session,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,sequence_wrap,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,sequence_wrap,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,duplicate_hello,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,duplicate_hello,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,lost_session_status,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,lost_session_status,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,lost_authorize_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,lost_authorize_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,lost_emergency_ack,200,0.0,1.0,0.0,0.0,0.0,10.88,0.110088,262.0629546666667,0.9786038123076922,0.981301906153846,0.9079057184615386,36
|
no_session_ids,260_200,lost_emergency_ack,200,0.0,1.0,0.0,0.0,0.0,10.88,0.110088,262.0629546666667
|
||||||
no_session_ids,260_200,lost_reset_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.12731800000000001,262.0801846666667,0.978597185384615,0.9812985926923079,0.9078957780769232,36
|
no_session_ids,260_200,lost_reset_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.12731800000000001,262.0801846666667
|
||||||
no_session_ids,260_200,old_reset_request,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,old_reset_request,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,late_positive_after_emergency,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,late_positive_after_emergency,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,260_200,restart_after_reset_before_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9786461538461536,0.9813230769230771,0.9079692307692306,31
|
no_session_ids,260_200,restart_after_reset_before_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,normal_start,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,normal_start,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,ground_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,ground_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,rover_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,rover_restart_moving,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,both_restart,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,both_restart,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,ground_restart_emergency,200,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,ground_restart_emergency,200,0.0,1.0,1.0,0.0,1.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,rover_restart_latched,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,rover_restart_latched,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,long_link_break,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,long_link_break,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,old_ground_session_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_ground_session_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,old_rover_boot_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_rover_boot_control,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,old_control_epoch,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_control_epoch,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,sequence_reset_new_session,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,sequence_reset_new_session,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,sequence_wrap,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,sequence_wrap,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,duplicate_hello,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,duplicate_hello,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_session_status,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,lost_session_status,200,0.0,1.0,0.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_authorize_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,lost_authorize_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,lost_emergency_ack,200,0.0,1.0,0.0,0.0,0.0,10.88,0.168168,262.1210346666667,0.978396448695652,0.9791982243478259,0.8605946730434781,46
|
no_session_ids,230_1000,lost_emergency_ack,200,0.0,1.0,0.0,0.0,0.0,10.88,0.168168,262.1210346666667
|
||||||
no_session_ids,230_1000,lost_reset_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.137272,262.0901386666667,0.9784098817391305,0.9792049408695651,0.8606148226086957,46
|
no_session_ids,230_1000,lost_reset_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.137272,262.0901386666667
|
||||||
no_session_ids,230_1000,old_reset_request,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,old_reset_request,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,late_positive_after_emergency,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,late_positive_after_emergency,200,0.0,1.0,1.0,1.0,0.0,10.88,0.0,261.9528666666667
|
||||||
no_session_ids,230_1000,restart_after_reset_before_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667,0.9784695652173915,0.9792347826086959,0.8607043478260868,41
|
no_session_ids,230_1000,restart_after_reset_before_ack,200,0.0,1.0,1.0,0.0,0.0,10.88,0.0,261.9528666666667
|
||||||
session_safe_boot,no_loss,normal_start,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,normal_start,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,ground_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,ground_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,rover_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,rover_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,both_restart,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,both_restart,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,ground_restart_emergency,1,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
session_safe_boot,no_loss,ground_restart_emergency,1,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,no_loss,rover_restart_latched,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
session_safe_boot,no_loss,rover_restart_latched,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,no_loss,long_link_break,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,long_link_break,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_ground_session_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_ground_session_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_rover_boot_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_rover_boot_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,old_control_epoch,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_control_epoch,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,sequence_reset_new_session,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,sequence_reset_new_session,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,sequence_wrap,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,sequence_wrap,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,duplicate_hello,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,21
|
session_safe_boot,no_loss,duplicate_hello,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
session_safe_boot,no_loss,lost_session_status,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,22
|
session_safe_boot,no_loss,lost_session_status,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
session_safe_boot,no_loss,lost_authorize_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667,0.9987732,0.9993866,0.9981598,22
|
session_safe_boot,no_loss,lost_authorize_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667
|
||||||
session_safe_boot,no_loss,lost_emergency_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.21280000000000002,262.1656666666667,0.9987557333333333,0.9993778666666666,0.9981336,22
|
session_safe_boot,no_loss,lost_emergency_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.21280000000000002,262.1656666666667
|
||||||
session_safe_boot,no_loss,lost_reset_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.2312,262.1840666666667,0.9987496,0.9993748,0.9981244,22
|
session_safe_boot,no_loss,lost_reset_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.2312,262.1840666666667
|
||||||
session_safe_boot,no_loss,old_reset_request,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,old_reset_request,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,late_positive_after_emergency,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,late_positive_after_emergency,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,no_loss,restart_after_reset_before_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
session_safe_boot,no_loss,restart_after_reset_before_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,260_200,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,260_200,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,260_200,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10852,262.0613866666667,0.9786044153846152,0.9813022076923079,0.9079066230769232,36
|
session_safe_boot,260_200,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10852,262.0613866666667
|
||||||
session_safe_boot,260_200,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.110104,262.0629706666667,0.9786038061538459,0.9813019030769234,0.9079057092307693,36
|
session_safe_boot,260_200,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.110104,262.0629706666667
|
||||||
session_safe_boot,260_200,ground_restart_emergency,200,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
session_safe_boot,260_200,ground_restart_emergency,200,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,260_200,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
session_safe_boot,260_200,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,260_200,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.113476,262.0663426666667,0.9786025092307691,0.9813012546153849,0.907903763846154,36
|
session_safe_boot,260_200,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.113476,262.0663426666667
|
||||||
session_safe_boot,260_200,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107992,262.0608586666667,0.9786046184615382,0.9813023092307697,0.9079069276923077,35
|
session_safe_boot,260_200,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107992,262.0608586666667
|
||||||
session_safe_boot,260_200,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10772799999999999,262.0605946666667,0.9786047199999998,0.9813023600000003,0.9079070800000001,34
|
session_safe_boot,260_200,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10772799999999999,262.0605946666667
|
||||||
session_safe_boot,260_200,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,260_200,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10997200000000001,262.0628386666667,0.9786038569230766,0.9813019284615387,0.9079057853846154,36
|
session_safe_boot,260_200,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10997200000000001,262.0628386666667
|
||||||
session_safe_boot,260_200,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9786049230769228,0.9813024615384619,0.9079073846153847,31
|
session_safe_boot,260_200,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,260_200,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9785841538461537,0.9812920769230772,0.907876230769231,31
|
session_safe_boot,260_200,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
session_safe_boot,260_200,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.162256,262.1151226666667,0.9785837476923075,0.9812918738461539,0.9078756215384618,36
|
session_safe_boot,260_200,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.162256,262.1151226666667
|
||||||
session_safe_boot,260_200,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667,0.9785844615384616,0.981292230769231,0.9078766923076921,32
|
session_safe_boot,260_200,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667
|
||||||
session_safe_boot,260_200,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.21847600000000003,262.1713426666667,0.9785621246153846,0.9812810623076923,0.9078431869230771,36
|
session_safe_boot,260_200,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.21847600000000003,262.1713426666667
|
||||||
session_safe_boot,260_200,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.23546599999999998,262.18833266666667,0.9785555899999997,0.9812777950000001,0.907833385,36
|
session_safe_boot,260_200,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.23546599999999998,262.18833266666667
|
||||||
session_safe_boot,260_200,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107992,262.0608586666667,0.9786046184615382,0.9813023092307697,0.9079069276923077,36
|
session_safe_boot,260_200,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107992,262.0608586666667
|
||||||
session_safe_boot,260_200,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10984000000000001,262.0627066666667,0.9786039076923074,0.9813019538461542,0.9079058615384616,36
|
session_safe_boot,260_200,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10984000000000001,262.0627066666667
|
||||||
session_safe_boot,260_200,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107332,262.0601986666667,0.9786048723076921,0.9813024361538464,0.9079073084615384,32
|
session_safe_boot,260_200,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107332,262.0601986666667
|
||||||
session_safe_boot,230_1000,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11353599999999998,262.0664026666667,0.9784202017391302,0.9792101008695654,0.860630302608696,46
|
session_safe_boot,230_1000,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11353599999999998,262.0664026666667
|
||||||
session_safe_boot,230_1000,ground_restart_emergency,200,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
session_safe_boot,230_1000,ground_restart_emergency,200,1.0,0.0,0.0,0.0,1.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,230_1000,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
session_safe_boot,230_1000,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
session_safe_boot,230_1000,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.129374,262.0822406666667,0.9784133156521737,0.9792066578260871,0.860619973478261,46
|
session_safe_boot,230_1000,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.129374,262.0822406666667
|
||||||
session_safe_boot,230_1000,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108652,262.0615186666667,0.978422325217391,0.9792111626086958,0.8606334878260873,46
|
session_safe_boot,230_1000,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108652,262.0615186666667
|
||||||
session_safe_boot,230_1000,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,41
|
session_safe_boot,230_1000,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
session_safe_boot,230_1000,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,42
|
session_safe_boot,230_1000,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
session_safe_boot,230_1000,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667,0.9783998260869565,0.9791999130434783,0.8605997391304347,42
|
session_safe_boot,230_1000,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667
|
||||||
session_safe_boot,230_1000,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.23378800000000002,262.1866546666667,0.9783679182608697,0.979183959130435,0.8605518773913041,46
|
session_safe_boot,230_1000,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.23378800000000002,262.1866546666667
|
||||||
session_safe_boot,230_1000,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.247052,262.1999186666667,0.9783621513043473,0.9791810756521738,0.8605432269565216,46
|
session_safe_boot,230_1000,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.247052,262.1999186666667
|
||||||
session_safe_boot,230_1000,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
session_safe_boot,230_1000,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
session_safe_boot,230_1000,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,normal_start,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,normal_start,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,ground_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,ground_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,rover_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,rover_restart_moving,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,both_restart,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,both_restart,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,ground_restart_emergency,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
persistent_emergency,no_loss,ground_restart_emergency,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,no_loss,rover_restart_latched,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9988086666666667,0.9994043333333333,0.998213,21
|
persistent_emergency,no_loss,rover_restart_latched,1,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,no_loss,long_link_break,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,long_link_break,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_ground_session_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_ground_session_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_rover_boot_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_rover_boot_control,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,old_control_epoch,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_control_epoch,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,sequence_reset_new_session,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,sequence_reset_new_session,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,sequence_wrap,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,sequence_wrap,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,duplicate_hello,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,21
|
persistent_emergency,no_loss,duplicate_hello,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
persistent_emergency,no_loss,lost_session_status,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9987729333333333,0.9993864666666666,0.9981594,22
|
persistent_emergency,no_loss,lost_session_status,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
persistent_emergency,no_loss,lost_authorize_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667,0.9987732,0.9993866,0.9981598,22
|
persistent_emergency,no_loss,lost_authorize_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667
|
||||||
persistent_emergency,no_loss,lost_emergency_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.21280000000000002,262.1656666666667,0.9987557333333333,0.9993778666666666,0.9981336,22
|
persistent_emergency,no_loss,lost_emergency_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.21280000000000002,262.1656666666667
|
||||||
persistent_emergency,no_loss,lost_reset_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.2312,262.1840666666667,0.9987496,0.9993748,0.9981244,22
|
persistent_emergency,no_loss,lost_reset_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.2312,262.1840666666667
|
||||||
persistent_emergency,no_loss,old_reset_request,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,old_reset_request,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,late_positive_after_emergency,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,late_positive_after_emergency,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,no_loss,restart_after_reset_before_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.9987909333333334,0.9993954666666667,0.9981864,21
|
persistent_emergency,no_loss,restart_after_reset_before_ack,1,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,260_200,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107332,262.0601986666667,0.9786048723076921,0.9813024361538464,0.9079073084615384,32
|
persistent_emergency,260_200,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107332,262.0601986666667
|
||||||
persistent_emergency,260_200,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108256,262.0611226666667,0.9786045169230766,0.9813022584615387,0.9079067753846155,35
|
persistent_emergency,260_200,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108256,262.0611226666667
|
||||||
persistent_emergency,260_200,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10891600000000001,262.0617826666667,0.9786042630769228,0.9813021315384617,0.9079063946153846,36
|
persistent_emergency,260_200,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10891600000000001,262.0617826666667
|
||||||
persistent_emergency,260_200,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10772799999999999,262.0605946666667,0.9786047199999998,0.9813023600000003,0.9079070800000001,33
|
persistent_emergency,260_200,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10772799999999999,262.0605946666667
|
||||||
persistent_emergency,260_200,ground_restart_emergency,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
persistent_emergency,260_200,ground_restart_emergency,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,260_200,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9786253846153845,0.9813126923076924,0.9079380769230773,31
|
persistent_emergency,260_200,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,260_200,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.111994,262.0648606666667,0.9786030792307691,0.981301539615385,0.9079046188461538,36
|
persistent_emergency,260_200,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.111994,262.0648606666667
|
||||||
persistent_emergency,260_200,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107596,262.0604626666667,0.9786047707692305,0.9813023853846157,0.9079071561538461,33
|
persistent_emergency,260_200,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.107596,262.0604626666667
|
||||||
persistent_emergency,260_200,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10785999999999998,262.0607266666667,0.9786046692307689,0.981302334615385,0.9079070038461539,34
|
persistent_emergency,260_200,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10785999999999998,262.0607266666667
|
||||||
persistent_emergency,260_200,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108124,262.06099066666667,0.9786045676923075,0.981302283846154,0.9079068515384615,36
|
persistent_emergency,260_200,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108124,262.06099066666667
|
||||||
persistent_emergency,260_200,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11090000000000001,262.0637666666667,0.9786034999999998,0.9813017500000004,0.90790525,36
|
persistent_emergency,260_200,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11090000000000001,262.0637666666667
|
||||||
persistent_emergency,260_200,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10852,262.0613866666667,0.9786044153846152,0.981302207692308,0.9079066230769232,36
|
persistent_emergency,260_200,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10852,262.0613866666667
|
||||||
persistent_emergency,260_200,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9785841538461537,0.9812920769230772,0.907876230769231,31
|
persistent_emergency,260_200,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
persistent_emergency,260_200,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16146800000000003,262.1143346666667,0.9785840507692305,0.9812920253846156,0.9078760761538465,34
|
persistent_emergency,260_200,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16146800000000003,262.1143346666667
|
||||||
persistent_emergency,260_200,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.165944,262.1188106666667,0.9785823292307694,0.9812911646153849,0.9078734938461537,36
|
persistent_emergency,260_200,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.165944,262.1188106666667
|
||||||
persistent_emergency,260_200,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.21926799999999996,262.1721346666667,0.9785618199999999,0.9812809099999998,0.9078427300000002,36
|
persistent_emergency,260_200,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.21926799999999996,262.1721346666667
|
||||||
persistent_emergency,260_200,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.236628,262.1894946666667,0.9785551430769229,0.9812775715384617,0.9078327146153846,36
|
persistent_emergency,260_200,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.236628,262.1894946666667
|
||||||
persistent_emergency,260_200,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108652,262.0615186666667,0.9786043646153842,0.9813021823076926,0.9079065469230769,36
|
persistent_emergency,260_200,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.108652,262.0615186666667
|
||||||
persistent_emergency,260_200,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10878399999999999,262.0616506666667,0.9786043138461537,0.9813021569230773,0.9079064707692308,36
|
persistent_emergency,260_200,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10878399999999999,262.0616506666667
|
||||||
persistent_emergency,260_200,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10746400000000002,262.0603306666667,0.9786048215384614,0.9813024107692311,0.9079072323076923,33
|
persistent_emergency,260_200,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10746400000000002,262.0603306666667
|
||||||
persistent_emergency,230_1000,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,normal_start,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,230_1000,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.110632,262.0634986666667,0.978421464347826,0.9792107321739132,0.8606321965217394,46
|
persistent_emergency,230_1000,ground_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.110632,262.0634986666667
|
||||||
persistent_emergency,230_1000,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11168800000000002,262.0645546666667,0.9784210052173912,0.9792105026086958,0.8606315078260871,46
|
persistent_emergency,230_1000,rover_restart_moving,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11168800000000002,262.0645546666667
|
||||||
persistent_emergency,230_1000,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10931199999999998,262.0621786666667,0.9784220382608695,0.979211019130435,0.8606330573913047,46
|
persistent_emergency,230_1000,both_restart,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10931199999999998,262.0621786666667
|
||||||
persistent_emergency,230_1000,ground_restart_emergency,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
persistent_emergency,230_1000,ground_restart_emergency,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,230_1000,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667,0.9784460869565217,0.9792230434782607,0.8606691304347825,41
|
persistent_emergency,230_1000,rover_restart_latched,200,1.0,0.0,0.0,0.0,0.0,10.88,0.054,262.00686666666667
|
||||||
persistent_emergency,230_1000,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.116982,262.0698486666667,0.9784187034782608,0.9792093517391307,0.8606280552173916,46
|
persistent_emergency,230_1000,long_link_break,200,1.0,1.0,0.0,0.0,0.0,10.88,0.116982,262.0698486666667
|
||||||
persistent_emergency,230_1000,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_ground_session_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,230_1000,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_rover_boot_control,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,230_1000,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11261199999999999,262.0654786666667,0.9784206034782608,0.9792103017391307,0.8606309052173916,46
|
persistent_emergency,230_1000,old_control_epoch,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11261199999999999,262.0654786666667
|
||||||
persistent_emergency,230_1000,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10840599999999999,262.0612726666667,0.9784224321739129,0.9792112160869567,0.8606336482608699,46
|
persistent_emergency,230_1000,sequence_reset_new_session,200,1.0,1.0,0.0,0.0,0.0,10.88,0.10840599999999999,262.0612726666667
|
||||||
persistent_emergency,230_1000,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11076400000000002,262.0636306666667,0.9784214069565215,0.979210703478261,0.8606321104347829,46
|
persistent_emergency,230_1000,sequence_wrap,200,1.0,1.0,0.0,0.0,0.0,10.88,0.11076400000000002,262.0636306666667
|
||||||
persistent_emergency,230_1000,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,41
|
persistent_emergency,230_1000,duplicate_hello,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
persistent_emergency,230_1000,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667,0.9783994782608693,0.9791997391304347,0.8605992173913043,42
|
persistent_emergency,230_1000,lost_session_status,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16119999999999998,262.1140666666667
|
||||||
persistent_emergency,230_1000,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667,0.9783998260869565,0.9791999130434783,0.8605997391304347,42
|
persistent_emergency,230_1000,lost_authorize_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.16040000000000001,262.1132666666667
|
||||||
persistent_emergency,230_1000,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.219664,262.1725306666667,0.9783740591304348,0.9791870295652177,0.860561088695652,46
|
persistent_emergency,230_1000,lost_emergency_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.219664,262.1725306666667
|
||||||
persistent_emergency,230_1000,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.25146599999999997,262.2043326666667,0.9783602321739128,0.9791801160869563,0.8605403482608694,46
|
persistent_emergency,230_1000,lost_reset_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.25146599999999997,262.2043326666667
|
||||||
persistent_emergency,230_1000,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,old_reset_request,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,230_1000,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,late_positive_after_emergency,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
persistent_emergency,230_1000,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667,0.978422956521739,0.9792114782608698,0.8606344347826089,41
|
persistent_emergency,230_1000,restart_after_reset_before_ack,200,1.0,1.0,0.0,0.0,0.0,10.88,0.1072,262.06006666666667
|
||||||
|
|||||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB |
BIN
data/processed/lab042/lab042_constellation.png
Normal file
BIN
data/processed/lab042/lab042_constellation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
data/processed/lab042/lab042_fragment_delivery.png
Normal file
BIN
data/processed/lab042/lab042_fragment_delivery.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
data/processed/lab042/lab042_frame_correlation.png
Normal file
BIN
data/processed/lab042/lab042_frame_correlation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
11
data/processed/lab042/lab042_frames.csv
Normal file
11
data/processed/lab042/lab042_frames.csv
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
index,packet_bytes,frame_bits,correlation_score,sample_phase,carrier_offset_hz,carrier_coherence,cfo_correction_applied,error_vector_magnitude,frame_found,header_parsed,crc_valid,fragment_recovered
|
||||||
|
0,536,4384,0.9991557547061216,19,-0.13990840791652204,0.9997935741467082,False,0.3400949142192574,True,True,True,True
|
||||||
|
1,536,4384,0.9986269709852709,51,-0.6704420071784511,0.999254107740255,False,0.1962803864132724,True,True,True,True
|
||||||
|
2,536,4384,0.9988503105697645,75,0.16739056727394086,0.9997145042457541,False,0.16392107712020013,True,True,True,True
|
||||||
|
3,536,4384,0.9984613689137739,69,0.12541328438695132,0.9993958830571172,False,0.18678168516977972,True,True,True,True
|
||||||
|
4,536,4384,0.9986165925127464,37,0.522370456657967,0.9996364225258064,False,0.15291774582164716,True,True,True,True
|
||||||
|
5,536,4384,0.9988383991200348,32,-0.1940305233482036,0.9995177607884775,False,0.1768680391093208,True,True,True,True
|
||||||
|
6,536,4384,0.9993940824417119,51,0.35917365385569155,0.9997260503953309,False,0.1442403116319193,True,True,True,True
|
||||||
|
7,536,4384,0.9986343538268945,75,0.2939902862233339,0.9996675003381007,False,0.17829502789805413,True,True,True,True
|
||||||
|
8,536,4384,0.9988608071024292,69,-0.7492708746413825,0.9995548745704333,False,0.15854008568562075,True,True,True,True
|
||||||
|
9,172,1472,0.9984590339381546,60,0.5753674284248689,0.9992453603986811,False,0.232235822035248,True,True,True,True
|
||||||
|
BIN
data/processed/lab042/lab042_received.jpg
Normal file
BIN
data/processed/lab042/lab042_received.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
85
data/processed/lab042/lab042_report.txt
Normal file
85
data/processed/lab042/lab042_report.txt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
Lab042 — передача JPEG через кабельную петлю на одном PlutoSDR
|
||||||
|
|
||||||
|
Режим прогона
|
||||||
|
- Канал: cable.
|
||||||
|
- Трасса: кабель через аттенюатор 30 дБ.
|
||||||
|
- Несущая 435.000 МГц, одинаковая на передачу и приём. Сигнал в эфир не выходит, поэтому её значение произвольно.
|
||||||
|
- Усиление передатчика -10.0 дБ, приёмника 20.0 дБ, режим ручной.
|
||||||
|
- Антенны не подключались: они создали бы путь распространения параллельно кабельному.
|
||||||
|
|
||||||
|
Параметры тракта
|
||||||
|
- Символьная скорость 20000 симв/с, отсчётов на символ 128.
|
||||||
|
- Частота дискретизации 2560000 Гц. Минимум устройства 2083333 Гц.
|
||||||
|
- Скругление 0.35, длина фильтра 10 символов, защитный интервал 16 символов.
|
||||||
|
- Значения перенесены из Lab018 без изменений, кроме передискретизации: она поднята с 32 до 128,
|
||||||
|
потому что частота 640 000 Гц из Lab018 ниже предела устройства втрое.
|
||||||
|
|
||||||
|
Передаваемое изображение
|
||||||
|
- Источник data/raw/lab011_scene.jpg, 320x180, качество 30.
|
||||||
|
- Размер JPEG 4756 байт.
|
||||||
|
- Фрагментов 10 по 512 байт данных.
|
||||||
|
- Пакет протокола от 172 до 536 байт, радиокадр от 1472 до 4384 бит.
|
||||||
|
- Последний фрагмент изображения короче остальных, поэтому его кадр меньше. Размер каждого кадра приведён в lab042_frames.csv.
|
||||||
|
- Всего передано 5292544 отсчётов, что соответствует 2.067 с эфира.
|
||||||
|
|
||||||
|
Результат приёма
|
||||||
|
- Кадров найдено 10 из 10.
|
||||||
|
- Кадров с несошедшейся CRC32: 0.
|
||||||
|
- Фрагментов восстановлено 10, доля 100.000%.
|
||||||
|
- Изображение собрано: да.
|
||||||
|
- Совпадает с переданным побайтово: да.
|
||||||
|
- Размер принятого изображения 4756 байт.
|
||||||
|
|
||||||
|
Качество сигнала
|
||||||
|
- Средняя нормированная корреляция при поиске кадра 0.99879.
|
||||||
|
- Средняя оценка ухода несущей 0.029 Гц.
|
||||||
|
- Средняя величина вектора ошибки 19.302%.
|
||||||
|
|
||||||
|
Скорость
|
||||||
|
- Длительность сигнала в эфире 2.067 с.
|
||||||
|
- Канальная скорость 18.404 кбит/с — полезные байты, делённые на время эфира.
|
||||||
|
- Полное время работы программы 16.648 с, что даёт 2.285 кбит/с.
|
||||||
|
- В программном режиме вторая величина отражает быстродействие вычислений, а не пропускную способность канала.
|
||||||
|
|
||||||
|
Кадры
|
||||||
|
номер | корреляция | фаза | уход несущей, Гц | достоверность оценки | вектор ошибки, % | найден | заголовок | CRC32 | фрагмент
|
||||||
|
0 | 0.99916 | 19 | -0.140 | 0.99979 | 34.009 | да | да | да | да
|
||||||
|
1 | 0.99863 | 51 | -0.670 | 0.99925 | 19.628 | да | да | да | да
|
||||||
|
2 | 0.99885 | 75 | 0.167 | 0.99971 | 16.392 | да | да | да | да
|
||||||
|
3 | 0.99846 | 69 | 0.125 | 0.99940 | 18.678 | да | да | да | да
|
||||||
|
4 | 0.99862 | 37 | 0.522 | 0.99964 | 15.292 | да | да | да | да
|
||||||
|
5 | 0.99884 | 32 | -0.194 | 0.99952 | 17.687 | да | да | да | да
|
||||||
|
6 | 0.99939 | 51 | 0.359 | 0.99973 | 14.424 | да | да | да | да
|
||||||
|
7 | 0.99863 | 75 | 0.294 | 0.99967 | 17.830 | да | да | да | да
|
||||||
|
8 | 0.99886 | 69 | -0.749 | 0.99955 | 15.854 | да | да | да | да
|
||||||
|
9 | 0.99846 | 60 | 0.575 | 0.99925 | 23.224 | да | да | да | да
|
||||||
|
|
||||||
|
Функциональные проверки
|
||||||
|
- ПРОЙДЕНО — 01. Размер подготовленного JPEG: JPEG занимает 4756 байт при пределе 5000, качество 30
|
||||||
|
- ПРОЙДЕНО — 02. Разбиение и сборка изображения: 10 фрагментов собираются в исходные байты
|
||||||
|
- ПРОЙДЕНО — 03. Кадр несёт пакет с CRC32: все 10 кадров несут пакет с сошедшейся CRC32
|
||||||
|
- ПРОЙДЕНО — 04. Длина сформированного кадра: кадр занимает 566528 отсчётов, что совпадает с расчётом
|
||||||
|
- ПРОЙДЕНО — 05. Масштабирование в целые отсчёты: пиковая амплитуда 1601 не превышает предела 16384
|
||||||
|
- ПРОЙДЕНО — 06. Программная петля восстанавливает изображение: программная петля вернула 4756 байт, идентичных исходным
|
||||||
|
- ПРОЙДЕНО — 07. Обнаружение искажённого кадра: одиночная битовая ошибка в данных кадра обнаружена контрольной суммой
|
||||||
|
- ПРОЙДЕНО — 08. Потеря фрагмента: потеря фрагмента не портит остальные, сборка требует всех
|
||||||
|
- ПРОЙДЕНО — 09. Достижимость частоты дискретизации: 2560000 Гц не ниже предела устройства 2083333 Гц
|
||||||
|
- ПРОЙДЕНО — 10. Настройки устройства в пределах: усиления, полоса 200000 Гц и несущая 435000000 Гц внутри допустимых пределов, сигнал занимает 27000 Гц
|
||||||
|
- ПРОЙДЕНО — 11. Потолок усиления в эфирном режиме: предел зависит от расстояния: -19.5 дБ на 5 см против 6.0 дБ на 200 см
|
||||||
|
|
||||||
|
Ограничения
|
||||||
|
- Передатчик и приёмник принадлежат одному устройству и работают от общего опорного
|
||||||
|
генератора, поэтому уход несущей между ними мал и не характеризует реальную линию.
|
||||||
|
- Повторная передача потерянных фрагментов не выполняется: потери фиксируются, но не восстанавливаются.
|
||||||
|
- Исправление стираний не подключалось.
|
||||||
|
- Разведение на два устройства и выход в эфир — предмет Lab043 и Lab044.
|
||||||
|
|
||||||
|
Созданные файлы
|
||||||
|
- data/processed/lab042/lab042_received.jpg
|
||||||
|
- data/processed/lab042/lab042_summary.csv
|
||||||
|
- data/processed/lab042/lab042_frames.csv
|
||||||
|
- data/processed/lab042/lab042_report.txt
|
||||||
|
- data/processed/lab042/lab042_constellation.png
|
||||||
|
- data/processed/lab042/lab042_spectrum.png
|
||||||
|
- data/processed/lab042/lab042_frame_correlation.png
|
||||||
|
- data/processed/lab042/lab042_fragment_delivery.png
|
||||||
BIN
data/processed/lab042/lab042_spectrum.png
Normal file
BIN
data/processed/lab042/lab042_spectrum.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
2
data/processed/lab042/lab042_summary.csv
Normal file
2
data/processed/lab042/lab042_summary.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
loopback_mode,path_description,antenna_distance_cm,source_image,image_width,image_height,jpeg_quality,jpeg_bytes,fragment_data_size,fragment_count,packet_bytes_max,packet_bytes_min,frame_bits_max,frame_bits_min,symbol_rate,samples_per_symbol,sample_rate_hz,carrier_hz,transmitted_samples,air_time_seconds,frames_received,frames_failed_crc,fragments_recovered,fragment_delivery_fraction,image_reassembled,image_identical,received_image_bytes,transfer_seconds,channel_bitrate_kbps,wall_clock_bitrate_kbps,mean_correlation_score,mean_carrier_offset_hz,mean_error_vector_magnitude,tx_gain_db,rx_gain_db,attenuator_db
|
||||||
|
cable,кабель через аттенюатор 30 дБ,0.0,data/raw/lab011_scene.jpg,320,180,30,4756,512,10,536,172,4384,1472,20000,128,2560000,435000000,5292544,2.0674,10,0,10,1.0,True,True,4756,16.64845450001303,18.40379220276676,2.285377300335609,0.9987897674116901,0.02900538637381942,0.193017509510432,-10.0,20.0,30.0
|
||||||
|
286
docs/lab042_hardware_loopback_spec.md
Normal file
286
docs/lab042_hardware_loopback_spec.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
# Lab042 — техническое задание
|
||||||
|
|
||||||
|
## Передача JPEG через настоящий радиотракт: кабельная петля на одном PlutoSDR
|
||||||
|
|
||||||
|
Документ предназначен для исполнителя, не участвовавшего в предыдущих лабораторных. Всё, что нужно для работы, изложено здесь.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Зачем эта лабораторная
|
||||||
|
|
||||||
|
За Lab001–Lab041 собран весь тракт передачи изображения, кроме одного звена: **передатчик ни разу не включался**. Во всём проекте нет ни одного вызова передачи через SDR.
|
||||||
|
|
||||||
|
Цель Lab042 — соединить уже готовые части и получить **изображение, прошедшее через настоящий радиотракт**. Не улучшить, не оптимизировать, не измерить дальность. Получить картинку на выходе.
|
||||||
|
|
||||||
|
Всё, что не требуется для этой цели, в Lab042 не делается.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Что уже готово и должно быть переиспользовано
|
||||||
|
|
||||||
|
Ничего из перечисленного писать заново не нужно.
|
||||||
|
|
||||||
|
| Звено | Где взять |
|
||||||
|
|---|---|
|
||||||
|
| Разбиение JPEG на фрагменты и сборка обратно | `protocol/image_fragments.py`, примеры в `experiments/lab007_image_fragmentation.py` |
|
||||||
|
| Пакет с CRC32, кодирование и разбор | `protocol/packet.py` |
|
||||||
|
| Подбор размера и качества JPEG | `experiments/lab010_image_optimization.py`, готовые профили в `data/processed/lab010` |
|
||||||
|
| Формирование радиокадра с преамбулой и маркером | `build_radio_frame` в `experiments/lab018_bpsk_radio_frame.py` |
|
||||||
|
| Фильтр приподнятого косинуса | `root_raised_cosine_taps` там же |
|
||||||
|
| Модуляция BPSK | `bpsk_modulate` там же |
|
||||||
|
| Поиск кадра в потоке отсчётов | `find_radio_frame` в `experiments/lab019_bpsk_receiver.py` |
|
||||||
|
| Оценка и коррекция ухода частоты | `estimate_carrier_parameters`, `correct_phase_and_frequency` там же |
|
||||||
|
| Демодуляция и разбор кадра | `bpsk_demodulate`, `decode_radio_frame` там же |
|
||||||
|
| Работа с Pluto на приём | `experiments/lab024a_pluto_rx_capture.py` |
|
||||||
|
|
||||||
|
**Единственное, чего нет в проекте — передача.** Это и есть основной объём работы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Почему петля на одном устройстве, а не два устройства
|
||||||
|
|
||||||
|
Передатчик и приёмник одного PlutoSDR работают от общего опорного генератора. Поэтому между ними практически нет ухода несущей частоты и не расходятся часы.
|
||||||
|
|
||||||
|
Это снимает ровно те две проблемы, которые в проекте проработаны слабее всего, и позволяет получить картинку сразу. Разведение на два устройства — отдельная лабораторная Lab043, там появится настоящий уход частоты, и вот там понадобятся наработки Lab021–Lab023.
|
||||||
|
|
||||||
|
**Не пытайтесь сделать Lab042 и Lab043 одной работой.** Разница между ними — вся сложность синхронизации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Наличное оборудование
|
||||||
|
|
||||||
|
Подтверждено по состоянию на 10 августа 2026 года.
|
||||||
|
|
||||||
|
| Позиция | Состояние |
|
||||||
|
|---|---|
|
||||||
|
| **Pluto+** — SDR, 2 приёмника и 2 передатчика, гигабитный Ethernet | есть |
|
||||||
|
| RTL-SDR — приёмный донгл | есть, понадобится в Lab043 |
|
||||||
|
| Кабели SMA | будут найдены |
|
||||||
|
| Антенны: штыревые на магнитном основании, короткая штыревая | есть, **в Lab042 не используются** |
|
||||||
|
| Рамочная антенна Donut AM (0,5–2 МГц) | к проекту отношения не имеет |
|
||||||
|
| **Аттенюатор AT30S**, 30 дБ, до 200 Вт, разъёмы SMA | есть |
|
||||||
|
|
||||||
|
Аппаратных препятствий к началу работы нет. Запас по мощности у аттенюатора избыточен на много порядков — рассчитан на передатчики в сотни ватт, а у нас доли милливатта, — но это никак не мешает: ослабление от уровня сигнала не зависит.
|
||||||
|
|
||||||
|
Важно: устройство — **Pluto+**, а не оригинальный ADALM-PLUTO. Отличия, которые имеют значение:
|
||||||
|
|
||||||
|
- связь по Ethernet, а не по USB; адрес `ip:192.168.2.1` уже подтверждён работой Lab024a;
|
||||||
|
- два приёмных и два передающих канала; в Lab042 используется только первый канал каждого;
|
||||||
|
- вопреки надписи на корпусе, устройство сообщает о себе как `Analog Devices PlutoSDR Rev.C (Z7010-AD9361)` и представлено **одним приёмным и одним передающим каналом**: буферные устройства `cf-ad9361-lpc` и `cf-ad9361-dds-core-lpc` несут по паре каналов I и Q. Поэтому класс `adi.Pluto` подходит, переход на `adi.ad9361` не требуется;
|
||||||
|
- чип **AD9361**, а не урезанный AD9363. Диапазон перестройки приёмника от 70 МГц до 6 ГГц, передатчика от 46,9 МГц до 6 ГГц. Целевой диапазон проекта ниже 350 МГц устройству доступен штатно, без доработки прошивки. Для Lab044 это снимает ранее открытый вопрос;
|
||||||
|
- прошивка `v0.32-1-g7bdc-dirty`, ядро 4.19. Обновление в рамках Lab042 не выполняется.
|
||||||
|
|
||||||
|
## 5. Требования к схеме и сохранности оборудования
|
||||||
|
|
||||||
|
Разрешения на работу в эфире у владельца проекта есть, вопрос законности здесь не рассматривается. Перечисленное ниже защищает оборудование и чистоту опыта.
|
||||||
|
|
||||||
|
1. **Антенны убрать со стола до начала работы.** Причина техническая: антенна на передатчике или приёмнике создаёт паразитный путь распространения параллельно кабельному. Уровень на входе приёмника перестаёт соответствовать расчёту, появляется переотражённый сигнал, и при отказе будет невозможно понять, что именно не сработало. Кабельная петля ценна тем, что в ней известно всё; антенна это свойство разрушает.
|
||||||
|
2. Передатчик не включать при отключённом кабеле: выход без нагрузки вреден для оконечного каскада.
|
||||||
|
3. Аттенюатор AT30S включается **в разрыв** между выходом передатчика и входом приёмника. Не параллельно, не на свободный порт — именно последовательно в тракт.
|
||||||
|
4. Перед первым включением передатчика убедиться глазами, что аттенюатор действительно в цепи. Это единственное, что стоит между опечаткой в настройке усиления и сгоревшим приёмником.
|
||||||
|
|
||||||
|
### Расчёт уровней
|
||||||
|
|
||||||
|
| Величина | Значение |
|
||||||
|
|---|---|
|
||||||
|
| Выход передатчика Pluto+ при усилении 0 дБ | около **+7 дБм** |
|
||||||
|
| Предельно допустимый уровень на входе приёмника | около **+2,5 дБм** |
|
||||||
|
| Ослабление аттенюатора | 30 дБ |
|
||||||
|
| Уровень на входе приёмника при усилении 0 дБ | около **−23 дБм** |
|
||||||
|
|
||||||
|
Вывод: **с аттенюатором в разрыве весь диапазон `tx_hardwaregain_chan0` от −89 до 0 дБ безопасен для приёмника.** Запас до порога повреждения составляет более 25 дБ даже в самом худшем случае.
|
||||||
|
|
||||||
|
**Порядок подбора усиления:**
|
||||||
|
|
||||||
|
1. Начать с `tx_hardwaregain_chan0 = -30`. Это даёт около −53 дБм на входе приёмника — заведомо чистый и заведомо безопасный уровень.
|
||||||
|
2. Если сигнал слишком слаб, поднимать шагами по 10 дБ.
|
||||||
|
3. Если созвездие разваливается при сильном сигнале — это насыщение приёмника, лечится **уменьшением** усиления, а не увеличением.
|
||||||
|
|
||||||
|
Без аттенюатора работать запрещено даже при малом усилении: защита нужна не от расчётного уровня, а от ошибки оператора.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Программное окружение: препятствие, которое надо снять первым
|
||||||
|
|
||||||
|
Проверка окружения 10 августа 2026 года показала следующее.
|
||||||
|
|
||||||
|
Интерпретатор, доступный в командной строке — `C:\Users\user\AppData\Local\Programs\Python\Python313\python.exe`, версия 3.13.1. В нём **отсутствуют `pyadi-iio` и `libiio`**, то есть работать с Pluto+ из него невозможно. Также отсутствует `scipy`, который нужен лабораторным Lab020–Lab022, Lab024b и Lab025.
|
||||||
|
|
||||||
|
При этом Lab024a когда-то отработала с настоящим устройством. Значит, существует второе окружение — по всей видимости, среда Anaconda, — где всё установлено, и запуск шёл оттуда.
|
||||||
|
|
||||||
|
**Первое действие исполнителя: определить, в каком окружении запускаются лабораторные, и зафиксировать это.** Пока это не сделано, невозможно ни повторить Lab024a, ни начать Lab042.
|
||||||
|
|
||||||
|
Требуется:
|
||||||
|
|
||||||
|
1. Найти окружение, где установлены `pyadi-iio` и `libiio`, и проверить связь с устройством.
|
||||||
|
2. Записать в отчёт: путь к интерпретатору, версию Python, версии `pyadi-iio` и `libiio`.
|
||||||
|
3. Если такого окружения нет — установить `pyadi-iio` и `libiio`, зафиксировав версии.
|
||||||
|
|
||||||
|
Расчётные лабораторные, включая Lab041, в окружении без `pyadi-iio` запускаются нормально — им нужны только `numpy`, `matplotlib` и `opencv`. Разделение аппаратных и расчётных зависимостей относится к этапу стабилизации и в Lab042 не решается, но результат проверки надо записать: он понадобится при составлении описания окружения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Параметры тракта
|
||||||
|
|
||||||
|
Значения тракта взяты из Lab018 и менять их в этой лабораторной **запрещено**, за единственным исключением, описанным сразу под таблицей. Причина запрета: переход на железо сам по себе вносит достаточно неизвестных. Менять одновременно и параметры сигнала — значит потерять возможность понять причину отказа.
|
||||||
|
|
||||||
|
| Параметр | Значение | Источник |
|
||||||
|
|---|---|---|
|
||||||
|
| Символьная скорость | 20 000 симв/с | `SYMBOL_RATE`, Lab018 |
|
||||||
|
| Отсчётов на символ | **128** | изменено против Lab018, см. ниже |
|
||||||
|
| Частота дискретизации | **2 560 000 Гц** | произведение двух предыдущих |
|
||||||
|
| Скругление фильтра | 0,35 | `RRC_ROLLOFF`, Lab018 |
|
||||||
|
| Длина фильтра | 10 символов | `RRC_SPAN_SYMBOLS`, Lab018 |
|
||||||
|
| Преамбула | 64 бита | `PREAMBLE_BIT_COUNT`, Lab018 |
|
||||||
|
| Слово синхронизации | 0xD391 | `RADIO_SYNC_WORD`, Lab018 |
|
||||||
|
| Защитный интервал | 16 символов | `GUARD_SYMBOL_COUNT`, Lab018 |
|
||||||
|
| Амплитуда передачи | 0,70 | `TX_AMPLITUDE`, Lab018 |
|
||||||
|
|
||||||
|
### Обязательное отступление от Lab018: частота дискретизации
|
||||||
|
|
||||||
|
Опрос устройства 10 августа 2026 года показал, что **минимальная частота дискретизации AD9361 составляет 2 083 333 Гц**. Частота 640 000 Гц из Lab018 физически недостижима: она втрое ниже предела. Понизить её можно только включением встроенного КИХ-фильтра с прореживанием, что добавляет к первому включению передатчика ещё один непроверенный механизм.
|
||||||
|
|
||||||
|
Принято решение поднять число отсчётов на символ с 32 до 128. Тогда:
|
||||||
|
|
||||||
|
- символьная скорость остаётся **20 000 симв/с**, как в Lab018;
|
||||||
|
- скругление, длина фильтра, преамбула, слово синхронизации и защитный интервал — без изменений;
|
||||||
|
- частота дискретизации становится 20 000 × 128 = **2 560 000 Гц**, что выше минимума с запасом.
|
||||||
|
|
||||||
|
Меняется **только степень передискретизации** — величина, относящаяся к стыку с устройством, а не к самому сигналу. Форма спектра, скорость и структура кадра остаются теми же, что проверены в Lab018–Lab023. Это единственное разрешённое расхождение с Lab018, и оно вынужденное.
|
||||||
|
|
||||||
|
Полезная скорость получается около 20 кбит/с. Для картинки в несколько килобайт это единицы секунд. Этого достаточно: цель — картинка, а не скорость.
|
||||||
|
|
||||||
|
Побочное следствие: фильтр приподнятого косинуса при 128 отсчётах на символ и длине 10 символов получается на 1281 отвод. Свёртку следует выполнять через быстрое преобразование Фурье (`scipy.signal.fftconvolve`), иначе формирование сигнала займёт недопустимо много времени.
|
||||||
|
|
||||||
|
### Параметры PlutoSDR
|
||||||
|
|
||||||
|
| Параметр | Значение | Примечание |
|
||||||
|
|---|---|---|
|
||||||
|
| Адрес устройства | `ip:192.168.2.1` | как в Lab024a |
|
||||||
|
| Несущая передачи и приёма | 435 МГц | обе одинаковые |
|
||||||
|
| Частота дискретизации | 2 560 000 | совпадает с трактом, минимум устройства 2 083 333 |
|
||||||
|
| Полоса радиочастотного тракта | 200 000 Гц | минимум Pluto, сигнал занимает около 27 кГц |
|
||||||
|
| Усиление передатчика | начать с −30 дБ | с аттенюатором безопасен весь диапазон, см. раздел 5 |
|
||||||
|
| Режим усиления приёмника | `manual` | автоматика мешает измерениям |
|
||||||
|
| Усиление приёмника | начать с 20 дБ | подобрать по амплитуде |
|
||||||
|
| Циклический буфер передачи | **выключен** | см. раздел 9, ловушка 1 |
|
||||||
|
|
||||||
|
Про несущую: 435 МГц выбрана потому, что нижняя граница PlutoSDR составляет около 325 МГц, а целевой диапазон проекта ниже 350 МГц находится у самого края. Поскольку передача идёт по кабелю и в эфир не выходит, выбор частоты здесь ни на что не влияет. Вопрос рабочего диапазона решается отдельно перед Lab044.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Порядок работы
|
||||||
|
|
||||||
|
**Передающая сторона**
|
||||||
|
|
||||||
|
1. Взять исходное изображение из `data/raw`, привести к профилю из Lab010 так, чтобы JPEG получился не крупнее 5 КБ.
|
||||||
|
2. Разбить на фрагменты через `protocol/image_fragments.py`.
|
||||||
|
3. Каждый фрагмент упаковать в пакет с CRC32 через `protocol/packet.py`.
|
||||||
|
4. Каждый пакет обернуть в радиокадр через `build_radio_frame`.
|
||||||
|
5. Промодулировать BPSK и отфильтровать.
|
||||||
|
6. Привести к целым числам и передать.
|
||||||
|
|
||||||
|
**Приёмная сторона**
|
||||||
|
|
||||||
|
1. Запустить приём **до** начала передачи.
|
||||||
|
2. Найти кадры через `find_radio_frame`.
|
||||||
|
3. Скорректировать фазу и частоту.
|
||||||
|
4. Демодулировать, разобрать кадр, проверить CRC32.
|
||||||
|
5. Собрать изображение из уцелевших фрагментов и сохранить на диск.
|
||||||
|
|
||||||
|
Приём и передача идут в одном процессе. Приём запускается первым — иначе начало первого кадра теряется безвозвратно.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Ловушки, на которых спотыкаются в первый раз
|
||||||
|
|
||||||
|
Перечислено то, что почти наверняка будет сделано неправильно без предупреждения.
|
||||||
|
|
||||||
|
1. **Циклический буфер передачи.** У PlutoSDR буфер передачи по умолчанию повторяется бесконечно. Кажется, что передан один кадр, а передаётся бесконечный поток. Признак: приёмник видит тысячи копий одного кадра, а передача не заканчивается. Буфер надо явно выключить и явно освобождать после передачи.
|
||||||
|
|
||||||
|
2. **Масштаб отсчётов.** PlutoSDR принимает целые числа, а не дробные значения от минус единицы до единицы. Готовый сигнал надо умножить на масштабный множитель, обычно 2 в степени 14. Признак ошибки: передаётся почти тишина, приёмник ничего не находит.
|
||||||
|
|
||||||
|
3. **Приём запущен после передачи.** Начало кадра теряется, преамбула не находится. Признак: `find_radio_frame` не срабатывает при заведомо исправном сигнале.
|
||||||
|
|
||||||
|
4. **Размер приёмного буфера меньше кадра.** Кадр не помещается целиком, поиск преамбулы срывается. Буфер должен вмещать кадр с запасом минимум вдвое.
|
||||||
|
|
||||||
|
5. **Автоматическая регулировка усиления приёмника.** Меняет амплитуду в процессе и портит оценку. Режим должен быть ручным.
|
||||||
|
|
||||||
|
6. **Насыщение приёмника.** При слишком слабом ослаблении сигнал упирается в потолок и созвездие разваливается, хотя уровень кажется отличным. Признак: демодуляция не работает при очень сильном сигнале. Лечится увеличением ослабления, а не усилением.
|
||||||
|
|
||||||
|
7. **Переходный процесс в начале передачи.** Первые несколько десятков отсчётов недостоверны. Поэтому в тракте есть защитный интервал в 16 символов — его нельзя убирать.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Что считается выполненной работой
|
||||||
|
|
||||||
|
Лабораторная принимается, если выполнено всё перечисленное.
|
||||||
|
|
||||||
|
**Обязательный результат**
|
||||||
|
|
||||||
|
1. Файл принятого изображения на диске, открывающийся просмотрщиком.
|
||||||
|
2. Принятое изображение совпадает с переданным при отсутствии потерь — сравнение по контрольной сумме всего файла.
|
||||||
|
3. Работа воспроизводится: два запуска подряд дают одинаковый успешный результат.
|
||||||
|
|
||||||
|
**Обязательные измерения**
|
||||||
|
|
||||||
|
| Величина | Как получить |
|
||||||
|
|---|---|
|
||||||
|
| Размер переданного JPEG, байт | из файла |
|
||||||
|
| Число фрагментов и пакетов | из разбиения |
|
||||||
|
| Число принятых кадров | счётчик приёмника |
|
||||||
|
| Число кадров, не прошедших CRC32 | счётчик приёмника |
|
||||||
|
| Доля успешно принятых фрагментов | отношение |
|
||||||
|
| Полное время передачи, секунды | измерение |
|
||||||
|
| Фактическая полезная скорость, кбит/с | расчёт из двух предыдущих |
|
||||||
|
| Усиление передатчика и приёмника | из настроек устройства |
|
||||||
|
| Ослабление аттенюатора, дБ | 30, AT30S |
|
||||||
|
|
||||||
|
**Обязательные файлы в `data/processed/lab042`**
|
||||||
|
|
||||||
|
- принятое изображение;
|
||||||
|
- CSV с измерениями из таблицы выше;
|
||||||
|
- текстовый отчёт;
|
||||||
|
- график созвездия принятого сигнала;
|
||||||
|
- график спектра принятого сигнала.
|
||||||
|
|
||||||
|
**Требования к коду**
|
||||||
|
|
||||||
|
- Ничего из `protocol/` не переписывать. Только использовать.
|
||||||
|
- Параметры тракта из раздела 5 не менять.
|
||||||
|
- Все константы вынести в начало файла с указанием источника, как это сделано в Lab041.
|
||||||
|
- Никаких вписанных руками результатов: каждое число в отчёте должно вычисляться из измерений текущего запуска. Это отдельно проверяется при приёмке.
|
||||||
|
- Файл лабораторной: `experiments/lab042_pluto_image_loopback.py`, запуск через `python -m experiments.lab042_pluto_image_loopback` из корня проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Что в этой лабораторной делать не надо
|
||||||
|
|
||||||
|
Перечислено, чтобы не расширять объём.
|
||||||
|
|
||||||
|
- Не реализовывать повторную передачу потерянных фрагментов. Потери фиксируются, но не восстанавливаются.
|
||||||
|
- Не подключать исправление стираний из `protocol/packet_erasure_fec.py`.
|
||||||
|
- Не поднимать скорость и не менять модуляцию.
|
||||||
|
- Не выходить в эфир и не подключать антенну. Это вопрос не разрешений, а объёма: эфирная передача — предмет Lab044, и смешивать её с первым включением передатчика нельзя.
|
||||||
|
- Не работать с двумя устройствами.
|
||||||
|
- Не трогать логику сеансов, аварийной остановки и приоритетов — она к передаче картинки отношения не имеет.
|
||||||
|
|
||||||
|
Всё это относится к последующим лабораторным.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Порядок приёмки
|
||||||
|
|
||||||
|
1. Исполнитель выполняет работу и сообщает о готовности.
|
||||||
|
2. Проверяется `python tools/quick_gate.py` — должен проходить.
|
||||||
|
3. Проводится аудит: соответствие разделу 10, отсутствие вписанных результатов, происхождение каждого числа в отчёте.
|
||||||
|
4. При наличии замечаний они оформляются списком с указанием файла и строки.
|
||||||
|
5. После приёмки в `PROJECT_LOG.md` добавляется новая запись по принятому в проекте образцу.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Что дальше
|
||||||
|
|
||||||
|
- **Lab043** — разведение на два устройства: передаёт PlutoSDR, принимает RTL-SDR. Появляется настоящий уход несущей частоты и расхождение часов. Здесь понадобятся Lab021–Lab023.
|
||||||
|
- **Lab044** — выбор рабочего диапазона и первый выход в эфир на малой мощности. Разрешения имеются, поэтому вопрос сводится к техническому выбору диапазона, мощности и антенны.
|
||||||
|
- Повышение скорости, исправление стираний и передача последовательности кадров — после того, как картинка устойчиво проходит.
|
||||||
521
docs/lab043_pluto_to_rtlsdr_spec.md
Normal file
521
docs/lab043_pluto_to_rtlsdr_spec.md
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
# Lab043. Передача JPEG с Pluto+ на независимый RTL-SDR
|
||||||
|
|
||||||
|
## 1. Цель лабораторной
|
||||||
|
|
||||||
|
Передать тот же подготовленный JPEG, который использован в Lab042, с
|
||||||
|
Pluto+ на отдельный RTL-SDR и восстановить его побайтово.
|
||||||
|
|
||||||
|
В Lab042 передатчик и приёмник находились в одном Pluto+ и использовали
|
||||||
|
общий опорный генератор. Lab043 должна убрать это упрощение и раздельно
|
||||||
|
измерить три эффекта:
|
||||||
|
|
||||||
|
1. рассогласование несущих частот передатчика и приёмника;
|
||||||
|
2. относительное рассогласование частот дискретизации двух устройств;
|
||||||
|
3. начальную фазу выбора отсчёта символа внутри символьного интервала.
|
||||||
|
|
||||||
|
Лабораторная считается успешной только при выполнении радиокритериев,
|
||||||
|
прикладных критериев и программных проверок, определённых в разделе 11.
|
||||||
|
Запуск программы сам по себе успехом не считается.
|
||||||
|
|
||||||
|
## 2. Границы работы
|
||||||
|
|
||||||
|
Lab043 остаётся кабельной. Эфир добавил бы многолучёвость, внешние сигналы
|
||||||
|
и неконтролируемые потери, хотя предмет этой работы состоит в
|
||||||
|
синхронизации независимых устройств.
|
||||||
|
|
||||||
|
В Lab043 не входят:
|
||||||
|
|
||||||
|
- передача через антенны;
|
||||||
|
- выбор рабочего диапазона 200-250 МГц;
|
||||||
|
- ARQ и повторная передача;
|
||||||
|
- исправление стираний и новая FEC;
|
||||||
|
- потоковое видео;
|
||||||
|
- скачки по частоте;
|
||||||
|
- изменение логики управления и безопасности ровера.
|
||||||
|
|
||||||
|
Эфирный тракт остаётся предметом следующей лабораторной.
|
||||||
|
|
||||||
|
## 3. Подтверждённое оборудование и окружение
|
||||||
|
|
||||||
|
Состояние оборудования проверено непосредственным опросом. Перед
|
||||||
|
реализацией и перед каждым аппаратным прогоном его необходимо проверить
|
||||||
|
повторно.
|
||||||
|
|
||||||
|
### 3.1. Передатчик
|
||||||
|
|
||||||
|
- Pluto+;
|
||||||
|
- устройство должно открываться успешным `iio.Context`, а не проверяться
|
||||||
|
только командой `ping`;
|
||||||
|
- чип AD9361;
|
||||||
|
- используется первый передающий канал.
|
||||||
|
|
||||||
|
Конкретный рабочий URI IIO определяется повторным опросом перед опытом и
|
||||||
|
сохраняется в метаданных запуска. Исторически работали сетевой URI
|
||||||
|
`ip:192.168.2.1` и прямой USB IIO, но ни один из них нельзя считать
|
||||||
|
доступным без текущей проверки.
|
||||||
|
|
||||||
|
### 3.2. Приёмник
|
||||||
|
|
||||||
|
- USB ID `VID_0BDA&PID_2838`;
|
||||||
|
- имя библиотеки: `Generic RTL2832U OEM`;
|
||||||
|
- тюнер, сообщённый библиотекой: Fitipower FC0013;
|
||||||
|
- основной интерфейс `MI_00` использует WinUSB от libwdi;
|
||||||
|
- устройство открыто из Python через `pyrtlsdr` и `librtlsdr`;
|
||||||
|
- установка 435 МГц подтверждена чтением значения обратно;
|
||||||
|
- частота дискретизации 2 400 000 отсчётов/с работает;
|
||||||
|
- получены комплексные отсчёты;
|
||||||
|
- доступные аппаратные усиления находятся в диапазоне от -9,9 до
|
||||||
|
19,7 дБ и задаются дискретными ступенями устройства.
|
||||||
|
|
||||||
|
Ошибка второго USB-интерфейса не считается отказом SDR. Основной
|
||||||
|
интерфейс `MI_00`, через который работает `librtlsdr`, исправен.
|
||||||
|
|
||||||
|
### 3.3. Зафиксированное программное окружение
|
||||||
|
|
||||||
|
- Python 3.13.1 x64;
|
||||||
|
- `pyrtlsdr` 0.5.0;
|
||||||
|
- `pyrtlsdrlib` 0.0.5;
|
||||||
|
- поставляемая пакетом нативная `librtlsdr` v0.9.0;
|
||||||
|
- `pyadi-iio` и `pylibiio` для Pluto+.
|
||||||
|
|
||||||
|
Принятый способ доступа к RTL-SDR: API `pyrtlsdr` с поставляемой
|
||||||
|
`pyrtlsdrlib` нативной библиотекой. Малый собственный DLL-адаптер не
|
||||||
|
нужен. Реализация должна проверять версии и выдавать понятную ошибку, если
|
||||||
|
библиотека или устройство недоступны. Пользовательский путь к DLL
|
||||||
|
зашивать в код запрещено.
|
||||||
|
|
||||||
|
## 4. Физическая схема и безопасность
|
||||||
|
|
||||||
|
Схема:
|
||||||
|
|
||||||
|
`Pluto+ TX -> SMA-кабель -> AT30S 30 дБ -> переходник/кабель -> RTL-SDR RX`
|
||||||
|
|
||||||
|
Обязательные условия перед каждым включением TX:
|
||||||
|
|
||||||
|
1. Антенны Pluto+ и RTL-SDR сняты.
|
||||||
|
2. AT30S включён последовательно в тракт, а не подключён к свободному
|
||||||
|
порту.
|
||||||
|
3. Все разъёмы затянуты, кабель не отсоединяется при включённом TX.
|
||||||
|
4. Пользователь отдельно подтверждает эту физическую схему.
|
||||||
|
5. Первый заранее определённый рабочий режим: TX -30 дБ, RTL-SDR
|
||||||
|
19,7 дБ, несущая 435 МГц, номинально 2,4 Мвыб/с на обоих устройствах.
|
||||||
|
6. Автоматический перебор усилений, частот, порогов и способов
|
||||||
|
синхронизации запрещён.
|
||||||
|
7. Если первый режим не проходит, программа сохраняет исходные данные и
|
||||||
|
диагностику и останавливается. Следующий прогон разрешён только после
|
||||||
|
разбора причины и с изменением ровно одного заранее названного
|
||||||
|
параметра.
|
||||||
|
|
||||||
|
При TX -30 дБ расчётный уровень после AT30S составляет около -53 дБм.
|
||||||
|
Это значение следует из бюджета Lab042: около +7 дБм при TX 0 дБ,
|
||||||
|
минус 30 дБ настройки TX и минус 30 дБ аттенюатора. Расчёт не заменяет
|
||||||
|
проверку наличия аттенюатора и правильности схемы.
|
||||||
|
|
||||||
|
## 5. Номинальные параметры сигнала и обязательное чтение обратно
|
||||||
|
|
||||||
|
| Параметр | Запрошенное значение | Происхождение |
|
||||||
|
|---|---:|---|
|
||||||
|
| Несущая TX и RX | 435 МГц | Кабельная рабочая точка Lab042 |
|
||||||
|
| Символьная скорость | 20 000 симв/с | Lab018-Lab023 и Lab042 |
|
||||||
|
| Частота дискретизации TX | 2 400 000 отсчётов/с | Поддерживается Pluto+ и проверена на RTL-SDR |
|
||||||
|
| Частота дискретизации RX | 2 400 000 отсчётов/с | Проверена непосредственным опросом RTL-SDR |
|
||||||
|
| Номинальное число отсчётов на символ | 120 | 2 400 000 / 20 000 |
|
||||||
|
| Полоса TX | 200 кГц | Минимальная полоса AD9361, использованная в Lab042 |
|
||||||
|
| Размер данных фрагмента | 512 байт | Lab042 |
|
||||||
|
| Модуляция | BPSK | Lab018-Lab023 и Lab042 |
|
||||||
|
|
||||||
|
Одинаковое номинальное значение 2,4 Мвыб/с выбрано специально. Lab043
|
||||||
|
измеряет физическую ошибку генераторов, а не искусственно созданную
|
||||||
|
разницу номинальных частот дискретизации.
|
||||||
|
|
||||||
|
Перед аппаратным опытом программа обязана установить и прочитать обратно:
|
||||||
|
|
||||||
|
- частоту дискретизации TX Pluto+;
|
||||||
|
- частоту дискретизации RX RTL-SDR;
|
||||||
|
- центральную частоту TX Pluto+;
|
||||||
|
- центральную частоту RX RTL-SDR;
|
||||||
|
- усиление TX Pluto+;
|
||||||
|
- фактически выбранную ступень усиления RTL-SDR.
|
||||||
|
|
||||||
|
Для каждого параметра в отчёте и метаданных хранятся отдельные поля
|
||||||
|
`requested` и `actual`. Если API не предоставляет независимого чтения
|
||||||
|
обратно, поле `actual` имеет значение `N/A`, а не копию запрошенного
|
||||||
|
значения. Передача не начинается, если частота дискретизации или несущая
|
||||||
|
не прочитаны обратно либо отличаются от запрошенных больше, чем допускает
|
||||||
|
API устройства и заранее установленная проверка.
|
||||||
|
|
||||||
|
## 6. Двухтоновая калибровка
|
||||||
|
|
||||||
|
Перед BPSK Pluto+ передаёт два комплексных тона, симметричных относительно
|
||||||
|
несущей:
|
||||||
|
|
||||||
|
`-Fcal` и `+Fcal`, где `Fcal = 50 кГц`.
|
||||||
|
|
||||||
|
50 кГц выбраны до измерения: оба тона находятся внутри полосы 200 кГц,
|
||||||
|
не сливаются около нуля и дают разнос 100 кГц для оценки масштаба частот.
|
||||||
|
RTL-SDR измеряет положения двух пиков относительно своей настроенной
|
||||||
|
несущей: `f_low` и `f_high`.
|
||||||
|
|
||||||
|
### 6.1. Рассогласование несущей
|
||||||
|
|
||||||
|
`carrier_offset_hz = (f_low + f_high) / 2`
|
||||||
|
|
||||||
|
`carrier_offset_ppm = carrier_offset_hz / 435000000 * 1e6`
|
||||||
|
|
||||||
|
Положительный результат означает, что принятый спектр сдвинут вверх по
|
||||||
|
частоте. Для компенсации сырые комплексные отсчёты умножаются на:
|
||||||
|
|
||||||
|
`exp(-j * 2*pi*carrier_offset_hz*n/Fs_rx_actual)`
|
||||||
|
|
||||||
|
Знак поправки проверяется независимо: после компенсации среднее положение
|
||||||
|
двух тонов должно стать ближе к нулю. Рассогласование несущей не должно
|
||||||
|
подменяться оценкой ошибки частоты дискретизации.
|
||||||
|
|
||||||
|
### 6.2. Относительная ошибка частот дискретизации
|
||||||
|
|
||||||
|
`clock_scale = (f_high - f_low) / (2*Fcal)`
|
||||||
|
|
||||||
|
`sample_clock_error_ppm = (clock_scale - 1) * 1e6`
|
||||||
|
|
||||||
|
Здесь положительная ошибка означает, что масштаб передающего такта больше
|
||||||
|
масштаба приёмного. Для перехода принятых отсчётов на временную сетку
|
||||||
|
передатчика ожидаемая длина после передискретизации равна:
|
||||||
|
|
||||||
|
`len_corrected = round(len_received * clock_scale)`
|
||||||
|
|
||||||
|
Направление этой операции не принимается на веру. Оно подтверждается
|
||||||
|
машинными тестами из раздела 8. После передискретизации накопленная ошибка
|
||||||
|
символьного времени должна уменьшаться.
|
||||||
|
|
||||||
|
### 6.3. Начальная фаза выбора отсчёта символа
|
||||||
|
|
||||||
|
После грубой CFO-коррекции и передискретизации приёмник отдельно ищет
|
||||||
|
начальную фазу выбора отсчёта в диапазоне от 0 до 119 номинальных
|
||||||
|
отсчётов на символ. Это не CFO и не ошибка такта. В результатах отдельно
|
||||||
|
хранятся:
|
||||||
|
|
||||||
|
- `carrier_offset_hz` и `carrier_offset_ppm`;
|
||||||
|
- `clock_scale` и `sample_clock_error_ppm`;
|
||||||
|
- `symbol_sample_phase` и остаточная ошибка символьного времени.
|
||||||
|
|
||||||
|
### 6.4. Формальное определение шумового фона
|
||||||
|
|
||||||
|
Шумовой фон и превышение пиков вычисляются одинаковым детерминированным
|
||||||
|
алгоритмом:
|
||||||
|
|
||||||
|
1. Из комплексных отсчётов вычитается среднее.
|
||||||
|
2. Оценивается спектральная плотность мощности методом Welch с окном Hann,
|
||||||
|
`nfft = nperseg = 65536` и перекрытием 50 процентов.
|
||||||
|
3. Разрешение одного БПФ вычисляется по прочитанной обратно частоте как
|
||||||
|
`delta_f = Fs_rx_actual / 65536`. При 2,4 Мвыб/с оно равно
|
||||||
|
`36,62109375 Гц`.
|
||||||
|
4. Анализируется полоса от -100 до +100 кГц относительно настроенной
|
||||||
|
несущей.
|
||||||
|
5. Нижний пик ищется в диапазоне от -80 до -20 кГц, верхний от +20 до
|
||||||
|
+80 кГц. Эти непересекающиеся окна заданы до измерения.
|
||||||
|
6. После нахождения кандидатов ожидаемых тонов из оценки шума исключаются
|
||||||
|
защитные зоны шириной +/-2 кГц вокруг измеренного положения каждого
|
||||||
|
кандидата. Каждая полузона занимает около 55 разрешающих элементов БПФ
|
||||||
|
при 2,4 Мвыб/с, поэтому утечка основного лепестка и ближайших боковых
|
||||||
|
лепестков тона не попадает в медиану шума.
|
||||||
|
7. Шумовая мощность `P_noise` равна медиане линейных значений мощности
|
||||||
|
всех оставшихся спектральных элементов анализируемой полосы.
|
||||||
|
8. Для каждого тона вычисляется
|
||||||
|
`peak_excess_db = 10*log10(P_peak/P_noise)`.
|
||||||
|
|
||||||
|
Оба пика должны превышать шумовой фон не менее чем на 10 дБ. Порог 10 дБ,
|
||||||
|
полоса анализа, окна поиска и защитные зоны зафиксированы до аппаратного
|
||||||
|
измерения. Если нет двух конечных пиков, недостаточно элементов шума или
|
||||||
|
хотя бы один пик не проходит порог, оценка этого захвата недействительна.
|
||||||
|
|
||||||
|
### 6.5. Повторы и представление недействительных оценок
|
||||||
|
|
||||||
|
Без изменения схемы выполняются три отдельных калибровочных захвата. Для
|
||||||
|
каждого захвата отдельно сохраняются:
|
||||||
|
|
||||||
|
- `f_low_hz` и `f_high_hz`;
|
||||||
|
- `low_peak_excess_db` и `high_peak_excess_db`;
|
||||||
|
- `carrier_offset_hz` и `carrier_offset_ppm`;
|
||||||
|
- `clock_scale` и `sample_clock_error_ppm`;
|
||||||
|
- остаточная CFO после коррекции;
|
||||||
|
- остаточная ошибка разноса тонов в герцах и ppm;
|
||||||
|
- признак достоверности и причина отказа.
|
||||||
|
|
||||||
|
По трём захватам вычисляются среднее, минимум, максимум и стандартное
|
||||||
|
отклонение каждой конечной оценки, а также число недействительных
|
||||||
|
захватов. Стандартное отклонение считается по совокупности всех конечных
|
||||||
|
оценок с `ddof=0`. Недействительное значение записывается как `NaN` в CSV
|
||||||
|
и как `N/A` в текстовом отчёте. Оно никогда не заменяется нулём. Если
|
||||||
|
конечных значений нет, все агрегаты этой величины равны `N/A`.
|
||||||
|
|
||||||
|
## 7. Диагностическая последовательность передачи
|
||||||
|
|
||||||
|
Аппаратная часть выполняется по ступеням. Переход к следующей ступени
|
||||||
|
разрешён только после сохранения результатов предыдущей:
|
||||||
|
|
||||||
|
1. двухтоновые калибровочные захваты;
|
||||||
|
2. известная короткая BPSK-последовательность;
|
||||||
|
3. символьная синхронизация и измерение BER известной последовательности;
|
||||||
|
4. один пакет с CRC;
|
||||||
|
5. десять пакетированных фрагментов JPEG;
|
||||||
|
6. сборка JPEG;
|
||||||
|
7. проверка размера и SHA-256.
|
||||||
|
|
||||||
|
Известная последовательность задаётся до измерения как PRBS11 длиной
|
||||||
|
2047 бит с полиномом `x^11 + x^9 + 1` и фиксированным ненулевым начальным
|
||||||
|
состоянием, записанным в метаданные. BER вычисляется прямым сравнением
|
||||||
|
каждого принятого бита с ожидаемым до упаковки в пакет и до проверки CRC.
|
||||||
|
CRC не может использоваться вместо измерения BER.
|
||||||
|
|
||||||
|
### 7.1. Контрольный опыт PRBS11 после проверки непрерывного async-приёма
|
||||||
|
|
||||||
|
Короткая запись S и длинная запись L являются двумя отдельными аппаратными
|
||||||
|
захватами. Каждый захват выполняется одним непрерывным
|
||||||
|
`rtlsdr_read_async()` и содержит в этой последовательности: входной запас,
|
||||||
|
двухтоновую калибровку, фиксированный защитный участок 0,10 с, один
|
||||||
|
начальный маркер, непрерывную PRBS11 и выходной запас. Полезная PRBS11
|
||||||
|
длится 0,25 с в S и не менее 2,0 с в L. Внутри полезной PRBS11 нет
|
||||||
|
периодической повторной синхронизации.
|
||||||
|
|
||||||
|
Оценки CFO и масштаба частоты дискретизации для обработки PRBS11 берутся
|
||||||
|
только из двух тонов в той же самой IQ-записи. Оценку из предыдущего
|
||||||
|
двухтонового опыта или из другой записи применять запрещено. Если тоны в
|
||||||
|
текущем захвате недостоверны, PRBS11 этой записи не интерпретируется.
|
||||||
|
|
||||||
|
Для обоих захватов заранее фиксируются 435 МГц, номинальные 2 400 000
|
||||||
|
отсчётов/с TX и RX, 20 000 символов/с, TX -30 дБ, запрос усиления RTL-SDR
|
||||||
|
19,7 дБ и `Fcal = 50 кГц`. Асинхронный приём использует callback 262144
|
||||||
|
байта, 15 буферов и отбрасывает первый callback как разогревочный без
|
||||||
|
перезапуска сеанса. Автоматический подбор параметров запрещён.
|
||||||
|
|
||||||
|
Одна и та же IQ-запись обрабатывается режимами A, B и C. В B и C грубая
|
||||||
|
CFO берётся из тонов этой записи; в C дополнительно применяется
|
||||||
|
`clock_scale` этих же тонов. Разрешённая защищённая тонкая CFO-коррекция не
|
||||||
|
заменяет двухтоновое измерение. Для длинной записи SRO дополнительно
|
||||||
|
оценивается независимо по накопленному дрейфу символьной фазы.
|
||||||
|
|
||||||
|
## 8. Обязательные программные проверки до аппаратного опыта
|
||||||
|
|
||||||
|
На синтетических комплексных данных проверяются четыре ошибки частоты
|
||||||
|
дискретизации: +20, -20, +100 и -100 ppm. Для каждого случая тест обязан
|
||||||
|
проверить:
|
||||||
|
|
||||||
|
- знак оценки `sample_clock_error_ppm`;
|
||||||
|
- величину оценки в заранее заданном допуске;
|
||||||
|
- направление изменения длины при передискретизации;
|
||||||
|
- уменьшение остаточной накопленной ошибки символьного времени после
|
||||||
|
поправки.
|
||||||
|
|
||||||
|
Для положительной ошибки длина должна изменяться в направлении,
|
||||||
|
предписанном формулой `round(N * clock_scale)`, для отрицательной в
|
||||||
|
противоположном. Тест должен упасть, если вместо `clock_scale` применена
|
||||||
|
обратная величина. Допуск оценки и критерий уменьшения остаточной ошибки
|
||||||
|
задаются в тесте до аппаратного измерения и печатаются в его результате.
|
||||||
|
|
||||||
|
Дополнительно синтетические тесты проверяют:
|
||||||
|
|
||||||
|
- знак грубой CFO-коррекции для положительной и отрицательной CFO;
|
||||||
|
- уменьшение остаточной CFO после поправки;
|
||||||
|
- отдельное восстановление начальной фазы выбора отсчёта символа;
|
||||||
|
- явный `N/A`, если два тона не найдены достоверно;
|
||||||
|
- формулу шумового фона и порог 10 дБ;
|
||||||
|
- BER известной последовательности до CRC;
|
||||||
|
- отсутствие ложного успеха при неполном пакете или изображении;
|
||||||
|
- формирование кода возврата только по полному набору критериев.
|
||||||
|
|
||||||
|
До прохождения этих проверок передатчик включать нельзя.
|
||||||
|
|
||||||
|
## 9. Порядок аппаратного эксперимента
|
||||||
|
|
||||||
|
1. Открыть Pluto+ успешным `iio.Context` и записать URI и идентификаторы.
|
||||||
|
2. Открыть RTL-SDR через `pyrtlsdr` и записать имя устройства и тюнера.
|
||||||
|
3. Установить и прочитать обратно параметры из раздела 5.
|
||||||
|
4. Получить отдельное подтверждение пользователя, что кабельная схема из
|
||||||
|
раздела 4 собрана и AT30S находится в разрыве.
|
||||||
|
5. Запустить один непрерывный приём RTL-SDR за 0,5 с до передачи Pluto+.
|
||||||
|
6. Выполнить три отдельных двухтоновых калибровочных захвата.
|
||||||
|
7. Сохранить оценки каждого захвата, агрегаты и не менее одного
|
||||||
|
эталонного сырого IQ-захвата.
|
||||||
|
8. Передать известную BPSK-последовательность и измерить BER.
|
||||||
|
9. Принять один пакет и проверить CRC.
|
||||||
|
10. Передать десять фрагментов JPEG и проверить критерии раздела 11.
|
||||||
|
|
||||||
|
Если любая ступень не проходит, переход к следующей запрещён. Исходные
|
||||||
|
данные и отрицательный результат сохраняются без подгонки. Изменение
|
||||||
|
одного параметра для следующего запуска оформляется как отдельное заранее
|
||||||
|
объяснённое действие.
|
||||||
|
|
||||||
|
Весь опыт принимается RTL-SDR как одна непрерывная временная
|
||||||
|
последовательность. Продолжительность приёма вычисляется из фактически
|
||||||
|
сформированной передаваемой последовательности:
|
||||||
|
|
||||||
|
`T_rx = 0,5 с + N_tx / Fs_tx_actual + 0,5 с`.
|
||||||
|
|
||||||
|
Здесь `N_tx` является фактическим числом комплексных отсчётов всей
|
||||||
|
сформированной TX-последовательности. Общая длительность опыта не задаётся
|
||||||
|
произвольной константой. Поток разрешено читать блоками, например около
|
||||||
|
262144 отсчётов, но блоки объединяются без пропусков и перестановок в одну
|
||||||
|
последовательность. Размер блока чтения является параметром реализации, а
|
||||||
|
не физическим защитным интервалом. Десять отдельных запусков приёмника для
|
||||||
|
десяти кадров запрещены, потому что они уничтожили бы наблюдаемое
|
||||||
|
накопление ошибки тактов.
|
||||||
|
|
||||||
|
Между BPSK-кадрами не добавляются новые интервалы. Используется структура
|
||||||
|
Lab042: `GUARD_SYMBOL_COUNT` импортируется из её текущей конфигурации без
|
||||||
|
копии числовой константы в Lab043, а формирование кадра повторяет ту же
|
||||||
|
последовательность переднего интервала, символов, заднего интервала и RRC.
|
||||||
|
Готовая функция Lab042 жёстко связана с 128 отсчётами на символ, поэтому
|
||||||
|
при 120 отсчётах Lab043 переиспользуются её параметры, а не скрытая копия
|
||||||
|
её глобальной частоты дискретизации. Если в дальнейшем отдельный интервал
|
||||||
|
исчезнет из Lab042, Lab043 не создаёт новый без отдельного диагностического
|
||||||
|
решения.
|
||||||
|
|
||||||
|
## 10. Три режима обработки одной записи
|
||||||
|
|
||||||
|
Одна и та же сохранённая BPSK/IQ-запись обрабатывается тремя режимами:
|
||||||
|
|
||||||
|
- режим A: без грубой CFO-коррекции и без коррекции частоты
|
||||||
|
дискретизации;
|
||||||
|
- режим B: только грубая CFO-коррекция из двухтоновой калибровки;
|
||||||
|
- режим C: грубая CFO-коррекция, коррекция частоты дискретизации и
|
||||||
|
защищённая тонкая CFO-коррекция.
|
||||||
|
|
||||||
|
Во всех режимах начальная фаза выбора отсчёта символа оценивается и
|
||||||
|
записывается отдельно. Для каждого режима сохраняются BER известной
|
||||||
|
последовательности, остаточная CFO, остаточная временная ошибка,
|
||||||
|
корреляция маркера, число найденных кадров, число разобранных заголовков и
|
||||||
|
CRC пакетов.
|
||||||
|
|
||||||
|
В режиме C защищённая функция `should_apply_cfo_correction` из
|
||||||
|
`protocol/bpsk_radio.py` остаётся тонкой коррекцией после грубого
|
||||||
|
измерения. Она не используется как единственный способ поиска сдвига в
|
||||||
|
десятки килогерц, поскольку символьная оценка неоднозначна за пределами
|
||||||
|
половины символьной скорости.
|
||||||
|
|
||||||
|
Провал режимов A или B не считается провалом лабораторной. Они являются
|
||||||
|
контрольными измерениями. Критерии передачи применяются к режиму C.
|
||||||
|
|
||||||
|
## 11. Критерии приёмки
|
||||||
|
|
||||||
|
### 11.1. Радиокритерии режима C
|
||||||
|
|
||||||
|
- найдены все 10 кадров;
|
||||||
|
- разобраны все 10 пакетов;
|
||||||
|
- CRC32 сошлась у всех 10 пакетов.
|
||||||
|
|
||||||
|
### 11.2. Прикладные критерии режима C
|
||||||
|
|
||||||
|
- восстановлены все 10 уникальных фрагментов;
|
||||||
|
- JPEG собран без заполнения отсутствующих данных;
|
||||||
|
- размер принятого JPEG совпадает с размером переданного;
|
||||||
|
- SHA-256 принятого JPEG совпадает с SHA-256 переданного.
|
||||||
|
|
||||||
|
### 11.3. Общий результат
|
||||||
|
|
||||||
|
Код возврата 0 разрешён только при одновременном выполнении:
|
||||||
|
|
||||||
|
- всех радиокритериев;
|
||||||
|
- всех прикладных критериев;
|
||||||
|
- всех обязательных программных проверок раздела 8.
|
||||||
|
|
||||||
|
Для подтверждения воспроизводимости заранее выбранная рабочая точка
|
||||||
|
запускается три раза подряд без изменения схемы и параметров. Каждый
|
||||||
|
повтор сохраняется отдельно. Три повтора являются проверкой
|
||||||
|
воспроизводимости, а не статистической оценкой вероятности отказа.
|
||||||
|
|
||||||
|
Отрицательный результат допустим и сохраняется полностью. Запрещено
|
||||||
|
автоматически поднимать усиление, менять частоту, пороги или алгоритм
|
||||||
|
синхронизации после неудачного прогона.
|
||||||
|
|
||||||
|
## 12. Измерения и артефакты
|
||||||
|
|
||||||
|
### 12.1. Измерения передачи
|
||||||
|
|
||||||
|
Для каждого запуска и каждого режима обработки сохраняются:
|
||||||
|
|
||||||
|
- запрошенные и фактические усиления, несущие и частоты дискретизации;
|
||||||
|
- число принятых отсчётов;
|
||||||
|
- отдельные оценки CFO, ошибки частоты дискретизации и начальной
|
||||||
|
символьной фазы;
|
||||||
|
- BER известной последовательности до CRC;
|
||||||
|
- число найденных кадров и разобранных заголовков;
|
||||||
|
- CRC каждого пакета;
|
||||||
|
- решение защищённой тонкой CFO-коррекции и его диагностические величины;
|
||||||
|
- корреляция маркера и корректно определённая EVM;
|
||||||
|
- число и номера восстановленных фрагментов;
|
||||||
|
- размер и SHA-256 переданного и принятого JPEG;
|
||||||
|
- длительность сигнала и полезная скорость.
|
||||||
|
|
||||||
|
Каждое число вычисляется из текущего запуска. Перенесённые константы
|
||||||
|
снабжаются ссылкой на лабораторную-источник. Невычислимая величина
|
||||||
|
записывается как `NaN` или `N/A`, но не как правдоподобный ноль.
|
||||||
|
|
||||||
|
### 12.2. План программных артефактов
|
||||||
|
|
||||||
|
Предполагаемые файлы после отдельного согласования реализации:
|
||||||
|
|
||||||
|
- `experiments/lab043_pluto_to_rtlsdr.py`;
|
||||||
|
- `tests/test_lab043_calibration.py`;
|
||||||
|
- `data/processed/lab043/lab043_calibration.csv`;
|
||||||
|
- `data/processed/lab043/lab043_frames.csv`;
|
||||||
|
- `data/processed/lab043/lab043_summary.csv`;
|
||||||
|
- `data/processed/lab043/lab043_report.txt`;
|
||||||
|
- графики калибровочного спектра, остаточной ошибки и доставки
|
||||||
|
фрагментов.
|
||||||
|
|
||||||
|
### 12.3. Эталонный сырой IQ-захват
|
||||||
|
|
||||||
|
Не менее одного полного эталонного захвата до CFO- и SRO-коррекций
|
||||||
|
сохраняется в `data/raw/lab043/` и не добавляется в Git.
|
||||||
|
|
||||||
|
Формат отсчётов: NumPy `.npy`, одномерный массив `complex64` в порядке
|
||||||
|
приёма. Нормирование исходных 8-битных I/Q отсчётов и порядок I/Q должны
|
||||||
|
быть однозначно описаны в метаданных. Рядом сохраняется UTF-8 JSON с тем
|
||||||
|
же базовым именем и полями:
|
||||||
|
|
||||||
|
- локальная метка времени и UTC;
|
||||||
|
- идентификаторы устройств и модель тюнера;
|
||||||
|
- версии Python, `pyrtlsdr`, `pyrtlsdrlib`, `librtlsdr`, `pyadi-iio` и
|
||||||
|
`pylibiio`;
|
||||||
|
- запрошенные и фактические частоты дискретизации, несущие и усиления;
|
||||||
|
- число отсчётов, длительность, dtype и порядок байтов;
|
||||||
|
- идентификатор и SHA-256 переданной формы сигнала;
|
||||||
|
- результаты калибровки, применённые к этой записи;
|
||||||
|
- SHA-256 файла `.npy`.
|
||||||
|
|
||||||
|
IQ-файл содержит всю единую последовательность приёма. Ожидаемое число
|
||||||
|
отсчётов вычисляется как `ceil(T_rx * Fs_rx_actual)`. Чтение выполняется
|
||||||
|
последовательными блоками до достижения этого числа, последний блок
|
||||||
|
обрезается только после получения требуемого количества отсчётов.
|
||||||
|
|
||||||
|
### 12.4. Разделение общего и лабораторного кода
|
||||||
|
|
||||||
|
Общие математические примитивы обнаружения двух известных тонов, оценки
|
||||||
|
грубой CFO и относительной ошибки частот дискретизации, грубой частотной
|
||||||
|
коррекции и передискретизации размещаются в `protocol/bpsk_radio.py` либо
|
||||||
|
в другом подходящем существующем модуле `protocol/`. Каждый такой перенос
|
||||||
|
сопровождается быстрым синтетическим тестом.
|
||||||
|
|
||||||
|
В `experiments/lab043_pluto_to_rtlsdr.py` остаются сценарий лабораторной,
|
||||||
|
конкретное `Fcal`, три калибровочных захвата, методика оценки шума Lab043,
|
||||||
|
режимы A/B/C, PRBS11, контрольный пакет, JPEG, отчётные артефакты,
|
||||||
|
критерии приёмки и аппаратная последовательность. Общий код не зависит от
|
||||||
|
конкретного JPEG и номера лабораторной.
|
||||||
|
|
||||||
|
## 13. Точка остановки перед аппаратным опытом
|
||||||
|
|
||||||
|
Программная реализация и синтетические проверки разрешены. Перед
|
||||||
|
аппаратным опытом необходимо отдельно:
|
||||||
|
|
||||||
|
1. пройти все обязательные программные проверки раздела 8;
|
||||||
|
2. повторно проверить доступность устройств и чтение параметров обратно;
|
||||||
|
3. получить подтверждение пользователя о схеме и отдельное разрешение на
|
||||||
|
включение TX.
|
||||||
|
|
||||||
|
Способ Python-доступа к RTL-SDR уже выбран и не является вопросом точки
|
||||||
|
остановки.
|
||||||
|
|
||||||
|
До отдельного разрешения передатчик не включается, усиление Pluto+ не
|
||||||
|
изменяется, аппаратная калибровка и передача тонов, BPSK или JPEG не
|
||||||
|
выполняются. Операции `git add`, `commit`, `push`, `pull` и `fetch` также
|
||||||
|
не выполняются.
|
||||||
0
experiments/__init__.py
Normal file
0
experiments/__init__.py
Normal file
@@ -27,7 +27,7 @@ from protocol.video_packet import (
|
|||||||
PacketCRCError,
|
PacketCRCError,
|
||||||
VideoPacketError,
|
VideoPacketError,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
@@ -32,14 +32,14 @@ from protocol.video_packet import (
|
|||||||
ObjectType,
|
ObjectType,
|
||||||
decode_packet,
|
decode_packet,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
load_video_profile,
|
load_video_profile,
|
||||||
)
|
)
|
||||||
from tests.lab029_packet_channel_simulation import (
|
from experiments.lab029_packet_channel_simulation import (
|
||||||
PAYLOAD_SIZES,
|
PAYLOAD_SIZES,
|
||||||
PreparedProfile,
|
PreparedProfile,
|
||||||
prepare_profiles,
|
prepare_profiles,
|
||||||
@@ -44,18 +44,18 @@ from protocol.video_packet import (
|
|||||||
ObjectType,
|
ObjectType,
|
||||||
decode_packet as decode_inner_packet,
|
decode_packet as decode_inner_packet,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
load_video_profile,
|
load_video_profile,
|
||||||
)
|
)
|
||||||
from tests.lab029_packet_channel_simulation import (
|
from experiments.lab029_packet_channel_simulation import (
|
||||||
PreparedProfile,
|
PreparedProfile,
|
||||||
prepare_profiles,
|
prepare_profiles,
|
||||||
)
|
)
|
||||||
from tests.lab029b_time_based_burst_simulation import (
|
from experiments.lab029b_time_based_burst_simulation import (
|
||||||
BAD_TIME_FRACTION,
|
BAD_TIME_FRACTION,
|
||||||
CONTROL_STREAM_BITRATE_BPS,
|
CONTROL_STREAM_BITRATE_BPS,
|
||||||
CONTROL_STREAM_BITRATE_KBPS,
|
CONTROL_STREAM_BITRATE_KBPS,
|
||||||
@@ -36,18 +36,18 @@ from protocol.video_packet import (
|
|||||||
CompositeReassembler,
|
CompositeReassembler,
|
||||||
decode_packet as decode_inner_packet,
|
decode_packet as decode_inner_packet,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
load_video_profile,
|
load_video_profile,
|
||||||
)
|
)
|
||||||
from tests.lab029_packet_channel_simulation import (
|
from experiments.lab029_packet_channel_simulation import (
|
||||||
PreparedProfile,
|
PreparedProfile,
|
||||||
prepare_profiles,
|
prepare_profiles,
|
||||||
)
|
)
|
||||||
from tests.lab029b_time_based_burst_simulation import (
|
from experiments.lab029b_time_based_burst_simulation import (
|
||||||
BAD_TIME_FRACTION,
|
BAD_TIME_FRACTION,
|
||||||
CONTROL_STREAM_BITRATE_BPS,
|
CONTROL_STREAM_BITRATE_BPS,
|
||||||
CONTROL_STREAM_BITRATE_KBPS,
|
CONTROL_STREAM_BITRATE_KBPS,
|
||||||
@@ -55,7 +55,7 @@ from tests.lab029b_time_based_burst_simulation import (
|
|||||||
generate_bad_intervals,
|
generate_bad_intervals,
|
||||||
percentile,
|
percentile,
|
||||||
)
|
)
|
||||||
from tests.lab030_packet_erasure_fec import (
|
from experiments.lab030_packet_erasure_fec import (
|
||||||
FECBlockPlan,
|
FECBlockPlan,
|
||||||
FECMode,
|
FECMode,
|
||||||
FunctionalTestResult,
|
FunctionalTestResult,
|
||||||
@@ -34,18 +34,18 @@ from protocol.video_packet import (
|
|||||||
CompositeReassembler,
|
CompositeReassembler,
|
||||||
decode_packet as decode_inner_packet,
|
decode_packet as decode_inner_packet,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
load_video_profile,
|
load_video_profile,
|
||||||
)
|
)
|
||||||
from tests.lab029_packet_channel_simulation import (
|
from experiments.lab029_packet_channel_simulation import (
|
||||||
PreparedProfile,
|
PreparedProfile,
|
||||||
prepare_profiles,
|
prepare_profiles,
|
||||||
)
|
)
|
||||||
from tests.lab029b_time_based_burst_simulation import (
|
from experiments.lab029b_time_based_burst_simulation import (
|
||||||
BAD_TIME_FRACTION,
|
BAD_TIME_FRACTION,
|
||||||
CONTROL_STREAM_BITRATE_BPS,
|
CONTROL_STREAM_BITRATE_BPS,
|
||||||
CONTROL_STREAM_BITRATE_KBPS,
|
CONTROL_STREAM_BITRATE_KBPS,
|
||||||
@@ -53,7 +53,7 @@ from tests.lab029b_time_based_burst_simulation import (
|
|||||||
generate_bad_intervals,
|
generate_bad_intervals,
|
||||||
percentile,
|
percentile,
|
||||||
)
|
)
|
||||||
from tests.lab030_packet_erasure_fec import (
|
from experiments.lab030_packet_erasure_fec import (
|
||||||
FECBlockPlan,
|
FECBlockPlan,
|
||||||
FECMode,
|
FECMode,
|
||||||
FunctionalTestResult,
|
FunctionalTestResult,
|
||||||
@@ -47,21 +47,21 @@ from protocol.video_packet import (
|
|||||||
CompositeReassembler,
|
CompositeReassembler,
|
||||||
decode_packet as decode_inner_packet,
|
decode_packet as decode_inner_packet,
|
||||||
)
|
)
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
SOURCE_VIDEO_PATH,
|
SOURCE_VIDEO_PATH,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
load_video_profile,
|
load_video_profile,
|
||||||
)
|
)
|
||||||
from tests.lab029_packet_channel_simulation import prepare_profiles
|
from experiments.lab029_packet_channel_simulation import prepare_profiles
|
||||||
from tests.lab030_packet_erasure_fec import (
|
from experiments.lab030_packet_erasure_fec import (
|
||||||
FECBlockPlan,
|
FECBlockPlan,
|
||||||
SourcePacket,
|
SourcePacket,
|
||||||
TransmissionUnit,
|
TransmissionUnit,
|
||||||
prepare_source_packets,
|
prepare_source_packets,
|
||||||
)
|
)
|
||||||
from tests.lab032_fec_parameter_sweep import (
|
from experiments.lab032_fec_parameter_sweep import (
|
||||||
SweepMode,
|
SweepMode,
|
||||||
build_parameter_units,
|
build_parameter_units,
|
||||||
)
|
)
|
||||||
@@ -994,7 +994,7 @@ def write_report(
|
|||||||
"8. Созданные файлы и итоговый Git status",
|
"8. Созданные файлы и итоговый Git status",
|
||||||
"- protocol/link_packet.py",
|
"- protocol/link_packet.py",
|
||||||
"- protocol/priority_scheduler.py",
|
"- protocol/priority_scheduler.py",
|
||||||
"- tests/lab033_priority_channel_scheduler.py",
|
"- experiments/lab033_priority_channel_scheduler.py",
|
||||||
"- data/processed/lab033/lab033_summary.csv",
|
"- data/processed/lab033/lab033_summary.csv",
|
||||||
"- data/processed/lab033/lab033_class_metrics.csv",
|
"- data/processed/lab033/lab033_class_metrics.csv",
|
||||||
"- data/processed/lab033/lab033_report.txt",
|
"- data/processed/lab033/lab033_report.txt",
|
||||||
@@ -34,10 +34,10 @@ from protocol.video_age_policy import (
|
|||||||
schedule_with_video_age,
|
schedule_with_video_age,
|
||||||
)
|
)
|
||||||
from protocol.video_packet import CompositeReassembler, decode_packet as decode_inner_packet
|
from protocol.video_packet import CompositeReassembler, decode_packet as decode_inner_packet
|
||||||
from tests.lab028_video_packetization import COMPOSITE_FPS
|
from experiments.lab028_video_packetization import COMPOSITE_FPS
|
||||||
from tests.lab029_packet_channel_simulation import prepare_profiles
|
from experiments.lab029_packet_channel_simulation import prepare_profiles
|
||||||
from tests.lab030_packet_erasure_fec import SourcePacket, prepare_source_packets
|
from experiments.lab030_packet_erasure_fec import SourcePacket, prepare_source_packets
|
||||||
from tests.lab033_priority_channel_scheduler import (
|
from experiments.lab033_priority_channel_scheduler import (
|
||||||
CHANNEL_RATES_KBPS,
|
CHANNEL_RATES_KBPS,
|
||||||
STREAM_VIDEO,
|
STREAM_VIDEO,
|
||||||
VIDEO_PAYLOAD_SIZE,
|
VIDEO_PAYLOAD_SIZE,
|
||||||
@@ -982,7 +982,7 @@ def write_report(lab033, continuous, aligned, results, tests):
|
|||||||
])
|
])
|
||||||
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
||||||
lines.extend(["", "8. Созданные файлы"])
|
lines.extend(["", "8. Созданные файлы"])
|
||||||
lines.extend(f"- {path.as_posix()}" for path in (Path("protocol/video_age_policy.py"), Path("tests/lab034_stale_video_drop.py"), SUMMARY_CSV_PATH, VIDEO_CSV_PATH, CONTROL_CSV_PATH, REPORT_PATH, *PLOT_PATHS))
|
lines.extend(f"- {path.as_posix()}" for path in (Path("protocol/video_age_policy.py"), Path("experiments/lab034_stale_video_drop.py"), SUMMARY_CSV_PATH, VIDEO_CSV_PATH, CONTROL_CSV_PATH, REPORT_PATH, *PLOT_PATHS))
|
||||||
lines.extend([
|
lines.extend([
|
||||||
"",
|
"",
|
||||||
"9. Итоговый Git status",
|
"9. Итоговый Git status",
|
||||||
@@ -25,8 +25,8 @@ from protocol.video_frame_scheduler import (
|
|||||||
schedule_video_frames,
|
schedule_video_frames,
|
||||||
)
|
)
|
||||||
from protocol.video_packet import CompositeReassembler, decode_packet as decode_inner_packet
|
from protocol.video_packet import CompositeReassembler, decode_packet as decode_inner_packet
|
||||||
from tests.lab028_video_packetization import COMPOSITE_FPS
|
from experiments.lab028_video_packetization import COMPOSITE_FPS
|
||||||
from tests.lab034_stale_video_drop import (
|
from experiments.lab034_stale_video_drop import (
|
||||||
PolicyDefinition as Lab034Policy,
|
PolicyDefinition as Lab034Policy,
|
||||||
build_aligned_workload,
|
build_aligned_workload,
|
||||||
build_lab033_workload,
|
build_lab033_workload,
|
||||||
@@ -721,7 +721,7 @@ def write_report(aligned,results,tests):
|
|||||||
lines.extend(["","5. Интерпретация","- Реактивная Lab034 начинает кадр без гарантии завершения, затем удаляет остаток: уже переданные байты становятся бесполезными, а обновление не публикуется.","- Удаление до первого пакета исключает бесполезную передачу; обслуживание целыми кадрами гарантирует, что начатый кадр будет опубликован.","- Политика самого свежего уменьшает задержку ожидающих данных, но удаляет больше промежуточных кадров; очередь из двух кадров сохраняет больше последовательных обновлений ценой возраста.","- Прогноз полного завершения учитывает весь размер кадра и будущую периодическую высокоприоритетную нагрузку, поэтому полезнее проверки только текущего возраста.","- В модели точно известны команды 20 Гц, телеметрия 10 Гц и аварийная команда 10,0 с; неизвестные будущие дискретные события не моделируются и в реальной системе потребовали бы запаса.","- При устойчивой перегрузке невозможно одновременно сохранить все кадры, исходное JPEG-качество и малую задержку; требуется уменьшить частоту, качество или заранее пропускать кадры.","- Частота обновления, возраст изображения и длительность отсутствия нового изображения оцениваются одновременно: оптимизация одного показателя может ухудшить остальные.","","6. Допущения","- Ошибки и помехи отсутствуют; один общий абстрактный ресурс, форматы Lab028-Lab034 неизменны, активный пакет не прерывается.","- Прогноз не изменяет настоящую очередь; для допущенных кадров сохраняются только агрегированные ошибки, без подробного журнала.","- Политика автоматически не выбирается.","","7. Функциональные проверки"])
|
lines.extend(["","5. Интерпретация","- Реактивная Lab034 начинает кадр без гарантии завершения, затем удаляет остаток: уже переданные байты становятся бесполезными, а обновление не публикуется.","- Удаление до первого пакета исключает бесполезную передачу; обслуживание целыми кадрами гарантирует, что начатый кадр будет опубликован.","- Политика самого свежего уменьшает задержку ожидающих данных, но удаляет больше промежуточных кадров; очередь из двух кадров сохраняет больше последовательных обновлений ценой возраста.","- Прогноз полного завершения учитывает весь размер кадра и будущую периодическую высокоприоритетную нагрузку, поэтому полезнее проверки только текущего возраста.","- В модели точно известны команды 20 Гц, телеметрия 10 Гц и аварийная команда 10,0 с; неизвестные будущие дискретные события не моделируются и в реальной системе потребовали бы запаса.","- При устойчивой перегрузке невозможно одновременно сохранить все кадры, исходное JPEG-качество и малую задержку; требуется уменьшить частоту, качество или заранее пропускать кадры.","- Частота обновления, возраст изображения и длительность отсутствия нового изображения оцениваются одновременно: оптимизация одного показателя может ухудшить остальные.","","6. Допущения","- Ошибки и помехи отсутствуют; один общий абстрактный ресурс, форматы Lab028-Lab034 неизменны, активный пакет не прерывается.","- Прогноз не изменяет настоящую очередь; для допущенных кадров сохраняются только агрегированные ошибки, без подробного журнала.","- Политика автоматически не выбирается.","","7. Функциональные проверки"])
|
||||||
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
||||||
lines.extend(["","8. Созданные файлы"])
|
lines.extend(["","8. Созданные файлы"])
|
||||||
lines.extend(f"- {path.as_posix()}" for path in (Path("protocol/video_frame_scheduler.py"),Path("tests/lab035_video_frame_admission.py"),SUMMARY_CSV_PATH,VIDEO_CSV_PATH,CONTROL_CSV_PATH,PREDICTION_CSV_PATH,REPORT_PATH,*PLOT_PATHS))
|
lines.extend(f"- {path.as_posix()}" for path in (Path("protocol/video_frame_scheduler.py"),Path("experiments/lab035_video_frame_admission.py"),SUMMARY_CSV_PATH,VIDEO_CSV_PATH,CONTROL_CSV_PATH,PREDICTION_CSV_PATH,REPORT_PATH,*PLOT_PATHS))
|
||||||
lines.extend(["","9. Итоговый Git status","- Lab035 не добавлена в индекс и не закоммичена.","",git_status])
|
lines.extend(["","9. Итоговый Git status","- Lab035 не добавлена в индекс и не закоммичена.","",git_status])
|
||||||
REPORT_PATH.write_text("\n".join(lines)+"\n",encoding="utf-8")
|
REPORT_PATH.write_text("\n".join(lines)+"\n",encoding="utf-8")
|
||||||
|
|
||||||
@@ -31,12 +31,12 @@ from protocol.packet_erasure_fec import (
|
|||||||
from protocol.video_age_policy import AgePolicyPacket
|
from protocol.video_age_policy import AgePolicyPacket
|
||||||
from protocol.video_frame_scheduler import FramePolicy, VideoFrameGroup
|
from protocol.video_frame_scheduler import FramePolicy, VideoFrameGroup
|
||||||
from protocol.video_packet import decode_packet as decode_inner_packet
|
from protocol.video_packet import decode_packet as decode_inner_packet
|
||||||
from tests.lab028_video_packetization import (
|
from experiments.lab028_video_packetization import (
|
||||||
COMPOSITE_FPS,
|
COMPOSITE_FPS,
|
||||||
EncodedComposite,
|
EncodedComposite,
|
||||||
packets_for_composite,
|
packets_for_composite,
|
||||||
)
|
)
|
||||||
from tests.lab033_priority_channel_scheduler import (
|
from experiments.lab033_priority_channel_scheduler import (
|
||||||
STREAM_CONTROL,
|
STREAM_CONTROL,
|
||||||
STREAM_EMERGENCY,
|
STREAM_EMERGENCY,
|
||||||
STREAM_TELEMETRY,
|
STREAM_TELEMETRY,
|
||||||
@@ -44,7 +44,7 @@ from tests.lab033_priority_channel_scheduler import (
|
|||||||
build_workload as build_lab033_workload,
|
build_workload as build_lab033_workload,
|
||||||
deterministic_payload,
|
deterministic_payload,
|
||||||
)
|
)
|
||||||
from tests.lab034_stale_video_drop import parity_for_partial
|
from experiments.lab034_stale_video_drop import parity_for_partial
|
||||||
|
|
||||||
|
|
||||||
OUTPUT_DIRECTORY = Path("data/processed/lab036")
|
OUTPUT_DIRECTORY = Path("data/processed/lab036")
|
||||||
@@ -1318,7 +1318,7 @@ def write_report(
|
|||||||
f"- CRC-проверено общих пакетов: {workload.crc_packets_checked}; FEC-блоков: {workload.crc_blocks_checked}.",
|
f"- CRC-проверено общих пакетов: {workload.crc_packets_checked}; FEC-блоков: {workload.crc_blocks_checked}.",
|
||||||
"",
|
"",
|
||||||
"7. Созданные файлы",
|
"7. Созданные файлы",
|
||||||
"- tests/lab036_long_duration_scheduler.py",
|
"- experiments/lab036_long_duration_scheduler.py",
|
||||||
"- data/processed/lab036/lab036_summary.csv",
|
"- data/processed/lab036/lab036_summary.csv",
|
||||||
"- data/processed/lab036/lab036_video_metrics.csv",
|
"- data/processed/lab036/lab036_video_metrics.csv",
|
||||||
"- data/processed/lab036/lab036_control_metrics.csv",
|
"- data/processed/lab036/lab036_control_metrics.csv",
|
||||||
@@ -42,8 +42,8 @@ from protocol.packet_erasure_fec import (
|
|||||||
from protocol.video_packet import decode_packet as decode_inner_packet
|
from protocol.video_packet import decode_packet as decode_inner_packet
|
||||||
from protocol.video_frame_scheduler import VideoFrameGroup
|
from protocol.video_frame_scheduler import VideoFrameGroup
|
||||||
from protocol.video_age_policy import AgePolicyPacket
|
from protocol.video_age_policy import AgePolicyPacket
|
||||||
from tests.lab028_video_packetization import COMPOSITE_FPS, EncodedComposite, packets_for_composite
|
from experiments.lab028_video_packetization import COMPOSITE_FPS, EncodedComposite, packets_for_composite
|
||||||
from tests.lab033_priority_channel_scheduler import (
|
from experiments.lab033_priority_channel_scheduler import (
|
||||||
STREAM_CONTROL,
|
STREAM_CONTROL,
|
||||||
STREAM_EMERGENCY,
|
STREAM_EMERGENCY,
|
||||||
STREAM_TELEMETRY,
|
STREAM_TELEMETRY,
|
||||||
@@ -51,7 +51,7 @@ from tests.lab033_priority_channel_scheduler import (
|
|||||||
build_workload as build_lab033_workload,
|
build_workload as build_lab033_workload,
|
||||||
deterministic_payload,
|
deterministic_payload,
|
||||||
)
|
)
|
||||||
from tests.lab034_stale_video_drop import parity_for_partial
|
from experiments.lab034_stale_video_drop import parity_for_partial
|
||||||
|
|
||||||
|
|
||||||
OUTPUT_DIRECTORY = Path("data/processed/lab037")
|
OUTPUT_DIRECTORY = Path("data/processed/lab037")
|
||||||
@@ -1186,13 +1186,13 @@ def write_report(
|
|||||||
"",
|
"",
|
||||||
"Созданные файлы",
|
"Созданные файлы",
|
||||||
"- protocol/control_repetition.py",
|
"- protocol/control_repetition.py",
|
||||||
"- tests/lab037_lossy_full_link.py",
|
"- experiments/lab037_lossy_full_link.py",
|
||||||
*[f"- {path.as_posix()}" for path in (SUMMARY_CSV_PATH, COMMAND_CSV_PATH, VIDEO_CSV_PATH, EMERGENCY_CSV_PATH, REPORT_PATH, *PLOT_PATHS)],
|
*[f"- {path.as_posix()}" for path in (SUMMARY_CSV_PATH, COMMAND_CSV_PATH, VIDEO_CSV_PATH, EMERGENCY_CSV_PATH, REPORT_PATH, *PLOT_PATHS)],
|
||||||
"",
|
"",
|
||||||
"Итоговый git status",
|
"Итоговый git status",
|
||||||
"?? data/processed/lab037/",
|
"?? data/processed/lab037/",
|
||||||
"?? protocol/control_repetition.py",
|
"?? protocol/control_repetition.py",
|
||||||
"?? tests/lab037_lossy_full_link.py",
|
"?? experiments/lab037_lossy_full_link.py",
|
||||||
"",
|
"",
|
||||||
"Двоичные пакеты, JPEG, подробные пакетные журналы и дампы не сохранялись.",
|
"Двоичные пакеты, JPEG, подробные пакетные журналы и дампы не сохранялись.",
|
||||||
"Режим защиты автоматически не выбирается: все три режима представлены отдельно.",
|
"Режим защиты автоматически не выбирается: все три режима представлены отдельно.",
|
||||||
@@ -43,12 +43,12 @@ from protocol.packet_erasure_fec import (
|
|||||||
decode_outer_symbol,
|
decode_outer_symbol,
|
||||||
)
|
)
|
||||||
from protocol.video_packet import decode_packet as decode_inner_packet
|
from protocol.video_packet import decode_packet as decode_inner_packet
|
||||||
from tests.lab033_priority_channel_scheduler import (
|
from experiments.lab033_priority_channel_scheduler import (
|
||||||
STREAM_CONTROL,
|
STREAM_CONTROL,
|
||||||
STREAM_EMERGENCY,
|
STREAM_EMERGENCY,
|
||||||
deterministic_payload,
|
deterministic_payload,
|
||||||
)
|
)
|
||||||
from tests.lab037_lossy_full_link import (
|
from experiments.lab037_lossy_full_link import (
|
||||||
BAD_TIME_FRACTION,
|
BAD_TIME_FRACTION,
|
||||||
CHANNEL_RATES_KBPS,
|
CHANNEL_RATES_KBPS,
|
||||||
DURATION_SECONDS,
|
DURATION_SECONDS,
|
||||||
@@ -1577,7 +1577,7 @@ def write_report(
|
|||||||
created = (
|
created = (
|
||||||
Path("protocol/control_failsafe.py"),
|
Path("protocol/control_failsafe.py"),
|
||||||
Path("protocol/emergency_ack.py"),
|
Path("protocol/emergency_ack.py"),
|
||||||
Path("tests/lab038_control_failsafe.py"),
|
Path("experiments/lab038_control_failsafe.py"),
|
||||||
SUMMARY_CSV_PATH,
|
SUMMARY_CSV_PATH,
|
||||||
COMMAND_CSV_PATH,
|
COMMAND_CSV_PATH,
|
||||||
EMERGENCY_CSV_PATH,
|
EMERGENCY_CSV_PATH,
|
||||||
@@ -1596,8 +1596,8 @@ def write_report(
|
|||||||
"?? protocol/control_failsafe.py",
|
"?? protocol/control_failsafe.py",
|
||||||
"?? protocol/control_repetition.py",
|
"?? protocol/control_repetition.py",
|
||||||
"?? protocol/emergency_ack.py",
|
"?? protocol/emergency_ack.py",
|
||||||
"?? tests/lab037_lossy_full_link.py",
|
"?? experiments/lab037_lossy_full_link.py",
|
||||||
"?? tests/lab038_control_failsafe.py",
|
"?? experiments/lab038_control_failsafe.py",
|
||||||
"",
|
"",
|
||||||
"Lab038 не добавлен в Git и не закоммичен. Двоичные пакеты, JPEG, дампы и подробные пакетные журналы не сохранялись.",
|
"Lab038 не добавлен в Git и не закоммичен. Двоичные пакеты, JPEG, дампы и подробные пакетные журналы не сохранялись.",
|
||||||
]
|
]
|
||||||
@@ -24,9 +24,9 @@ from protocol.two_stage_failsafe import (
|
|||||||
forced_stop,
|
forced_stop,
|
||||||
integrate_kinematics,
|
integrate_kinematics,
|
||||||
)
|
)
|
||||||
from tests.lab033_priority_channel_scheduler import STREAM_EMERGENCY
|
from experiments.lab033_priority_channel_scheduler import STREAM_EMERGENCY
|
||||||
from tests.lab037_lossy_full_link import bad_intervals
|
from experiments.lab037_lossy_full_link import bad_intervals
|
||||||
from tests.lab038_control_failsafe import (
|
from experiments.lab038_control_failsafe import (
|
||||||
DURATION_SECONDS,
|
DURATION_SECONDS,
|
||||||
EMERGENCY_SPREAD_US,
|
EMERGENCY_SPREAD_US,
|
||||||
MASTER_SEED,
|
MASTER_SEED,
|
||||||
@@ -1776,7 +1776,7 @@ def write_report(
|
|||||||
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
lines.extend(f"- {'PASS' if item.passed else 'FAIL'} {item.name}: {item.detail}" for item in tests)
|
||||||
created = (
|
created = (
|
||||||
Path("protocol/two_stage_failsafe.py"),
|
Path("protocol/two_stage_failsafe.py"),
|
||||||
Path("tests/lab039_two_stage_braking.py"),
|
Path("experiments/lab039_two_stage_braking.py"),
|
||||||
SUMMARY_CSV_PATH,
|
SUMMARY_CSV_PATH,
|
||||||
WATCHDOG_CSV_PATH,
|
WATCHDOG_CSV_PATH,
|
||||||
DISTANCE_CSV_PATH,
|
DISTANCE_CSV_PATH,
|
||||||
@@ -37,9 +37,9 @@ from protocol.two_stage_failsafe import (
|
|||||||
TwoStageFailsafe,
|
TwoStageFailsafe,
|
||||||
integrate_kinematics,
|
integrate_kinematics,
|
||||||
)
|
)
|
||||||
from tests.lab033_priority_channel_scheduler import STREAM_CONTROL, STREAM_EMERGENCY
|
from experiments.lab033_priority_channel_scheduler import STREAM_CONTROL, STREAM_EMERGENCY
|
||||||
from tests.lab037_lossy_full_link import bad_intervals
|
from experiments.lab037_lossy_full_link import bad_intervals
|
||||||
from tests.lab038_control_failsafe import DURATION_SECONDS, Frame, Unit, Workload, build_workload, packet_is_lost
|
from experiments.lab038_control_failsafe import DURATION_SECONDS, Frame, Unit, Workload, build_workload, packet_is_lost
|
||||||
|
|
||||||
|
|
||||||
OUTPUT_DIRECTORY = Path("data/processed/lab040")
|
OUTPUT_DIRECTORY = Path("data/processed/lab040")
|
||||||
@@ -1650,7 +1650,7 @@ def write_report(
|
|||||||
created = (
|
created = (
|
||||||
Path("protocol/persistent_emergency.py"),
|
Path("protocol/persistent_emergency.py"),
|
||||||
Path("protocol/safe_reset.py"),
|
Path("protocol/safe_reset.py"),
|
||||||
Path("tests/lab040_persistent_emergency.py"),
|
Path("experiments/lab040_persistent_emergency.py"),
|
||||||
SUMMARY_CSV,
|
SUMMARY_CSV,
|
||||||
EMERGENCY_CSV,
|
EMERGENCY_CSV,
|
||||||
RESET_CSV,
|
RESET_CSV,
|
||||||
@@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
|
||||||
import csv
|
import csv
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
import inspect
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@@ -66,7 +63,6 @@ PLOT_PATHS = (
|
|||||||
OUTPUT_DIRECTORY / "lab041_emergency_intent.png",
|
OUTPUT_DIRECTORY / "lab041_emergency_intent.png",
|
||||||
OUTPUT_DIRECTORY / "lab041_safety_violations.png",
|
OUTPUT_DIRECTORY / "lab041_safety_violations.png",
|
||||||
OUTPUT_DIRECTORY / "lab041_protection_load.png",
|
OUTPUT_DIRECTORY / "lab041_protection_load.png",
|
||||||
OUTPUT_DIRECTORY / "lab041_traffic_impact.png",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
REPETITIONS = 200
|
REPETITIONS = 200
|
||||||
@@ -86,9 +82,25 @@ LAB033_HEADER_BYTES = 32
|
|||||||
LAB040_CONTROL_PAYLOAD_BYTES = 14
|
LAB040_CONTROL_PAYLOAD_BYTES = 14
|
||||||
LAB040_CONTROL_PACKET_BYTES = LAB033_HEADER_BYTES + LAB040_CONTROL_PAYLOAD_BYTES
|
LAB040_CONTROL_PACKET_BYTES = LAB033_HEADER_BYTES + LAB040_CONTROL_PAYLOAD_BYTES
|
||||||
LAB040_CONTROL_LOAD_KBPS = LAB040_CONTROL_PACKET_BYTES * CONTROL_FREQUENCY_HZ * 8.0 / 1000.0
|
LAB040_CONTROL_LOAD_KBPS = LAB040_CONTROL_PACKET_BYTES * CONTROL_FREQUENCY_HZ * 8.0 / 1000.0
|
||||||
|
|
||||||
|
# Перенесённые величины. Источник указан явно, чтобы их нельзя было принять за
|
||||||
|
# результат моделирования Lab041: оба значения совпадают с выводом Lab038.
|
||||||
|
# data/processed/lab038/lab038_summary.csv, поле video_load_kbps.
|
||||||
VIDEO_OFFERED_LOAD_KBPS = 243.39286666666666
|
VIDEO_OFFERED_LOAD_KBPS = 243.39286666666666
|
||||||
|
# data/processed/lab038/lab038_summary.csv, поле telemetry_load_kbps.
|
||||||
TELEMETRY_OFFERED_LOAD_KBPS = 7.68
|
TELEMETRY_OFFERED_LOAD_KBPS = 7.68
|
||||||
|
|
||||||
|
# Параметры торможения взяты из профиля "nominal" Lab039, а не подобраны заново.
|
||||||
|
# experiments/lab039_two_stage_braking.py: DecelerationProfile("nominal", 1.0, 3.0).
|
||||||
|
# Оценка пути ниже одноступенчатая и потому консервативнее двухступенчатой
|
||||||
|
# модели Lab039: она не учитывает предварительное замедление Stage1.
|
||||||
|
LAB039_NOMINAL_STAGE2_MPS2 = 3.0
|
||||||
|
LAB039_STAGE2_THRESHOLD_SECONDS = 0.250
|
||||||
|
# Принятое допущение: горизонт, на котором оценивается путь ровера, не
|
||||||
|
# получившего безопасного запрета. Из модели не выводится, выбран как
|
||||||
|
# заведомо большой по сравнению с временем согласования сеанса.
|
||||||
|
UNPROTECTED_RUNAWAY_SECONDS = 60.0
|
||||||
|
|
||||||
|
|
||||||
class ArchitectureKind(IntEnum):
|
class ArchitectureKind(IntEnum):
|
||||||
NO_SESSION_IDS = 1
|
NO_SESSION_IDS = 1
|
||||||
@@ -231,17 +243,11 @@ class TrialMetrics:
|
|||||||
old_reset_erroneously_accepted: int
|
old_reset_erroneously_accepted: int
|
||||||
movement_before_reset_ack: int
|
movement_before_reset_ack: int
|
||||||
automatic_motion_after_reset: int
|
automatic_motion_after_reset: int
|
||||||
negative_speed_cases: int
|
|
||||||
speed_limit_exceeded_cases: int
|
|
||||||
session_load_bytes: int
|
session_load_bytes: int
|
||||||
authorization_load_bytes: int
|
authorization_load_bytes: int
|
||||||
emergency_load_bytes: int
|
emergency_load_bytes: int
|
||||||
retry_load_bytes: int
|
retry_load_bytes: int
|
||||||
episodic_service_bytes: int
|
episodic_service_bytes: int
|
||||||
control_delivered_fraction: float
|
|
||||||
telemetry_delivered_fraction: float
|
|
||||||
video_published_fraction: float
|
|
||||||
maximum_queue_packets: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -258,10 +264,6 @@ class SummaryMetrics:
|
|||||||
control_state_load_kbps: float
|
control_state_load_kbps: float
|
||||||
episodic_service_load_kbps: float
|
episodic_service_load_kbps: float
|
||||||
full_offered_load_kbps: float
|
full_offered_load_kbps: float
|
||||||
control_delivered_fraction: float
|
|
||||||
telemetry_delivered_fraction: float
|
|
||||||
video_published_fraction: float
|
|
||||||
maximum_queue_packets: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -333,8 +335,6 @@ class RestartMetrics:
|
|||||||
old_reset_erroneously_accepted: int
|
old_reset_erroneously_accepted: int
|
||||||
movement_before_reset_ack: int
|
movement_before_reset_ack: int
|
||||||
automatic_motion_after_reset: int
|
automatic_motion_after_reset: int
|
||||||
negative_speed_cases: int
|
|
||||||
speed_limit_exceeded_cases: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -357,10 +357,6 @@ class LoadMetrics:
|
|||||||
video_offered_load_kbps: float
|
video_offered_load_kbps: float
|
||||||
telemetry_offered_load_kbps: float
|
telemetry_offered_load_kbps: float
|
||||||
full_offered_load_kbps: float
|
full_offered_load_kbps: float
|
||||||
control_delivered_fraction: float
|
|
||||||
telemetry_delivered_fraction: float
|
|
||||||
video_published_fraction: float
|
|
||||||
maximum_queue_packets: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -641,13 +637,14 @@ def simulate_trial(
|
|||||||
continued = startup_old = 0
|
continued = startup_old = 0
|
||||||
if restart_case:
|
if restart_case:
|
||||||
if architecture.safe_boot:
|
if architecture.safe_boot:
|
||||||
restart_to_safe = min(sync_time, 0.250) if scenario.number == 2 else 0.0
|
braking_distance = INITIAL_SPEED_MPS**2 / (2.0 * LAB039_NOMINAL_STAGE2_MPS2)
|
||||||
|
restart_to_safe = min(sync_time, LAB039_STAGE2_THRESHOLD_SECONDS) if scenario.number == 2 else 0.0
|
||||||
if scenario.number == 2:
|
if scenario.number == 2:
|
||||||
distance_after_restart = INITIAL_SPEED_MPS * 0.250 + INITIAL_SPEED_MPS**2 / 6.0
|
distance_after_restart = INITIAL_SPEED_MPS * LAB039_STAGE2_THRESHOLD_SECONDS + braking_distance
|
||||||
elif scenario.number in (3, 4, 6, 20):
|
elif scenario.number in (3, 4, 6, 20):
|
||||||
distance_after_restart = INITIAL_SPEED_MPS**2 / 6.0
|
distance_after_restart = braking_distance
|
||||||
else:
|
else:
|
||||||
distance_after_restart = INITIAL_SPEED_MPS * 60.0
|
distance_after_restart = INITIAL_SPEED_MPS * UNPROTECTED_RUNAWAY_SECONDS
|
||||||
continued = 1
|
continued = 1
|
||||||
startup_old = 1
|
startup_old = 1
|
||||||
|
|
||||||
@@ -686,14 +683,6 @@ def simulate_trial(
|
|||||||
emergency_transmitted_bytes = emergency_extra + reset_extra
|
emergency_transmitted_bytes = emergency_extra + reset_extra
|
||||||
emergency_initial_bytes = emergency_transmitted_bytes - emergency_retry_bytes
|
emergency_initial_bytes = emergency_transmitted_bytes - emergency_retry_bytes
|
||||||
episodic_service_bytes = session_initial_bytes + authorization_initial_bytes + emergency_initial_bytes + retry_bytes
|
episodic_service_bytes = session_initial_bytes + authorization_initial_bytes + emergency_initial_bytes + retry_bytes
|
||||||
episodic_service_load = episodic_service_bytes * 8.0 / EXPERIMENT_SECONDS / 1000.0
|
|
||||||
base_video = {"no_loss": 1.0, "260_200": 0.910, "230_1000": 0.863}[condition.name]
|
|
||||||
base_telemetry = {"no_loss": 1.0, "260_200": 0.982, "230_1000": 0.980}[condition.name]
|
|
||||||
load_penalty = (CONTROL_LOAD_INCREASE_KBPS + episodic_service_load) / condition.rate_kbps
|
|
||||||
control_fraction = max(0.0, 1.0 - BAD_FRACTION * int(condition.mean_bad_ms is not None) - load_penalty * 0.10)
|
|
||||||
telemetry_fraction = max(0.0, base_telemetry - load_penalty * 0.05)
|
|
||||||
video_fraction = max(0.0, base_video - load_penalty * 0.15)
|
|
||||||
maximum_queue = {"no_loss": 21, "260_200": 31, "230_1000": 41}[condition.name] + min(5, retries)
|
|
||||||
|
|
||||||
return TrialMetrics(
|
return TrialMetrics(
|
||||||
safe_session,
|
safe_session,
|
||||||
@@ -743,17 +732,11 @@ def simulate_trial(
|
|||||||
old_reset_accepted,
|
old_reset_accepted,
|
||||||
movement_before_reset_ack,
|
movement_before_reset_ack,
|
||||||
automatic_after_reset,
|
automatic_after_reset,
|
||||||
0,
|
|
||||||
0,
|
|
||||||
session_initial_bytes,
|
session_initial_bytes,
|
||||||
authorization_initial_bytes,
|
authorization_initial_bytes,
|
||||||
emergency_initial_bytes,
|
emergency_initial_bytes,
|
||||||
retry_bytes,
|
retry_bytes,
|
||||||
episodic_service_bytes,
|
episodic_service_bytes,
|
||||||
control_fraction,
|
|
||||||
telemetry_fraction,
|
|
||||||
video_fraction,
|
|
||||||
maximum_queue,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -777,10 +760,6 @@ def aggregate_combination(
|
|||||||
CONTROL_STATE_LOAD_KBPS,
|
CONTROL_STATE_LOAD_KBPS,
|
||||||
episodic_service_load,
|
episodic_service_load,
|
||||||
full_offered_load,
|
full_offered_load,
|
||||||
_mean(item.control_delivered_fraction for item in trials),
|
|
||||||
_mean(item.telemetry_delivered_fraction for item in trials),
|
|
||||||
_mean(item.video_published_fraction for item in trials),
|
|
||||||
max(item.maximum_queue_packets for item in trials),
|
|
||||||
)
|
)
|
||||||
session = SessionMetrics(
|
session = SessionMetrics(
|
||||||
*key,
|
*key,
|
||||||
@@ -838,8 +817,6 @@ def aggregate_combination(
|
|||||||
sum(item.old_reset_erroneously_accepted for item in trials),
|
sum(item.old_reset_erroneously_accepted for item in trials),
|
||||||
sum(item.movement_before_reset_ack for item in trials),
|
sum(item.movement_before_reset_ack for item in trials),
|
||||||
sum(item.automatic_motion_after_reset for item in trials),
|
sum(item.automatic_motion_after_reset for item in trials),
|
||||||
sum(item.negative_speed_cases for item in trials),
|
|
||||||
sum(item.speed_limit_exceeded_cases for item in trials),
|
|
||||||
)
|
)
|
||||||
load = LoadMetrics(
|
load = LoadMetrics(
|
||||||
*key,
|
*key,
|
||||||
@@ -857,10 +834,6 @@ def aggregate_combination(
|
|||||||
VIDEO_OFFERED_LOAD_KBPS,
|
VIDEO_OFFERED_LOAD_KBPS,
|
||||||
TELEMETRY_OFFERED_LOAD_KBPS,
|
TELEMETRY_OFFERED_LOAD_KBPS,
|
||||||
full_offered_load,
|
full_offered_load,
|
||||||
summary.control_delivered_fraction,
|
|
||||||
summary.telemetry_delivered_fraction,
|
|
||||||
summary.video_published_fraction,
|
|
||||||
summary.maximum_queue_packets,
|
|
||||||
)
|
)
|
||||||
return summary, session, rejection, restart, load
|
return summary, session, rejection, restart, load
|
||||||
|
|
||||||
@@ -954,6 +927,8 @@ def run_functional_tests() -> tuple[FunctionalTestResult, ...]:
|
|||||||
def control_ranges() -> str:
|
def control_ranges() -> str:
|
||||||
for message in (
|
for message in (
|
||||||
ControlStateMessage(context, 15.1, 0.0, False, True),
|
ControlStateMessage(context, 15.1, 0.0, False, True),
|
||||||
|
ControlStateMessage(context, -0.1, 0.0, False, True),
|
||||||
|
ControlStateMessage(context, -5.0, 0.0, False, True),
|
||||||
ControlStateMessage(context, 1.0, 1.1, False, True),
|
ControlStateMessage(context, 1.0, 1.1, False, True),
|
||||||
ControlStateMessage(context, 1.0, 0.0, True, False),
|
ControlStateMessage(context, 1.0, 0.0, True, False),
|
||||||
):
|
):
|
||||||
@@ -962,7 +937,9 @@ def run_functional_tests() -> tuple[FunctionalTestResult, ...]:
|
|||||||
raise AssertionError("недопустимое управление принято")
|
raise AssertionError("недопустимое управление принято")
|
||||||
except ControlMessageError:
|
except ControlMessageError:
|
||||||
pass
|
pass
|
||||||
return "диапазоны и сочетания полей управления проверены"
|
assert encode_message(ControlStateMessage(context, 15.0, 0.0, False, True))
|
||||||
|
assert encode_message(ControlStateMessage(context, 0.0, 0.0, True, False))
|
||||||
|
return "диапазоны и сочетания полей управления проверены, включая отрицательную скорость и границу 15 м/с"
|
||||||
|
|
||||||
def sequence_zero_new_session() -> str:
|
def sequence_zero_new_session() -> str:
|
||||||
ground, rover = _active_pair(1)
|
ground, rover = _active_pair(1)
|
||||||
@@ -1275,24 +1252,6 @@ def save_plots(
|
|||||||
figure.savefig(PLOT_PATHS[6], dpi=150)
|
figure.savefig(PLOT_PATHS[6], dpi=150)
|
||||||
plt.close(figure)
|
plt.close(figure)
|
||||||
|
|
||||||
figure, axes = plt.subplots(1, 2, figsize=(12, 5))
|
|
||||||
for architecture in ARCHITECTURES:
|
|
||||||
video = [_group_mean(loads, architecture.name, channel, "video_published_fraction") * 100.0 for channel in channel_names]
|
|
||||||
telemetry = [_group_mean(loads, architecture.name, channel, "telemetry_delivered_fraction") * 100.0 for channel in channel_names]
|
|
||||||
axes[0].plot(channel_labels, video, marker="o", label=architecture.label, color=colors[architecture.name])
|
|
||||||
axes[1].plot(channel_labels, telemetry, marker="o", label=architecture.label, color=colors[architecture.name])
|
|
||||||
axes[0].set_title("Опубликованные видеокадры")
|
|
||||||
axes[0].set_ylabel("Доля, %")
|
|
||||||
axes[1].set_title("Доставленная телеметрия")
|
|
||||||
axes[1].set_ylabel("Доля, %")
|
|
||||||
for axis in axes:
|
|
||||||
axis.tick_params(axis="x", rotation=15)
|
|
||||||
axis.grid(True, alpha=0.3)
|
|
||||||
axes[1].legend(fontsize=8)
|
|
||||||
figure.tight_layout()
|
|
||||||
figure.savefig(PLOT_PATHS[7], dpi=150)
|
|
||||||
plt.close(figure)
|
|
||||||
|
|
||||||
|
|
||||||
def write_report(
|
def write_report(
|
||||||
summaries: tuple[SummaryMetrics, ...],
|
summaries: tuple[SummaryMetrics, ...],
|
||||||
@@ -1365,6 +1324,10 @@ def write_report(
|
|||||||
"Ограничение модели",
|
"Ограничение модели",
|
||||||
"- Исследуются задержки, дубликаты, перестановка, старые пакеты и потеря оперативного состояния при перезапуске.",
|
"- Исследуются задержки, дубликаты, перестановка, старые пакеты и потеря оперативного состояния при перезапуске.",
|
||||||
"- Намеренная подделка не моделируется. Идентификаторы сеанса не доказывают отправителя, поэтому эта защита не заменяет криптографическую аутентификацию.",
|
"- Намеренная подделка не моделируется. Идентификаторы сеанса не доказывают отправителя, поэтому эта защита не заменяет криптографическую аутентификацию.",
|
||||||
|
"- Что именно моделируется розыгрышем: моменты потерь по экспоненциальной модели Good-Bad, доставка и повтор служебных сообщений сеанса, время до безопасного согласования и до разрешения движения, объём эпизодической служебной нагрузки. Только эти величины являются результатом моделирования Lab041.",
|
||||||
|
"- Что задано архитектурой, а не разыгрывается: исход обработки старого пакета, разрешено ли движение после перезапуска и сохраняется ли аварийное намерение. Матрица перебирает эти случаи, а поведение реализации проверяется отдельно функциональными проверками.",
|
||||||
|
"- Что не моделируется вовсе: транспорт видео, планировщик очередей, доставка телеметрии и доставка команд по радиоканалу. Соответствующие показатели относятся к Lab037 и Lab040 и в Lab041 не пересчитываются.",
|
||||||
|
"- Оценка пути после перезапуска получена по замкнутой формуле при параметрах профиля nominal Lab039 и одинакова во всех повторах; это расчёт, а не результат розыгрыша.",
|
||||||
"",
|
"",
|
||||||
"Матрица 180 сочетаний",
|
"Матрица 180 сочетаний",
|
||||||
"архитектура | канал | сценарий | повторы | безопасный сеанс, % | разрешение, % | нарушения, % | старые пакеты приняты, % | потеря намерения, % | управление, кбит/с | эпизодическая служебная, кбит/с | полный поток, кбит/с",
|
"архитектура | канал | сценарий | повторы | безопасный сеанс, % | разрешение, % | нарушения, % | старые пакеты приняты, % | потеря намерения, % | управление, кбит/с | эпизодическая служебная, кбит/с | полный поток, кбит/с",
|
||||||
@@ -1382,10 +1345,13 @@ def write_report(
|
|||||||
"Перезапуски и безопасное состояние",
|
"Перезапуски и безопасное состояние",
|
||||||
f"- В основном режиме движение до синхронизации: {sum(row.movement_before_sync for row in candidate_sessions)}; до подтверждения разрешения: {sum(row.movement_before_authorize_ack for row in candidate_sessions)}; без новой команды оператора: {sum(row.movement_without_new_operator for row in candidate_sessions)}.",
|
f"- В основном режиме движение до синхронизации: {sum(row.movement_before_sync for row in candidate_sessions)}; до подтверждения разрешения: {sum(row.movement_before_authorize_ack for row in candidate_sessions)}; без новой команды оператора: {sum(row.movement_without_new_operator for row in candidate_sessions)}.",
|
||||||
f"- Максимальный путь после перезапуска в основном режиме: {max(row.max_distance_after_restart_m for row in candidate_restarts):.4f} м; это путь безопасного торможения, а не продолжение старой команды.",
|
f"- Максимальный путь после перезапуска в основном режиме: {max(row.max_distance_after_restart_m for row in candidate_restarts):.4f} м; это путь безопасного торможения, а не продолжение старой команды.",
|
||||||
|
f"- Величина рассчитана по формуле v*t + v^2/(2a) при v = {INITIAL_SPEED_MPS:.4f} м/с, t = {LAB039_STAGE2_THRESHOLD_SECONDS:.3f} с и a = {LAB039_NOMINAL_STAGE2_MPS2:.1f} м/с^2 из профиля nominal Lab039. Оценка одноступенчатая и не учитывает предварительное замедление Stage1, поэтому она консервативнее двухступенчатой модели Lab039.",
|
||||||
f"- Продолжение движения после перезапуска: {sum(row.continued_movement_after_restart for row in candidate_restarts)}; запуск по старой команде: {sum(row.startup_from_old_command for row in candidate_restarts)}.",
|
f"- Продолжение движения после перезапуска: {sum(row.continued_movement_after_restart for row in candidate_restarts)}; запуск по старой команде: {sum(row.startup_from_old_command for row in candidate_restarts)}.",
|
||||||
"",
|
"",
|
||||||
"Пакеты предыдущего сеанса",
|
"Пакеты предыдущего сеанса",
|
||||||
f"- Основной режим: введено {sum(row.old_packets_injected for row in candidate_rejections)}, отклонено {sum(row.old_packets_rejected for row in candidate_rejections)}, ошибочно принято {sum(row.old_packets_erroneously_accepted for row in candidate_rejections)}.",
|
f"- Основной режим: введено {sum(row.old_packets_injected for row in candidate_rejections)}, отклонено {sum(row.old_packets_rejected for row in candidate_rejections)}, ошибочно принято {sum(row.old_packets_erroneously_accepted for row in candidate_rejections)}.",
|
||||||
|
"- Эти числа получены перебором таблицы сценариев: семь сценариев со старым пакетом умножаются на число повторов, а исход перебора задан архитектурой, а не розыгрышем помех. Они показывают охват перебора, а не измеренную вероятность отклонения.",
|
||||||
|
"- Что старый пакет действительно отклоняется реализацией, показывают проверки 06, 07, 08, 10 и 19: они вызывают receive_control и receive_reset настоящих автоматов и сверяют причину отказа.",
|
||||||
f"- Отрицательный уровень без идентификаторов: ошибочно принято {sum(row.old_packets_erroneously_accepted for row in baseline_rejections)} старых пакетов.",
|
f"- Отрицательный уровень без идентификаторов: ошибочно принято {sum(row.old_packets_erroneously_accepted for row in baseline_rejections)} старых пакетов.",
|
||||||
f"- Причины отказа основного режима: сеанс станции={sum(row.rejected_ground_session for row in candidate_rejections)}, запуск ровера={sum(row.rejected_rover_boot for row in candidate_rejections)}, период управления={sum(row.rejected_control_epoch for row in candidate_rejections)}, номер сообщения={sum(row.rejected_sequence for row in candidate_rejections)}, событие аварии={sum(row.rejected_emergency_event for row in candidate_rejections)}, запрос сброса={sum(row.rejected_reset_request for row in candidate_rejections)}.",
|
f"- Причины отказа основного режима: сеанс станции={sum(row.rejected_ground_session for row in candidate_rejections)}, запуск ровера={sum(row.rejected_rover_boot for row in candidate_rejections)}, период управления={sum(row.rejected_control_epoch for row in candidate_rejections)}, номер сообщения={sum(row.rejected_sequence for row in candidate_rejections)}, событие аварии={sum(row.rejected_emergency_event for row in candidate_rejections)}, запрос сброса={sum(row.rejected_reset_request for row in candidate_rejections)}.",
|
||||||
f"- Неоднозначных delta=2^31 отклонено: {sum(row.ambiguous_sequence_rejected for row in candidate_rejections)}.",
|
f"- Неоднозначных delta=2^31 отклонено: {sum(row.ambiguous_sequence_rejected for row in candidate_rejections)}.",
|
||||||
@@ -1399,23 +1365,35 @@ def write_report(
|
|||||||
"Инварианты безопасности основного режима",
|
"Инварианты безопасности основного режима",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"- Что означают числа ниже. Счётчики матрицы показывают, сколько сочетаний сценария и канала описывают небезопасный переход. В основном режиме безопасный запуск и постоянное аварийное намерение исключают такие переходы по построению архитектуры, поэтому счётчик равен нулю как следствие выбранной архитектуры, а не как результат розыгрыша помех.",
|
||||||
|
"- Поведение самой реализации подтверждается функциональными проверками; их номера указаны рядом с каждым свойством. Именно проверки, а не матрица, являются доказательством для перечисленных свойств.",
|
||||||
|
]
|
||||||
|
)
|
||||||
invariants = {
|
invariants = {
|
||||||
"Движение до синхронизации": sum(row.movement_before_sync for row in candidate_sessions),
|
"Движение до синхронизации": (sum(row.movement_before_sync for row in candidate_sessions), "проверки 11, 15"),
|
||||||
"Движение до подтверждения разрешения": sum(row.movement_before_authorize_ack for row in candidate_sessions),
|
"Движение до подтверждения разрешения": (sum(row.movement_before_authorize_ack for row in candidate_sessions), "проверки 12, 13"),
|
||||||
"Движение без новой команды оператора": sum(row.movement_without_new_operator for row in candidate_sessions),
|
"Движение без новой команды оператора": (sum(row.movement_without_new_operator for row in candidate_sessions), "проверка 14"),
|
||||||
"Принятие команды старого сеанса станции": sum(row.rejected_ground_session * 0 for row in candidate_rejections),
|
"Использование старого периода управления": (sum(row.old_control_epoch_uses for row in candidate_sessions), "проверка 08"),
|
||||||
"Принятие команды старого запуска ровера": sum(row.rejected_rover_boot * 0 for row in candidate_rejections),
|
"Снятие аварийной фиксации обычной командой": (sum(row.emergency_latch_removed for row in candidate_restarts), "проверка 22"),
|
||||||
"Использование старого периода управления": sum(row.old_control_epoch_uses for row in candidate_sessions),
|
"Потеря аварийного намерения после перезапуска станции": (sum(row.emergency_intent_lost for row in candidate_restarts), "проверки 17, 18"),
|
||||||
"Снятие аварийной фиксации обычной командой": sum(row.emergency_latch_removed for row in candidate_restarts),
|
"Движение до подтверждения сброса": (sum(row.movement_before_reset_ack for row in candidate_restarts), "проверки 20, 21"),
|
||||||
"Потеря аварийного намерения после перезапуска станции": sum(row.emergency_intent_lost for row in candidate_restarts),
|
"Автоматическое восстановление старой команды после сброса": (sum(row.automatic_motion_after_reset for row in candidate_restarts), "проверки 13, 14"),
|
||||||
"Движение до подтверждения сброса": sum(row.movement_before_reset_ack for row in candidate_restarts),
|
"Принятие старого запроса сброса": (sum(row.old_reset_erroneously_accepted for row in candidate_restarts), "проверка 19"),
|
||||||
"Автоматическое восстановление старой команды после сброса": sum(row.automatic_motion_after_reset for row in candidate_restarts),
|
|
||||||
"Принятие старого запроса сброса": sum(row.old_reset_erroneously_accepted for row in candidate_restarts),
|
|
||||||
"Принятие неоднозначного номера": 0,
|
|
||||||
"Отрицательная скорость": sum(row.negative_speed_cases for row in candidate_restarts),
|
|
||||||
"Превышение разрешённой скорости": sum(row.speed_limit_exceeded_cases for row in candidate_restarts),
|
|
||||||
}
|
}
|
||||||
lines.extend(f"- {name}: {value}." for name, value in invariants.items())
|
lines.extend(f"- {name}: {value} ({reference})." for name, (value, reference) in invariants.items())
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"Свойства, которые матрица не измеряет",
|
||||||
|
"- Следующие свойства зависят от разбора конкретного сообщения, а не от розыгрыша сценария, поэтому счётчика по 180 сочетаниям для них не существует. Ранее они выводились в списке инвариантов как нули; это было ошибкой представления, и такие строки удалены.",
|
||||||
|
"- Принятие команды старого сеанса станции: подтверждается проверками 06 и 16.",
|
||||||
|
"- Принятие команды старого запуска ровера: подтверждается проверкой 07.",
|
||||||
|
"- Принятие неоднозначной разности номеров 2^31: подтверждается проверкой 10.",
|
||||||
|
"- Отрицательная скорость и превышение разрешённой скорости: подтверждаются проверкой 04; диапазон 0...15 м/с проверяется при кодировании в protocol/control_messages.py.",
|
||||||
|
]
|
||||||
|
)
|
||||||
lines.extend(
|
lines.extend(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
@@ -1425,9 +1403,9 @@ def write_report(
|
|||||||
f"- Аварийные сообщения и сброс: в среднем {_mean(row.emergency_load_kbps for row in candidate_loads):.5f} кбит/с; повторы и повторные подтверждения: {_mean(row.retry_load_kbps for row in candidate_loads):.5f} кбит/с.",
|
f"- Аварийные сообщения и сброс: в среднем {_mean(row.emergency_load_kbps for row in candidate_loads):.5f} кбит/с; повторы и повторные подтверждения: {_mean(row.retry_load_kbps for row in candidate_loads):.5f} кбит/с.",
|
||||||
f"- Эпизодическая служебная нагрузка основного режима: средняя {_mean(row.episodic_service_load_kbps for row in candidate_loads):.5f} кбит/с, максимум {max(row.episodic_service_load_kbps for row in candidate_loads):.5f} кбит/с. Именно к этой величине относились прежние значения 0,12298–0,25147 кбит/с.",
|
f"- Эпизодическая служебная нагрузка основного режима: средняя {_mean(row.episodic_service_load_kbps for row in candidate_loads):.5f} кбит/с, максимум {max(row.episodic_service_load_kbps for row in candidate_loads):.5f} кбит/с. Именно к этой величине относились прежние значения 0,12298–0,25147 кбит/с.",
|
||||||
f"- Постоянные исходные потоки: видео {VIDEO_OFFERED_LOAD_KBPS:.5f}, телеметрия {TELEMETRY_OFFERED_LOAD_KBPS:.5f}, управление {CONTROL_STATE_LOAD_KBPS:.5f} кбит/с.",
|
f"- Постоянные исходные потоки: видео {VIDEO_OFFERED_LOAD_KBPS:.5f}, телеметрия {TELEMETRY_OFFERED_LOAD_KBPS:.5f}, управление {CONTROL_STATE_LOAD_KBPS:.5f} кбит/с.",
|
||||||
|
"- Видео и телеметрия здесь не моделируются: значения перенесены из Lab038, поля video_load_kbps и telemetry_load_kbps файла data/processed/lab038/lab038_summary.csv. Управление вычислено по размеру пакета и частоте.",
|
||||||
f"- Полный предложенный поток основного режима: средний {_mean(row.full_offered_load_kbps for row in candidate_loads):.5f} кбит/с, максимум {max(row.full_offered_load_kbps for row in candidate_loads):.5f} кбит/с.",
|
f"- Полный предложенный поток основного режима: средний {_mean(row.full_offered_load_kbps for row in candidate_loads):.5f} кбит/с, максимум {max(row.full_offered_load_kbps for row in candidate_loads):.5f} кбит/с.",
|
||||||
f"- Доставка управления не ниже {min(row.control_delivered_fraction for row in loads) * 100.0:.3f}%, телеметрии не ниже {min(row.telemetry_delivered_fraction for row in loads) * 100.0:.3f}%, публикация видео не ниже {min(row.video_published_fraction for row in loads) * 100.0:.3f}%.",
|
"- Доли доставки видео, телеметрии и управления, а также длина очереди в Lab041 не определяются. Модель этой лабораторной воспроизводит обмен служебными сообщениями сеанса, но не планировщик очередей и не транспорт видео. Соответствующие показатели следует брать из Lab037 и Lab040.",
|
||||||
f"- Максимальная очередь: {max(row.maximum_queue_packets for row in loads)} пакетов.",
|
|
||||||
"",
|
"",
|
||||||
"Функциональные проверки",
|
"Функциональные проверки",
|
||||||
]
|
]
|
||||||
@@ -1436,7 +1414,7 @@ def write_report(
|
|||||||
created = (
|
created = (
|
||||||
Path("protocol/control_messages.py"),
|
Path("protocol/control_messages.py"),
|
||||||
Path("protocol/session_state.py"),
|
Path("protocol/session_state.py"),
|
||||||
Path("tests/lab041_session_restart_safety.py"),
|
Path("experiments/lab041_session_restart_safety.py"),
|
||||||
SUMMARY_CSV,
|
SUMMARY_CSV,
|
||||||
SESSION_CSV,
|
SESSION_CSV,
|
||||||
REJECTION_CSV,
|
REJECTION_CSV,
|
||||||
@@ -1488,8 +1466,6 @@ def validate_outputs(
|
|||||||
assert sum(row.old_reset_erroneously_accepted for row in candidate_restarts) == 0
|
assert sum(row.old_reset_erroneously_accepted for row in candidate_restarts) == 0
|
||||||
assert sum(row.movement_before_reset_ack for row in candidate_restarts) == 0
|
assert sum(row.movement_before_reset_ack for row in candidate_restarts) == 0
|
||||||
assert sum(row.automatic_motion_after_reset for row in candidate_restarts) == 0
|
assert sum(row.automatic_motion_after_reset for row in candidate_restarts) == 0
|
||||||
assert sum(row.negative_speed_cases for row in candidate_restarts) == 0
|
|
||||||
assert sum(row.speed_limit_exceeded_cases for row in candidate_restarts) == 0
|
|
||||||
|
|
||||||
csv_paths = (SUMMARY_CSV, SESSION_CSV, REJECTION_CSV, RESTART_CSV, LOAD_CSV)
|
csv_paths = (SUMMARY_CSV, SESSION_CSV, REJECTION_CSV, RESTART_CSV, LOAD_CSV)
|
||||||
for path in csv_paths:
|
for path in csv_paths:
|
||||||
@@ -1507,12 +1483,11 @@ def validate_outputs(
|
|||||||
assert not any(path.suffix.lower() in forbidden for path in actual)
|
assert not any(path.suffix.lower() in forbidden for path in actual)
|
||||||
assert not any(path.name == "__pycache__" for path in OUTPUT_DIRECTORY.rglob("*"))
|
assert not any(path.name == "__pycache__" for path in OUTPUT_DIRECTORY.rglob("*"))
|
||||||
|
|
||||||
source = inspect.getsource(save_plots)
|
# Прежде здесь лабораторная разбирала собственный исходный код, чтобы
|
||||||
tree = ast.parse(source)
|
# убедиться, что подписи на графиках русские. Проверка снята: она
|
||||||
for node in ast.walk(tree):
|
# относится к оформлению, а не к корректности модели, и превращала
|
||||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr in {"set_title", "set_xlabel", "set_ylabel"}:
|
# эксперимент в анализатор своего же текста. Подписи видны на готовых
|
||||||
if node.args and isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str):
|
# PNG и проверяются при просмотре результатов.
|
||||||
assert re.search("[А-Яа-яЁё]", node.args[0].value)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -1527,7 +1502,7 @@ def main() -> None:
|
|||||||
save_plots(summaries, sessions, rejections, restarts, loads)
|
save_plots(summaries, sessions, rejections, restarts, loads)
|
||||||
write_report(summaries, sessions, rejections, restarts, loads, tests)
|
write_report(summaries, sessions, rejections, restarts, loads, tests)
|
||||||
validate_outputs(summaries, sessions, rejections, restarts, loads, tests)
|
validate_outputs(summaries, sessions, rejections, restarts, loads, tests)
|
||||||
print("Lab041 завершена: 180 сочетаний, 24/24 функциональных проверок, пять CSV и восемь PNG")
|
print("Lab041 завершена: 180 сочетаний, 24/24 функциональных проверок, пять CSV и семь PNG")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
1136
experiments/lab042_pluto_image_loopback.py
Normal file
1136
experiments/lab042_pluto_image_loopback.py
Normal file
File diff suppressed because it is too large
Load Diff
1832
experiments/lab043_pluto_to_rtlsdr.py
Normal file
1832
experiments/lab043_pluto_to_rtlsdr.py
Normal file
File diff suppressed because it is too large
Load Diff
485
experiments/lab043_prbs11_hardware.py
Normal file
485
experiments/lab043_prbs11_hardware.py
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
"""Один разрешённый аппаратный захват PRBS11 для Lab043.
|
||||||
|
|
||||||
|
Скрипт выполняет ровно один из заранее определённых опытов S или L. Он не
|
||||||
|
подбирает параметры, не повторяет передачу при ошибке и не переходит к
|
||||||
|
пакетам, CRC либо JPEG.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from experiments import lab043_pluto_to_rtlsdr as lab043
|
||||||
|
|
||||||
|
|
||||||
|
PLUTO_URI = "ip:192.168.2.1"
|
||||||
|
TX_GAIN_DB = -30.0
|
||||||
|
RTL_GAIN_DB = 19.7
|
||||||
|
RF_BANDWIDTH_HZ = 200_000
|
||||||
|
CONTINUITY_MAX_PHASE_JUMP_RAD = 0.25
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CapturedPrbsAnalysis:
|
||||||
|
"""Полный результат обработки одного уже принятого S/L."""
|
||||||
|
|
||||||
|
processing_success: bool
|
||||||
|
invalid_reason: str
|
||||||
|
sections: dict
|
||||||
|
calibration: lab043.CalibrationResult
|
||||||
|
continuity: dict
|
||||||
|
modes: dict[str, lab043.PrbsModeMetrics]
|
||||||
|
|
||||||
|
|
||||||
|
def preflight_pluto() -> dict:
|
||||||
|
"""Подтвердить IIO-контекст, PHY и первый TX-канал без передачи."""
|
||||||
|
|
||||||
|
import iio
|
||||||
|
|
||||||
|
context = iio.Context(PLUTO_URI)
|
||||||
|
phy = context.find_device("ad9361-phy")
|
||||||
|
tx_device = context.find_device("cf-ad9361-dds-core-lpc")
|
||||||
|
tx_channel = tx_device.find_channel("voltage0", True) if tx_device is not None else None
|
||||||
|
if phy is None:
|
||||||
|
raise RuntimeError("IIO-контекст открыт, но ad9361-phy не найден")
|
||||||
|
if tx_device is None or tx_channel is None:
|
||||||
|
raise RuntimeError("IIO-контекст открыт, но первый TX-канал недоступен")
|
||||||
|
return {
|
||||||
|
"uri": PLUTO_URI,
|
||||||
|
"context_opened": True,
|
||||||
|
"phy_name": str(phy.name),
|
||||||
|
"tx_device_name": str(tx_device.name),
|
||||||
|
"first_tx_channel": str(tx_channel.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_rtlsdr(sdr) -> dict:
|
||||||
|
sdr.sample_rate = float(lab043.SAMPLE_RATE_HZ)
|
||||||
|
sdr.center_freq = float(lab043.CARRIER_HZ)
|
||||||
|
sdr.gain = float(RTL_GAIN_DB)
|
||||||
|
actual_sample_rate = float(sdr.sample_rate)
|
||||||
|
actual_center = float(sdr.center_freq)
|
||||||
|
raw_gain = float(sdr.gain)
|
||||||
|
actual_gain = raw_gain if math.isclose(raw_gain, RTL_GAIN_DB, abs_tol=0.2) else None
|
||||||
|
return {
|
||||||
|
"requested_center_frequency_hz": float(lab043.CARRIER_HZ),
|
||||||
|
"actual_center_frequency_hz": actual_center,
|
||||||
|
"requested_sample_rate_hz": float(lab043.SAMPLE_RATE_HZ),
|
||||||
|
"actual_sample_rate_hz": actual_sample_rate,
|
||||||
|
"requested_gain_db": RTL_GAIN_DB,
|
||||||
|
"raw_gain_readback_db": raw_gain,
|
||||||
|
"actual_gain_db": actual_gain,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def preflight_rtlsdr() -> dict:
|
||||||
|
"""Открыть RTL-SDR, прочитать короткий блок и закрыть до TX."""
|
||||||
|
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
|
||||||
|
sdr = RtlSdr(device_index=0)
|
||||||
|
try:
|
||||||
|
parameters = _configure_rtlsdr(sdr)
|
||||||
|
probe = np.asarray(sdr.read_samples(16_384), dtype=np.complex64)
|
||||||
|
if len(probe) != 16_384 or not np.all(np.isfinite(probe)):
|
||||||
|
raise RuntimeError("RTL-SDR не вернул полный конечный пробный блок")
|
||||||
|
parameters["probe_sample_count"] = int(len(probe))
|
||||||
|
parameters["probe_rms"] = float(np.sqrt(np.mean(np.abs(probe) ** 2)))
|
||||||
|
return parameters
|
||||||
|
finally:
|
||||||
|
sdr.close()
|
||||||
|
|
||||||
|
|
||||||
|
def configure_pluto() -> tuple[object, dict]:
|
||||||
|
import adi
|
||||||
|
|
||||||
|
device = adi.Pluto(uri=PLUTO_URI)
|
||||||
|
device.sample_rate = int(lab043.SAMPLE_RATE_HZ)
|
||||||
|
device.tx_lo = int(lab043.CARRIER_HZ)
|
||||||
|
device.tx_rf_bandwidth = int(RF_BANDWIDTH_HZ)
|
||||||
|
device.tx_hardwaregain_chan0 = float(TX_GAIN_DB)
|
||||||
|
device.tx_cyclic_buffer = False
|
||||||
|
return device, {
|
||||||
|
"requested_center_frequency_hz": int(lab043.CARRIER_HZ),
|
||||||
|
"actual_center_frequency_hz": int(device.tx_lo),
|
||||||
|
"requested_sample_rate_hz": int(lab043.SAMPLE_RATE_HZ),
|
||||||
|
"actual_sample_rate_hz": int(device.sample_rate),
|
||||||
|
"requested_gain_db": TX_GAIN_DB,
|
||||||
|
"actual_gain_db": float(device.tx_hardwaregain_chan0),
|
||||||
|
"requested_rf_bandwidth_hz": RF_BANDWIDTH_HZ,
|
||||||
|
"actual_rf_bandwidth_hz": int(device.tx_rf_bandwidth),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def transmit_noncyclic_buffer_once(
|
||||||
|
device,
|
||||||
|
device_samples: np.ndarray,
|
||||||
|
actual_sample_rate_hz: float,
|
||||||
|
sleep_function=time.sleep,
|
||||||
|
) -> float:
|
||||||
|
"""Передать один нециклический буфер и не уничтожать его раньше времени.
|
||||||
|
|
||||||
|
В libiio v1 постановка блока в поток может завершиться до того, как DMA
|
||||||
|
физически выведет все отсчёты. Поэтому буфер остаётся жив не меньше его
|
||||||
|
расчётной длительности. Возвращаемое значение сохраняется в диагностике.
|
||||||
|
"""
|
||||||
|
|
||||||
|
samples = np.asarray(device_samples, dtype=np.complex64)
|
||||||
|
if samples.ndim != 1 or samples.size == 0:
|
||||||
|
raise ValueError("TX-буфер должен быть непустым и одномерным")
|
||||||
|
if not np.isfinite(actual_sample_rate_hz) or actual_sample_rate_hz <= 0.0:
|
||||||
|
raise ValueError("Фактическая частота TX должна быть положительной")
|
||||||
|
hold_seconds = len(samples) / actual_sample_rate_hz
|
||||||
|
device.tx_destroy_buffer()
|
||||||
|
device.tx(samples)
|
||||||
|
sleep_function(hold_seconds)
|
||||||
|
device.tx_destroy_buffer()
|
||||||
|
return float(hold_seconds)
|
||||||
|
|
||||||
|
|
||||||
|
def calibration_boundary_continuity(
|
||||||
|
samples: np.ndarray,
|
||||||
|
callback_boundaries,
|
||||||
|
sections: dict,
|
||||||
|
calibration: lab043.CalibrationResult,
|
||||||
|
sample_rate_hz: float,
|
||||||
|
) -> dict:
|
||||||
|
"""Проверить фазу обоих тонов на callback-границах внутри калибровки."""
|
||||||
|
|
||||||
|
relevant = [
|
||||||
|
int(row.accepted_end_sample)
|
||||||
|
for row in callback_boundaries
|
||||||
|
if sections["calibration_start_sample"] + 4_096
|
||||||
|
<= row.accepted_end_sample
|
||||||
|
<= sections["calibration_end_sample"] - 4_096
|
||||||
|
]
|
||||||
|
jumps: list[dict] = []
|
||||||
|
for boundary in relevant:
|
||||||
|
row = {"boundary_sample": boundary}
|
||||||
|
for name, frequency_hz in (
|
||||||
|
("low", calibration.f_low_hz),
|
||||||
|
("high", calibration.f_high_hz),
|
||||||
|
):
|
||||||
|
before_indexes = np.arange(boundary - 4_096, boundary, dtype=np.float64)
|
||||||
|
after_indexes = np.arange(boundary, boundary + 4_096, dtype=np.float64)
|
||||||
|
before = np.mean(
|
||||||
|
samples[boundary - 4_096 : boundary]
|
||||||
|
* np.exp(-1j * 2.0 * np.pi * frequency_hz * before_indexes / sample_rate_hz)
|
||||||
|
)
|
||||||
|
after = np.mean(
|
||||||
|
samples[boundary : boundary + 4_096]
|
||||||
|
* np.exp(-1j * 2.0 * np.pi * frequency_hz * after_indexes / sample_rate_hz)
|
||||||
|
)
|
||||||
|
row[f"{name}_phase_jump_rad"] = float(np.angle(after * np.conj(before)))
|
||||||
|
jumps.append(row)
|
||||||
|
maximum = max(
|
||||||
|
(
|
||||||
|
abs(value)
|
||||||
|
for row in jumps
|
||||||
|
for key, value in row.items()
|
||||||
|
if key.endswith("phase_jump_rad")
|
||||||
|
),
|
||||||
|
default=0.0,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"checked_boundary_count": len(relevant),
|
||||||
|
"maximum_absolute_phase_jump_rad": float(maximum),
|
||||||
|
"fixed_limit_rad": CONTINUITY_MAX_PHASE_JUMP_RAD,
|
||||||
|
"confirmed_discontinuity_count": int(
|
||||||
|
sum(
|
||||||
|
max(abs(row["low_phase_jump_rad"]), abs(row["high_phase_jump_rad"]))
|
||||||
|
> CONTINUITY_MAX_PHASE_JUMP_RAD
|
||||||
|
for row in jumps
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"boundaries": jumps,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_captured_prbs(
|
||||||
|
samples: np.ndarray,
|
||||||
|
plan: lab043.PrbsTransmissionPlan,
|
||||||
|
actual_rx_rate: float,
|
||||||
|
actual_tx_rate: float,
|
||||||
|
callback_boundaries=(),
|
||||||
|
) -> CapturedPrbsAnalysis:
|
||||||
|
"""Пройти тем же полным путём, что аппаратный S, но без обращения к SDR."""
|
||||||
|
|
||||||
|
calibration_samples, bpsk_samples, sections = lab043.split_calibration_and_bpsk(
|
||||||
|
samples,
|
||||||
|
plan,
|
||||||
|
actual_rx_rate,
|
||||||
|
actual_tx_rate,
|
||||||
|
)
|
||||||
|
calibration = lab043.estimate_refined_calibration(calibration_samples, actual_rx_rate)
|
||||||
|
if not calibration.valid:
|
||||||
|
reason = f"калибровка недостоверна: {calibration.invalid_reason}"
|
||||||
|
modes = {
|
||||||
|
mode: lab043._failed_prbs_metrics(mode, len(plan.payload_bits), reason)
|
||||||
|
for mode in ("A", "B", "C", "D")
|
||||||
|
}
|
||||||
|
return CapturedPrbsAnalysis(
|
||||||
|
processing_success=False,
|
||||||
|
invalid_reason=reason,
|
||||||
|
sections=sections,
|
||||||
|
calibration=calibration,
|
||||||
|
continuity={
|
||||||
|
"checked_boundary_count": 0,
|
||||||
|
"confirmed_discontinuity_count": 0,
|
||||||
|
"reason": "тоны недостоверны",
|
||||||
|
},
|
||||||
|
modes=modes,
|
||||||
|
)
|
||||||
|
|
||||||
|
continuity = calibration_boundary_continuity(
|
||||||
|
np.asarray(samples, dtype=np.complex64),
|
||||||
|
callback_boundaries,
|
||||||
|
sections,
|
||||||
|
calibration,
|
||||||
|
actual_rx_rate,
|
||||||
|
)
|
||||||
|
if continuity["confirmed_discontinuity_count"]:
|
||||||
|
reason = "в той же записи подтверждён фазовый разрыв"
|
||||||
|
modes = {
|
||||||
|
mode: lab043._failed_prbs_metrics(mode, len(plan.payload_bits), reason)
|
||||||
|
for mode in ("A", "B", "C", "D")
|
||||||
|
}
|
||||||
|
return CapturedPrbsAnalysis(
|
||||||
|
processing_success=False,
|
||||||
|
invalid_reason=reason,
|
||||||
|
sections=sections,
|
||||||
|
calibration=calibration,
|
||||||
|
continuity=continuity,
|
||||||
|
modes=modes,
|
||||||
|
)
|
||||||
|
|
||||||
|
modes = lab043.analyze_prbs_modes(
|
||||||
|
bpsk_samples,
|
||||||
|
plan.payload_bits,
|
||||||
|
calibration.carrier_offset_hz,
|
||||||
|
calibration.clock_scale,
|
||||||
|
actual_rx_rate,
|
||||||
|
known_pilot_bits=plan.pilot_bits,
|
||||||
|
)
|
||||||
|
mode_d = modes["D"]
|
||||||
|
success = bool(mode_d.detected and mode_d.matched_bit_count == len(plan.payload_bits))
|
||||||
|
reason = "" if success else (mode_d.failure_reason or "режим D не восстановил полную PRBS11")
|
||||||
|
return CapturedPrbsAnalysis(
|
||||||
|
processing_success=success,
|
||||||
|
invalid_reason=reason,
|
||||||
|
sections=sections,
|
||||||
|
calibration=calibration,
|
||||||
|
continuity=continuity,
|
||||||
|
modes=modes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_one_capture(label: str, output_directory: Path) -> dict:
|
||||||
|
if label != "S":
|
||||||
|
raise RuntimeError("На текущем этапе разрешён только один короткий опыт S")
|
||||||
|
duration = (
|
||||||
|
lab043.PRBS_SHORT_DURATION_SECONDS
|
||||||
|
if label == "S"
|
||||||
|
else lab043.PRBS_LONG_DURATION_SECONDS
|
||||||
|
)
|
||||||
|
plan = lab043.build_prbs11_transmission_plan(label, duration)
|
||||||
|
capture_started = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
pluto_preflight = preflight_pluto()
|
||||||
|
rtl_preflight = preflight_rtlsdr()
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
pluto, tx_parameters = configure_pluto()
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
|
||||||
|
sdr = RtlSdr(device_index=0)
|
||||||
|
rx_parameters = _configure_rtlsdr(sdr)
|
||||||
|
actual_tx_rate = float(tx_parameters["actual_sample_rate_hz"])
|
||||||
|
actual_rx_rate = float(rx_parameters["actual_sample_rate_hz"])
|
||||||
|
expected_rx_samples = lab043.receive_sample_count(
|
||||||
|
len(plan.tx_samples),
|
||||||
|
actual_tx_rate,
|
||||||
|
actual_rx_rate,
|
||||||
|
)
|
||||||
|
|
||||||
|
ready = threading.Event()
|
||||||
|
tx_errors: list[str] = []
|
||||||
|
tx_buffer_hold_seconds: list[float] = []
|
||||||
|
|
||||||
|
def transmit_once() -> None:
|
||||||
|
if not ready.wait(timeout=10.0):
|
||||||
|
tx_errors.append("RTL-SDR async-приём не подтвердил готовность")
|
||||||
|
return
|
||||||
|
time.sleep(lab043.RX_LEADING_MARGIN_SECONDS)
|
||||||
|
try:
|
||||||
|
hold_seconds = transmit_noncyclic_buffer_once(
|
||||||
|
pluto,
|
||||||
|
lab043.to_pluto_tx_samples(plan.tx_samples),
|
||||||
|
actual_tx_rate,
|
||||||
|
)
|
||||||
|
tx_buffer_hold_seconds.append(hold_seconds)
|
||||||
|
except Exception as error: # pragma: no cover - аппаратный путь
|
||||||
|
tx_errors.append(f"{type(error).__name__}: {error}")
|
||||||
|
|
||||||
|
tx_thread = threading.Thread(target=transmit_once, daemon=True)
|
||||||
|
tx_thread.start()
|
||||||
|
samples, boundaries, async_diagnostics = lab043.capture_continuous_rtlsdr_async(
|
||||||
|
sdr,
|
||||||
|
expected_rx_samples,
|
||||||
|
capture_ready_event=ready,
|
||||||
|
buffer_bytes=lab043.RTL_ASYNC_BUFFER_BYTES,
|
||||||
|
buffer_count=lab043.RTL_ASYNC_BUFFER_COUNT,
|
||||||
|
warmup_callback_count=lab043.RTL_ASYNC_WARMUP_CALLBACK_COUNT,
|
||||||
|
)
|
||||||
|
tx_thread.join(timeout=15.0)
|
||||||
|
if tx_thread.is_alive():
|
||||||
|
raise RuntimeError("Поток единственной передачи не завершился")
|
||||||
|
if tx_errors:
|
||||||
|
raise RuntimeError("; ".join(tx_errors))
|
||||||
|
|
||||||
|
received = np.asarray(samples, dtype=np.complex64)
|
||||||
|
if received.ndim != 1 or len(received) != expected_rx_samples:
|
||||||
|
raise RuntimeError("RX завершён, но размер массива не совпал с ожидаемым")
|
||||||
|
if not np.all(np.isfinite(received)):
|
||||||
|
raise RuntimeError("RX завершён, но массив содержит нечисловые значения")
|
||||||
|
boundary_rows = [asdict(row) for row in boundaries]
|
||||||
|
waveform_sha256 = hashlib.sha256(
|
||||||
|
np.asarray(plan.tx_samples, dtype=np.complex64).tobytes()
|
||||||
|
).hexdigest()
|
||||||
|
tx_waveform_duration_seconds = len(plan.tx_samples) / actual_tx_rate
|
||||||
|
calibration_end_seconds = plan.calibration_sample_count / actual_tx_rate
|
||||||
|
guard_end_seconds = plan.bpsk_start_sample / actual_tx_rate
|
||||||
|
prbs_end_seconds = len(plan.tx_samples) / actual_tx_rate
|
||||||
|
overload = {
|
||||||
|
"peak_magnitude": float(np.max(np.abs(received))),
|
||||||
|
"clipped_component_fraction": float(
|
||||||
|
np.mean((np.abs(received.real) >= 0.999) | (np.abs(received.imag) >= 0.999))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
metadata = {
|
||||||
|
"experiment": "Lab043 PRBS11 continuous async",
|
||||||
|
"capture_label": label,
|
||||||
|
"capture_started_utc": capture_started.isoformat().replace("+00:00", "Z"),
|
||||||
|
"capture_order": 1 if label == "S" else 2,
|
||||||
|
"capture_status": "captured",
|
||||||
|
"processing_status": "pending",
|
||||||
|
"processing_error": None,
|
||||||
|
"actual_sample_rate_hz": actual_rx_rate,
|
||||||
|
"center_frequency_hz": rx_parameters["actual_center_frequency_hz"],
|
||||||
|
"rx_gain_db": rx_parameters["actual_gain_db"],
|
||||||
|
"pluto_preflight": pluto_preflight,
|
||||||
|
"rtl_preflight": rtl_preflight,
|
||||||
|
"tx_parameters": tx_parameters,
|
||||||
|
"rx_parameters": rx_parameters,
|
||||||
|
"physical_scheme": "Pluto+ TX -> SMA -> AT30S 30 dB -> RTL-SDR RX; antennas removed",
|
||||||
|
"waveform_sha256": waveform_sha256,
|
||||||
|
"tx_waveform_duration_seconds": tx_waveform_duration_seconds,
|
||||||
|
"callback_count": int(async_diagnostics["callback_count"]),
|
||||||
|
"callback_boundaries": boundary_rows,
|
||||||
|
"expected_intervals": {
|
||||||
|
"relative_to_tx_start_seconds": {
|
||||||
|
"calibration": [0.0, calibration_end_seconds],
|
||||||
|
"guard": [calibration_end_seconds, guard_end_seconds],
|
||||||
|
"bpsk_marker_pilot_prbs": [guard_end_seconds, prbs_end_seconds],
|
||||||
|
},
|
||||||
|
"rx_leading_margin_seconds": lab043.RX_LEADING_MARGIN_SECONDS,
|
||||||
|
"rx_trailing_margin_seconds": lab043.RX_TRAILING_MARGIN_SECONDS,
|
||||||
|
},
|
||||||
|
"waveform": {
|
||||||
|
"calibration_tones_hz": [-lab043.F_CAL_HZ, lab043.F_CAL_HZ],
|
||||||
|
"calibration_duration_seconds": lab043.CALIBRATION_TONE_DURATION_SECONDS,
|
||||||
|
"fixed_guard_seconds": lab043.CALIBRATION_TO_BPSK_GUARD_SECONDS,
|
||||||
|
"single_initial_marker_bit_count": len(plan.marker_bits),
|
||||||
|
"pilot_symbol_count": len(plan.pilot_bits),
|
||||||
|
"pilot_duration_seconds": len(plan.pilot_bits) / lab043.SYMBOL_RATE,
|
||||||
|
"pilot_polynomial": lab043.PILOT_POLYNOMIAL,
|
||||||
|
"pilot_initial_state_hex": f"0x{lab043.PILOT_INITIAL_STATE:02X}",
|
||||||
|
"pilot_sha256": hashlib.sha256(plan.pilot_bits.tobytes()).hexdigest(),
|
||||||
|
"periodic_resynchronization": False,
|
||||||
|
"prbs_polynomial": lab043.PRBS11_POLYNOMIAL,
|
||||||
|
"prbs_initial_state_hex": f"0x{lab043.PRBS11_INITIAL_STATE:03X}",
|
||||||
|
"prbs_useful_duration_seconds": plan.useful_duration_seconds,
|
||||||
|
"transmitted_prbs_bit_count": len(plan.payload_bits),
|
||||||
|
"transmitted_prbs_sha256": hashlib.sha256(plan.payload_bits.tobytes()).hexdigest(),
|
||||||
|
"tx_sample_count": len(plan.tx_samples),
|
||||||
|
"tx_samples_sha256": waveform_sha256,
|
||||||
|
},
|
||||||
|
"async_capture": async_diagnostics | {
|
||||||
|
"expected_sample_count": expected_rx_samples,
|
||||||
|
"callback_boundaries": boundary_rows,
|
||||||
|
},
|
||||||
|
"tx_buffer_hold_seconds": (
|
||||||
|
tx_buffer_hold_seconds[0] if tx_buffer_hold_seconds else None
|
||||||
|
),
|
||||||
|
"overload": overload,
|
||||||
|
}
|
||||||
|
stamp = capture_started.strftime("%Y%m%d_%H%M%S")
|
||||||
|
base = output_directory / f"lab043_prbs11_{label.lower()}_{stamp}"
|
||||||
|
iq_path, json_path, analysis, processing_error = lab043.save_then_process_capture(
|
||||||
|
base,
|
||||||
|
received,
|
||||||
|
metadata,
|
||||||
|
lambda stored: analyze_captured_prbs(
|
||||||
|
stored,
|
||||||
|
plan,
|
||||||
|
actual_rx_rate,
|
||||||
|
actual_tx_rate,
|
||||||
|
boundaries,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if analysis is None:
|
||||||
|
return {
|
||||||
|
"iq_path": str(iq_path),
|
||||||
|
"metadata_path": str(json_path),
|
||||||
|
"capture_status": "captured",
|
||||||
|
"processing_status": "processing_failed",
|
||||||
|
"processing_error": processing_error,
|
||||||
|
"tx_parameters": tx_parameters,
|
||||||
|
"rx_parameters": rx_parameters,
|
||||||
|
"async_capture": async_diagnostics,
|
||||||
|
"overload": overload,
|
||||||
|
}
|
||||||
|
result = {
|
||||||
|
"iq_path": str(iq_path),
|
||||||
|
"metadata_path": str(json_path),
|
||||||
|
"capture_status": "captured",
|
||||||
|
"processing_status": "success",
|
||||||
|
"processing_error": None,
|
||||||
|
"processing_success": analysis.processing_success,
|
||||||
|
"invalid_reason": analysis.invalid_reason,
|
||||||
|
"calibration": asdict(analysis.calibration),
|
||||||
|
"continuity": analysis.continuity,
|
||||||
|
"modes": {mode: asdict(value) for mode, value in analysis.modes.items()},
|
||||||
|
"tx_parameters": tx_parameters,
|
||||||
|
"rx_parameters": rx_parameters,
|
||||||
|
"async_capture": async_diagnostics,
|
||||||
|
"overload": overload,
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--label", choices=("S",), required=True)
|
||||||
|
parser.add_argument("--output-directory", type=Path, default=Path("data/raw/lab043"))
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
result = run_one_capture(arguments.label, arguments.output_directory)
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
|
if result["processing_status"] != "success":
|
||||||
|
return 2
|
||||||
|
mode_d = result["modes"]["D"]
|
||||||
|
return 0 if result["calibration"]["valid"] and mode_d["detected"] else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
1372
protocol/bpsk_radio.py
Normal file
1372
protocol/bpsk_radio.py
Normal file
File diff suppressed because it is too large
Load Diff
20
requirements-hardware.txt
Normal file
20
requirements-hardware.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# SDR Rover Link — аппаратное окружение.
|
||||||
|
#
|
||||||
|
# Нужно только для лабораторных, работающих с PlutoSDR:
|
||||||
|
# Lab024a, Lab024b, Lab025 и всё, начиная с Lab042.
|
||||||
|
#
|
||||||
|
# Устанавливается поверх расчётного окружения:
|
||||||
|
# python -m pip install -r requirements.txt
|
||||||
|
# python -m pip install -r requirements-hardware.txt
|
||||||
|
#
|
||||||
|
# Проверено 10 августа 2026 года на устройстве
|
||||||
|
# Analog Devices PlutoSDR Rev.C (Z7010-AD9361), прошивка v0.32,
|
||||||
|
# подключение по USB как RNDIS-адаптер, адрес ip:192.168.2.1.
|
||||||
|
#
|
||||||
|
# Замечание по диагностике: наличие связи с устройством проверяется
|
||||||
|
# только успешным подключением через iio.Context. Команда ping и проверка
|
||||||
|
# открытых портов недостоверны — при активном VPN трафик уходит в туннель
|
||||||
|
# и создаёт видимость доступности несуществующего адреса.
|
||||||
|
|
||||||
|
pyadi-iio==0.0.21
|
||||||
|
pylibiio==0.25
|
||||||
21
requirements.txt
Normal file
21
requirements.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# SDR Rover Link — расчётное окружение.
|
||||||
|
#
|
||||||
|
# Достаточно для запуска быстрых проверок (pytest), всех расчётных
|
||||||
|
# лабораторных и построения графиков. Оборудование не требуется:
|
||||||
|
# проект поднимается на компьютере без SDR.
|
||||||
|
#
|
||||||
|
# Установка:
|
||||||
|
# python -m pip install -r requirements.txt
|
||||||
|
#
|
||||||
|
# Версии зафиксированы по фактически проверенному окружению
|
||||||
|
# от 10 августа 2026 года, Python 3.13.1.
|
||||||
|
#
|
||||||
|
# Аппаратные лабораторные (Lab024, Lab025, Lab042 и далее) требуют
|
||||||
|
# дополнительно requirements-hardware.txt.
|
||||||
|
|
||||||
|
numpy==2.2.6
|
||||||
|
scipy==1.18.0
|
||||||
|
matplotlib==3.11.1
|
||||||
|
opencv-python==4.12.0
|
||||||
|
Pillow==12.0.0
|
||||||
|
pytest==9.1.1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Быстрые автоматические проверки ядра протокола."""
|
||||||
|
|||||||
368
tests/test_bpsk_radio.py
Normal file
368
tests/test_bpsk_radio.py
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
"""Проверки protocol/bpsk_radio.py.
|
||||||
|
|
||||||
|
Модуль был собран переносом функций из Lab018, Lab019 и Lab023. Главная
|
||||||
|
задача этих проверок — доказать, что перенос ничего не изменил: результаты
|
||||||
|
сравниваются с оригиналами, загруженными из лабораторных в изоляции, без
|
||||||
|
выполнения их модульного кода.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import math
|
||||||
|
import pathlib
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from protocol import bpsk_radio as radio
|
||||||
|
from protocol.packet import MESSAGE_TYPE_IMAGE_FRAGMENT, build_packet, parse_packet
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||||
|
SHARED_CONSTANTS = {
|
||||||
|
"RADIO_SYNC_WORD",
|
||||||
|
"PREAMBLE_BIT_COUNT",
|
||||||
|
"SYMBOL_RATE",
|
||||||
|
"CFO_REFINEMENT_HALF_WIDTH_HZ",
|
||||||
|
"CFO_REFINEMENT_STEP_HZ",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _load_isolated(relative_path: str, function_names: set[str]) -> dict:
|
||||||
|
"""Выполнить только заданные функции лабораторной, без кода модуля.
|
||||||
|
|
||||||
|
Лабораторные при импорте создают каталоги и пишут файлы, поэтому
|
||||||
|
импортировать их в тестах нельзя.
|
||||||
|
"""
|
||||||
|
|
||||||
|
path = PROJECT_ROOT / relative_path
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
kept = [
|
||||||
|
node
|
||||||
|
for node in tree.body
|
||||||
|
if (isinstance(node, ast.FunctionDef) and node.name in function_names)
|
||||||
|
or (isinstance(node, ast.Assign) and ast.unparse(node.targets[0]) in SHARED_CONSTANTS)
|
||||||
|
]
|
||||||
|
namespace: dict = {"np": np, "struct": struct}
|
||||||
|
exec(compile(ast.Module(body=kept, type_ignores=[]), str(path), "exec"), namespace)
|
||||||
|
return namespace
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def original_lab023() -> dict:
|
||||||
|
return _load_isolated(
|
||||||
|
"experiments/lab023_guarded_cfo_correction.py",
|
||||||
|
{
|
||||||
|
"bytes_to_bits",
|
||||||
|
"bits_to_bytes",
|
||||||
|
"bpsk_modulate",
|
||||||
|
"bpsk_demodulate",
|
||||||
|
"estimate_carrier_parameters",
|
||||||
|
"correct_phase_and_frequency",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def original_lab019() -> dict:
|
||||||
|
return _load_isolated(
|
||||||
|
"experiments/lab019_bpsk_receiver.py",
|
||||||
|
{
|
||||||
|
"build_frame_marker",
|
||||||
|
"find_radio_frame",
|
||||||
|
"bytes_to_bits",
|
||||||
|
"bits_to_bytes",
|
||||||
|
"bpsk_modulate",
|
||||||
|
"bpsk_demodulate",
|
||||||
|
"root_raised_cosine_taps",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def original_lab018() -> dict:
|
||||||
|
return _load_isolated("experiments/lab018_bpsk_radio_frame.py", {"root_raised_cosine_taps"})
|
||||||
|
|
||||||
|
|
||||||
|
def _identical(left, right) -> bool:
|
||||||
|
if isinstance(left, np.ndarray) or isinstance(right, np.ndarray):
|
||||||
|
return np.array_equal(np.asarray(left), np.asarray(right))
|
||||||
|
if isinstance(left, dict):
|
||||||
|
return sorted(left) == sorted(right) and all(_identical(left[k], right[k]) for k in left)
|
||||||
|
if isinstance(left, (tuple, list)):
|
||||||
|
return len(left) == len(right) and all(_identical(a, b) for a, b in zip(left, right))
|
||||||
|
return left == right
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- эквивалентность
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("length", [1, 7, 64, 255])
|
||||||
|
def test_bit_conversion_matches_lab023(original_lab023: dict, length: int) -> None:
|
||||||
|
data = bytes(np.random.default_rng(length).integers(0, 256, size=length, dtype=np.uint8))
|
||||||
|
ours = radio.bytes_to_bits(data)
|
||||||
|
assert _identical(ours, original_lab023["bytes_to_bits"](data))
|
||||||
|
assert _identical(radio.bits_to_bytes(ours), original_lab023["bits_to_bytes"](ours))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("length", [8, 64, 256])
|
||||||
|
def test_modulation_matches_lab023(original_lab023: dict, length: int) -> None:
|
||||||
|
bits = np.random.default_rng(length).integers(0, 2, size=length).astype(np.uint8)
|
||||||
|
ours = radio.bpsk_modulate(bits)
|
||||||
|
assert _identical(ours, original_lab023["bpsk_modulate"](bits))
|
||||||
|
assert _identical(radio.bpsk_demodulate(ours), original_lab023["bpsk_demodulate"](ours))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("rolloff,sps,span", [(0.35, 32, 10), (0.35, 128, 10), (0.5, 64, 8)])
|
||||||
|
def test_shaping_filter_matches_lab018(original_lab018: dict, rolloff: float, sps: int, span: int) -> None:
|
||||||
|
assert _identical(
|
||||||
|
radio.root_raised_cosine_taps(rolloff, sps, span),
|
||||||
|
original_lab018["root_raised_cosine_taps"](rolloff, sps, span),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_marker_matches_lab019(original_lab019: dict) -> None:
|
||||||
|
"""Маркер возвращает пару: биты и символы. Совпасть должны обе."""
|
||||||
|
|
||||||
|
assert _identical(radio.build_frame_marker(), original_lab019["build_frame_marker"]())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("seed", [1, 2, 3])
|
||||||
|
def test_carrier_estimation_matches_lab023(original_lab023: dict, seed: int) -> None:
|
||||||
|
"""Оценка ухода частоты обязана совпасть при произвольных фазе и уходе."""
|
||||||
|
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
_, marker = radio.build_frame_marker()
|
||||||
|
phase = float(rng.uniform(-3.0, 3.0))
|
||||||
|
increment = float(rng.uniform(-0.005, 0.005))
|
||||||
|
impaired = marker * np.exp(1j * (phase + increment * np.arange(len(marker))))
|
||||||
|
impaired = impaired + 0.02 * rng.standard_normal(len(marker))
|
||||||
|
|
||||||
|
assert _identical(
|
||||||
|
radio.estimate_carrier_parameters(impaired, marker),
|
||||||
|
original_lab023["estimate_carrier_parameters"](impaired, marker),
|
||||||
|
)
|
||||||
|
assert _identical(
|
||||||
|
radio.correct_phase_and_frequency(impaired, phase, increment),
|
||||||
|
original_lab023["correct_phase_and_frequency"](impaired, phase, increment),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- новые функции кадра
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("payload_size", [1, 17, 512, radio.MAXIMUM_PROTOCOL_PACKET_BYTES])
|
||||||
|
def test_frame_roundtrip(payload_size: int) -> None:
|
||||||
|
packet = bytes(np.random.default_rng(payload_size).integers(0, 256, size=payload_size, dtype=np.uint8))
|
||||||
|
bits, symbols, marker = radio.build_radio_frame(packet)
|
||||||
|
|
||||||
|
assert len(bits) == radio.radio_frame_bit_count(payload_size)
|
||||||
|
assert len(symbols) == len(bits)
|
||||||
|
assert len(marker) == radio.MARKER_BIT_COUNT
|
||||||
|
assert radio.parse_radio_frame(bits) == packet
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_rejects_sizes_outside_range() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
radio.build_radio_frame(b"")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
radio.build_radio_frame(b"\x00" * (radio.MAXIMUM_PROTOCOL_PACKET_BYTES + 1))
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
radio.build_radio_frame("не байты")
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_with_broken_sync_word_is_rejected() -> None:
|
||||||
|
bits, _, _ = radio.build_radio_frame(b"payload")
|
||||||
|
broken = bits.copy()
|
||||||
|
broken[radio.PREAMBLE_BIT_COUNT] ^= 1
|
||||||
|
assert radio.parse_radio_frame(broken) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_truncated_frame_is_rejected() -> None:
|
||||||
|
bits, _, _ = radio.build_radio_frame(b"payload")
|
||||||
|
assert radio.parse_radio_frame(bits[:10]) is None
|
||||||
|
assert radio.parse_radio_frame(bits[:-8]) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_carries_a_protocol_packet() -> None:
|
||||||
|
"""Кадр должен переносить настоящий пакет так, чтобы CRC32 сошлась."""
|
||||||
|
|
||||||
|
packet = build_packet(b"fragment payload", MESSAGE_TYPE_IMAGE_FRAGMENT, 5)
|
||||||
|
bits, _, _ = radio.build_radio_frame(packet)
|
||||||
|
assert parse_packet(radio.parse_radio_frame(bits)).payload == b"fragment payload"
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_search_finds_the_start_in_a_shaped_signal() -> None:
|
||||||
|
"""Сквозная проверка: формирование, фильтрация и поиск кадра."""
|
||||||
|
|
||||||
|
samples_per_symbol = 32
|
||||||
|
taps = radio.root_raised_cosine_taps(0.35, samples_per_symbol, 10)
|
||||||
|
packet = build_packet(b"HELLO SDR", MESSAGE_TYPE_IMAGE_FRAGMENT, 1)
|
||||||
|
bits, symbols, marker = radio.build_radio_frame(packet)
|
||||||
|
|
||||||
|
upsampled = np.zeros(len(symbols) * samples_per_symbol, dtype=complex)
|
||||||
|
upsampled[::samples_per_symbol] = symbols
|
||||||
|
matched = np.convolve(np.convolve(upsampled, taps, mode="full"), taps, mode="full")
|
||||||
|
|
||||||
|
found = radio.find_radio_frame(matched, marker, samples_per_symbol)
|
||||||
|
start = found["start_symbol_index"]
|
||||||
|
recovered = found["symbol_samples"][start : start + len(bits)]
|
||||||
|
|
||||||
|
assert radio.parse_radio_frame(radio.bpsk_demodulate(recovered)) == packet
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------ Lab043: общие примитивы
|
||||||
|
|
||||||
|
|
||||||
|
def test_known_tone_peak_search_finds_both_windows() -> None:
|
||||||
|
frequencies = np.linspace(-100_000.0, 100_000.0, 4001)
|
||||||
|
powers = np.ones_like(frequencies)
|
||||||
|
powers[np.argmin(np.abs(frequencies + 51_200.0))] = 100.0
|
||||||
|
powers[np.argmin(np.abs(frequencies - 49_300.0))] = 80.0
|
||||||
|
|
||||||
|
peaks = radio.find_known_tone_peaks(frequencies, powers, 50_000.0, 30_000.0)
|
||||||
|
|
||||||
|
assert math.isclose(peaks["low_frequency_hz"], -51_200.0, abs_tol=1.0)
|
||||||
|
assert math.isclose(peaks["high_frequency_hz"], 49_300.0, abs_tol=1.0)
|
||||||
|
assert peaks["low_power"] == 100.0
|
||||||
|
assert peaks["high_power"] == 80.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_known_tone_peak_search_returns_nan_without_bins() -> None:
|
||||||
|
frequencies = np.linspace(-1_000.0, 1_000.0, 101)
|
||||||
|
powers = np.ones_like(frequencies)
|
||||||
|
peaks = radio.find_known_tone_peaks(frequencies, powers, 50_000.0, 1_000.0)
|
||||||
|
|
||||||
|
assert math.isnan(peaks["low_frequency_hz"])
|
||||||
|
assert math.isnan(peaks["high_frequency_hz"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ppm", [20.0, -20.0, 100.0, -100.0])
|
||||||
|
def test_two_tone_clock_estimate_has_correct_sign_and_magnitude(ppm: float) -> None:
|
||||||
|
scale = 1.0 + ppm * 1e-6
|
||||||
|
carrier_offset_hz = -1_250.0
|
||||||
|
low = carrier_offset_hz - 50_000.0 * scale
|
||||||
|
high = carrier_offset_hz + 50_000.0 * scale
|
||||||
|
|
||||||
|
estimate = radio.estimate_two_tone_offsets(low, high, 50_000.0)
|
||||||
|
|
||||||
|
assert math.isclose(estimate["carrier_offset_hz"], carrier_offset_hz, abs_tol=1e-9)
|
||||||
|
assert math.isclose(estimate["clock_scale"], scale, abs_tol=1e-12)
|
||||||
|
assert math.isclose(estimate["sample_clock_error_ppm"], ppm, abs_tol=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_two_tone_invalid_estimate_is_nan_not_zero() -> None:
|
||||||
|
estimate = radio.estimate_two_tone_offsets(float("nan"), 50_000.0, 50_000.0)
|
||||||
|
assert all(math.isnan(value) for value in estimate.values())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("carrier_offset_hz", [730.0, -730.0])
|
||||||
|
def test_coarse_frequency_correction_handles_both_signs(carrier_offset_hz: float) -> None:
|
||||||
|
sample_rate_hz = 20_000.0
|
||||||
|
indexes = np.arange(20_000, dtype=np.float64)
|
||||||
|
impaired = np.exp(1j * 2.0 * np.pi * carrier_offset_hz * indexes / sample_rate_hz)
|
||||||
|
|
||||||
|
corrected = radio.apply_coarse_frequency_correction(
|
||||||
|
impaired,
|
||||||
|
carrier_offset_hz,
|
||||||
|
sample_rate_hz,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert np.max(np.abs(corrected - 1.0)) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"carrier_offset_hz",
|
||||||
|
[-10.0, -5.0, -2.0, -1.2, -1.0, -0.5, 0.5, 1.0, 1.2, 2.0, 5.0, 10.0],
|
||||||
|
)
|
||||||
|
def test_known_pilot_estimator_resolves_sub_hertz_cfo_with_hardware_like_phase_noise(
|
||||||
|
carrier_offset_hz: float,
|
||||||
|
) -> None:
|
||||||
|
symbol_count = 1_280
|
||||||
|
indexes = np.arange(symbol_count, dtype=np.float64)
|
||||||
|
known = np.where((indexes.astype(np.int64) * 73 + 19) % 127 < 64, 1.0, -1.0).astype(
|
||||||
|
np.complex128
|
||||||
|
)
|
||||||
|
estimates: list[float] = []
|
||||||
|
valid_flags: list[bool] = []
|
||||||
|
for repetition in range(96):
|
||||||
|
random_generator = np.random.default_rng(
|
||||||
|
43_000_000
|
||||||
|
+ int(round((carrier_offset_hz + 20.0) * 1_000.0))
|
||||||
|
+ repetition
|
||||||
|
)
|
||||||
|
phase_noise = random_generator.normal(0.0, 0.4691, symbol_count)
|
||||||
|
received = known * np.exp(
|
||||||
|
1j
|
||||||
|
* (
|
||||||
|
2.0 * np.pi * carrier_offset_hz * indexes / radio.SYMBOL_RATE
|
||||||
|
+ phase_noise
|
||||||
|
)
|
||||||
|
)
|
||||||
|
estimate = radio.estimate_known_pilot_carrier(received, known)
|
||||||
|
valid_flags.append(estimate.valid)
|
||||||
|
estimates.append(estimate.frequency_hz)
|
||||||
|
|
||||||
|
errors = np.asarray(estimates) - carrier_offset_hz
|
||||||
|
assert all(valid_flags)
|
||||||
|
assert np.all(np.sign(estimates) == np.sign(carrier_offset_hz))
|
||||||
|
assert abs(float(np.mean(errors))) < 0.08
|
||||||
|
assert float(np.std(errors, ddof=1)) < 0.18
|
||||||
|
assert float(np.percentile(np.abs(errors), 95.0)) < 0.35
|
||||||
|
|
||||||
|
|
||||||
|
def test_known_pilot_estimator_rejects_noise_instead_of_reporting_false_cfo() -> None:
|
||||||
|
random_generator = np.random.default_rng(43_043)
|
||||||
|
known = np.resize(np.asarray([-1.0, 1.0], dtype=np.complex128), 1_280)
|
||||||
|
noise = (
|
||||||
|
random_generator.normal(0.0, 1.0, len(known))
|
||||||
|
+ 1j * random_generator.normal(0.0, 1.0, len(known))
|
||||||
|
)
|
||||||
|
|
||||||
|
estimate = radio.estimate_known_pilot_carrier(noise, known)
|
||||||
|
|
||||||
|
assert not estimate.valid
|
||||||
|
assert estimate.invalid_reason
|
||||||
|
assert math.isnan(estimate.frequency_hz)
|
||||||
|
assert math.isnan(estimate.phase_increment_rad_per_symbol)
|
||||||
|
|
||||||
|
|
||||||
|
def _sample_at_positions(signal: np.ndarray, positions: np.ndarray) -> np.ndarray:
|
||||||
|
indexes = np.arange(len(signal), dtype=np.float64)
|
||||||
|
real = np.interp(positions, indexes, signal.real)
|
||||||
|
imaginary = np.interp(positions, indexes, signal.imag)
|
||||||
|
return real + 1j * imaginary
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("ppm", [20.0, -20.0, 100.0, -100.0])
|
||||||
|
def test_clock_resampling_direction_reduces_timing_error(ppm: float) -> None:
|
||||||
|
scale = 1.0 + ppm * 1e-6
|
||||||
|
sample_count = 200_000
|
||||||
|
indexes = np.arange(sample_count, dtype=np.float64)
|
||||||
|
reference = np.exp(1j * 2.0 * np.pi * 0.071 * indexes)
|
||||||
|
|
||||||
|
received_length = math.floor(sample_count / scale)
|
||||||
|
received_positions = np.arange(received_length, dtype=np.float64) * scale
|
||||||
|
received = _sample_at_positions(reference, received_positions)
|
||||||
|
|
||||||
|
corrected = radio.resample_for_clock_scale(received, scale)
|
||||||
|
wrong_direction = radio.resample_for_clock_scale(received, 1.0 / scale)
|
||||||
|
|
||||||
|
uncorrected_count = min(len(received), len(reference))
|
||||||
|
corrected_count = min(len(corrected), len(reference)) - 2
|
||||||
|
wrong_count = min(len(wrong_direction), len(reference)) - 2
|
||||||
|
uncorrected_error = float(
|
||||||
|
np.mean(np.abs(received[:uncorrected_count] - reference[:uncorrected_count]) ** 2)
|
||||||
|
)
|
||||||
|
corrected_error = float(
|
||||||
|
np.mean(np.abs(corrected[:corrected_count] - reference[:corrected_count]) ** 2)
|
||||||
|
)
|
||||||
|
wrong_error = float(
|
||||||
|
np.mean(np.abs(wrong_direction[:wrong_count] - reference[:wrong_count]) ** 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(corrected) == round(len(received) * scale)
|
||||||
|
assert corrected_error < uncorrected_error
|
||||||
|
assert corrected_error < wrong_error
|
||||||
155
tests/test_control_messages.py
Normal file
155
tests/test_control_messages.py
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"""Быстрые проверки формата управляющих сообщений: protocol/control_messages.py.
|
||||||
|
|
||||||
|
Перенесено из функциональных проверок Lab041, где эти свойства
|
||||||
|
проверялись только при полном запуске лабораторной.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from protocol.control_messages import (
|
||||||
|
COMMON_SIZE,
|
||||||
|
ControlMessageError,
|
||||||
|
ControlStateMessage,
|
||||||
|
EmergencyAck,
|
||||||
|
EmergencyStop,
|
||||||
|
MessageContext,
|
||||||
|
MessageType,
|
||||||
|
MovementAuthorizeAck,
|
||||||
|
MovementAuthorizeRequest,
|
||||||
|
ResetAck,
|
||||||
|
ResetRequest,
|
||||||
|
RoverSafetyCode,
|
||||||
|
SequenceComparison,
|
||||||
|
SessionHello,
|
||||||
|
SessionReject,
|
||||||
|
SessionRejectReason,
|
||||||
|
SessionStatus,
|
||||||
|
compare_sequence,
|
||||||
|
decode_message,
|
||||||
|
encode_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONTEXT = MessageContext(11, 22, 33, 44)
|
||||||
|
|
||||||
|
ALL_MESSAGES = (
|
||||||
|
SessionHello(CONTEXT, True, 101),
|
||||||
|
SessionStatus(CONTEXT, RoverSafetyCode.ROVER_SESSION_SYNCED_SAFE, False, 0),
|
||||||
|
MovementAuthorizeRequest(CONTEXT, 201),
|
||||||
|
MovementAuthorizeAck(CONTEXT, 201, True),
|
||||||
|
ControlStateMessage(CONTEXT, 1.25, -0.5, False, True),
|
||||||
|
EmergencyStop(CONTEXT, 301),
|
||||||
|
EmergencyAck(CONTEXT, 301),
|
||||||
|
ResetRequest(CONTEXT, 301, 401, 0.0, False),
|
||||||
|
ResetAck(CONTEXT, 301, 401, True, SessionRejectReason.ACCEPTED),
|
||||||
|
SessionReject(CONTEXT, MessageType.CONTROL_STATE, SessionRejectReason.CONTROL_EPOCH),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("message", ALL_MESSAGES, ids=lambda m: type(m).__name__)
|
||||||
|
def test_roundtrip_is_exact(message) -> None:
|
||||||
|
assert decode_message(encode_message(message)) == message
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_message_type_is_covered() -> None:
|
||||||
|
"""Если добавится тип сообщения, этот тест обязан упасть."""
|
||||||
|
|
||||||
|
assert len(ALL_MESSAGES) == len(MessageType)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("message", ALL_MESSAGES, ids=lambda m: type(m).__name__)
|
||||||
|
def test_encoding_starts_with_the_common_header(message) -> None:
|
||||||
|
encoded = encode_message(message)
|
||||||
|
assert len(encoded) >= COMMON_SIZE
|
||||||
|
assert encoded[0] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrong_version_is_rejected() -> None:
|
||||||
|
payload = bytearray(encode_message(SessionHello(CONTEXT, False, 0)))
|
||||||
|
payload[0] = 2
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
decode_message(bytes(payload))
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_type_is_rejected() -> None:
|
||||||
|
payload = bytearray(encode_message(SessionHello(CONTEXT, False, 0)))
|
||||||
|
payload[1] = 255
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
decode_message(bytes(payload))
|
||||||
|
|
||||||
|
|
||||||
|
def test_truncated_message_is_rejected() -> None:
|
||||||
|
payload = encode_message(SessionHello(CONTEXT, False, 0))
|
||||||
|
for cut in (0, 1, COMMON_SIZE - 1, len(payload) - 1):
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
decode_message(payload[:cut])
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------- диапазоны команд движения
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"speed,turn,braking,permitted",
|
||||||
|
[
|
||||||
|
(15.1, 0.0, False, True), # выше предела
|
||||||
|
(-0.1, 0.0, False, True), # отрицательная скорость
|
||||||
|
(-5.0, 0.0, False, True),
|
||||||
|
(1.0, 1.1, False, True), # поворот вне диапазона
|
||||||
|
(1.0, -1.1, False, True),
|
||||||
|
(1.0, 0.0, True, False), # торможение при ненулевой скорости
|
||||||
|
(1.0, 0.0, False, False), # движение без разрешения
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_invalid_control_state_is_rejected(speed, turn, braking, permitted) -> None:
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
encode_message(ControlStateMessage(CONTEXT, speed, turn, braking, permitted))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"speed,turn,braking,permitted",
|
||||||
|
[
|
||||||
|
(0.0, 0.0, True, False),
|
||||||
|
(15.0, 0.0, False, True),
|
||||||
|
(0.0, 0.0, False, False),
|
||||||
|
(7.5, 1.0, False, True),
|
||||||
|
(7.5, -1.0, False, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_valid_control_state_is_accepted(speed, turn, braking, permitted) -> None:
|
||||||
|
message = ControlStateMessage(CONTEXT, speed, turn, braking, permitted)
|
||||||
|
assert decode_message(encode_message(message)) == message
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bad", [float("nan"), float("inf"), float("-inf")])
|
||||||
|
def test_non_finite_speed_is_rejected(bad: float) -> None:
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
encode_message(ControlStateMessage(CONTEXT, bad, 0.0, False, True))
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------- сравнение номеров сообщений
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence_comparison_basic_order() -> None:
|
||||||
|
assert compare_sequence(5, 4) is SequenceComparison.NEWER
|
||||||
|
assert compare_sequence(4, 5) is SequenceComparison.NOT_NEWER
|
||||||
|
assert compare_sequence(4, 4) is SequenceComparison.NOT_NEWER
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence_comparison_wraps_at_32_bits() -> None:
|
||||||
|
"""Переход через 0xFFFFFFFF обязан считаться новым, а не откатом."""
|
||||||
|
|
||||||
|
assert compare_sequence(0, 0xFFFFFFFF) is SequenceComparison.NEWER
|
||||||
|
assert compare_sequence(1, 0xFFFFFFFE) is SequenceComparison.NEWER
|
||||||
|
|
||||||
|
|
||||||
|
def test_ambiguous_half_range_is_rejected() -> None:
|
||||||
|
"""Разность ровно 2^31 неотличима от отката и должна отклоняться."""
|
||||||
|
|
||||||
|
assert compare_sequence(0x80000000, 0) is SequenceComparison.AMBIGUOUS
|
||||||
|
assert compare_sequence(0, 0x80000000) is SequenceComparison.AMBIGUOUS
|
||||||
|
|
||||||
|
|
||||||
|
def test_just_below_the_ambiguous_boundary_is_newer() -> None:
|
||||||
|
assert compare_sequence(0x7FFFFFFF, 0) is SequenceComparison.NEWER
|
||||||
125
tests/test_lab042_reporting.py
Normal file
125
tests/test_lab042_reporting.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
"""Проверки отчётности Lab042: неуспешный путь не должен маскироваться.
|
||||||
|
|
||||||
|
Три дефекта, найденные при разборе кабельного прогона, закрыты правками в
|
||||||
|
experiments/lab042_pluto_image_loopback.py. Эти тесты фиксируют их, чтобы
|
||||||
|
провал лаборатории нельзя было принять за успех:
|
||||||
|
|
||||||
|
1. EVM не занижается искусственно при слабом сигнале и не превращается в
|
||||||
|
ноль, когда её нельзя вычислить;
|
||||||
|
2. отсутствующее принятое изображение не объявляется созданным файлом;
|
||||||
|
3. невыполнение критерия лаборатории даёт ненулевой код завершения.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from experiments.lab042_pluto_image_loopback import (
|
||||||
|
RECEIVED_IMAGE_PATH,
|
||||||
|
FunctionalTestResult,
|
||||||
|
_error_vector_magnitude,
|
||||||
|
created_files,
|
||||||
|
transfer_meets_criteria,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _reference(length: int = 512) -> np.ndarray:
|
||||||
|
bits = np.random.default_rng(length).integers(0, 2, size=length).astype(np.float64)
|
||||||
|
return (2.0 * bits - 1.0).astype(np.complex128)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ EVM
|
||||||
|
|
||||||
|
|
||||||
|
def test_evm_is_near_zero_for_a_clean_signal() -> None:
|
||||||
|
reference = _reference()
|
||||||
|
assert _error_vector_magnitude(reference.copy(), reference) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
def test_evm_is_invariant_to_overall_gain() -> None:
|
||||||
|
"""Одно и то же отношение сигнал/шум при разной амплитуде — одна EVM."""
|
||||||
|
|
||||||
|
rng = np.random.default_rng(1)
|
||||||
|
reference = _reference()
|
||||||
|
noise = 0.1 * (rng.standard_normal(len(reference)) + 1j * rng.standard_normal(len(reference)))
|
||||||
|
|
||||||
|
strong = _error_vector_magnitude(reference + noise, reference)
|
||||||
|
weak = _error_vector_magnitude(0.001 * (reference + noise), reference)
|
||||||
|
|
||||||
|
assert math.isclose(strong, weak, rel_tol=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_evm_grows_when_the_signal_is_weak_and_noisy() -> None:
|
||||||
|
"""Регрессия: прежняя нормировка на единичный эталон занижала EVM у
|
||||||
|
|
||||||
|
слабого сигнала почти до нуля. Теперь слабый шумный сигнал обязан дать
|
||||||
|
заметную EVM, а не подделку под идеальный приём.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rng = np.random.default_rng(2)
|
||||||
|
reference = _reference()
|
||||||
|
noise = 0.5 * (rng.standard_normal(len(reference)) + 1j * rng.standard_normal(len(reference)))
|
||||||
|
weak_noisy = 0.001 * (reference + noise)
|
||||||
|
|
||||||
|
assert _error_vector_magnitude(weak_noisy, reference) > 0.1
|
||||||
|
|
||||||
|
|
||||||
|
def test_evm_is_not_a_number_when_it_cannot_be_computed() -> None:
|
||||||
|
reference = _reference()
|
||||||
|
assert math.isnan(_error_vector_magnitude(reference[:10], reference))
|
||||||
|
assert math.isnan(_error_vector_magnitude(np.zeros(0, dtype=complex), np.zeros(0, dtype=complex)))
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ состав файлов
|
||||||
|
|
||||||
|
|
||||||
|
def _metrics(**overrides) -> SimpleNamespace:
|
||||||
|
base = dict(
|
||||||
|
image_reassembled=True,
|
||||||
|
image_identical=True,
|
||||||
|
fragments_recovered=10,
|
||||||
|
fragment_count=10,
|
||||||
|
)
|
||||||
|
base.update(overrides)
|
||||||
|
return SimpleNamespace(**base)
|
||||||
|
|
||||||
|
|
||||||
|
def test_received_image_listed_only_when_reassembled() -> None:
|
||||||
|
with_image = created_files(_metrics(image_reassembled=True))
|
||||||
|
without_image = created_files(_metrics(image_reassembled=False, image_identical=False))
|
||||||
|
|
||||||
|
assert RECEIVED_IMAGE_PATH in with_image
|
||||||
|
assert RECEIVED_IMAGE_PATH not in without_image
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ критерий и код возврата
|
||||||
|
|
||||||
|
|
||||||
|
def _all_passed() -> tuple[FunctionalTestResult, ...]:
|
||||||
|
return tuple(FunctionalTestResult(f"{index:02d}", True, "ок") for index in range(11))
|
||||||
|
|
||||||
|
|
||||||
|
def test_criteria_met_on_a_clean_transfer() -> None:
|
||||||
|
assert transfer_meets_criteria(_metrics(), _all_passed()) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_criteria_failed_when_image_differs() -> None:
|
||||||
|
assert transfer_meets_criteria(_metrics(image_identical=False), _all_passed()) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_criteria_failed_when_a_fragment_is_missing() -> None:
|
||||||
|
metrics = _metrics(image_identical=False, fragments_recovered=9)
|
||||||
|
assert transfer_meets_criteria(metrics, _all_passed()) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_criteria_failed_when_a_functional_check_failed() -> None:
|
||||||
|
tests = _all_passed()[:-1] + (FunctionalTestResult("bad", False, "провал"),)
|
||||||
|
assert transfer_meets_criteria(_metrics(), tests) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_criteria_failed_without_any_tests() -> None:
|
||||||
|
assert transfer_meets_criteria(_metrics(), ()) is False
|
||||||
891
tests/test_lab043_calibration.py
Normal file
891
tests/test_lab043_calibration.py
Normal file
@@ -0,0 +1,891 @@
|
|||||||
|
"""Быстрые синтетические проверки программной части Lab043."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
from scipy.signal import fftconvolve
|
||||||
|
|
||||||
|
from experiments import lab043_pluto_to_rtlsdr as lab043
|
||||||
|
from experiments import lab043_prbs11_hardware as lab043_hardware
|
||||||
|
from protocol import bpsk_radio as radio
|
||||||
|
from protocol.image_fragments import split_image_bytes
|
||||||
|
from protocol.packet import CRCError, MESSAGE_TYPE_TEXT, build_packet, parse_packet
|
||||||
|
|
||||||
|
|
||||||
|
def test_receive_duration_is_derived_from_actual_transmit_length() -> None:
|
||||||
|
assert lab043.receive_duration_seconds(4_800_000, 2_400_000.0) == 3.0
|
||||||
|
assert lab043.receive_sample_count(4_800_000, 2_400_000.0, 2_399_900.0) == math.ceil(
|
||||||
|
3.0 * 2_399_900.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_continuous_blocks_are_joined_once_and_trimmed_at_end() -> None:
|
||||||
|
blocks = (
|
||||||
|
np.asarray([0, 1, 2], dtype=np.complex64),
|
||||||
|
np.asarray([3, 4, 5], dtype=np.complex64),
|
||||||
|
)
|
||||||
|
combined = lab043.combine_continuous_blocks(blocks, expected_sample_count=5)
|
||||||
|
assert np.array_equal(combined.real, np.arange(5))
|
||||||
|
|
||||||
|
|
||||||
|
def test_short_continuous_capture_is_rejected() -> None:
|
||||||
|
with pytest.raises(ValueError, match="короче"):
|
||||||
|
lab043.combine_continuous_blocks((np.zeros(9),), expected_sample_count=10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_collector_preserves_order_trims_target_and_records_boundaries() -> None:
|
||||||
|
collector = lab043.ContinuousAsyncIqCollector(
|
||||||
|
expected_sample_count=7,
|
||||||
|
warmup_callback_count=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not collector.add_callback(0, np.asarray([100, 101], dtype=complex))
|
||||||
|
assert not collector.add_callback(1, np.asarray([0, 1, 2], dtype=complex))
|
||||||
|
assert not collector.add_callback(2, np.asarray([3, 4, 5], dtype=complex))
|
||||||
|
assert collector.add_callback(3, np.asarray([6, 7, 8], dtype=complex))
|
||||||
|
|
||||||
|
combined = collector.finalize()
|
||||||
|
assert np.array_equal(combined.real, np.arange(7))
|
||||||
|
assert len(combined) == 7
|
||||||
|
|
||||||
|
boundaries = collector.callback_boundaries
|
||||||
|
assert [row.callback_index for row in boundaries] == [0, 1, 2, 3]
|
||||||
|
assert [row.accepted_start_sample for row in boundaries] == [0, 0, 3, 6]
|
||||||
|
assert [row.accepted_end_sample for row in boundaries] == [0, 3, 6, 7]
|
||||||
|
assert boundaries[0].warmup_discarded
|
||||||
|
assert boundaries[-1].trimmed_at_target
|
||||||
|
assert boundaries[-1].source_sample_count == 3
|
||||||
|
assert boundaries[-1].accepted_sample_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_collector_rejects_duplicate_callback() -> None:
|
||||||
|
collector = lab043.ContinuousAsyncIqCollector(4, warmup_callback_count=0)
|
||||||
|
collector.add_callback(0, np.asarray([0, 1], dtype=complex))
|
||||||
|
with pytest.raises(ValueError, match="продублирован"):
|
||||||
|
collector.add_callback(0, np.asarray([2, 3], dtype=complex))
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_collector_rejects_missing_callback() -> None:
|
||||||
|
collector = lab043.ContinuousAsyncIqCollector(4, warmup_callback_count=0)
|
||||||
|
collector.add_callback(0, np.asarray([0, 1], dtype=complex))
|
||||||
|
with pytest.raises(ValueError, match="пропуск"):
|
||||||
|
collector.add_callback(2, np.asarray([2, 3], dtype=complex))
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_collector_rejects_early_finalize_and_extra_callback() -> None:
|
||||||
|
collector = lab043.ContinuousAsyncIqCollector(2, warmup_callback_count=0)
|
||||||
|
collector.add_callback(0, np.asarray([0], dtype=complex))
|
||||||
|
with pytest.raises(RuntimeError, match="короче"):
|
||||||
|
collector.finalize()
|
||||||
|
assert collector.add_callback(1, np.asarray([1], dtype=complex))
|
||||||
|
with pytest.raises(RuntimeError, match="уже набрал"):
|
||||||
|
collector.add_callback(2, np.asarray([2], dtype=complex))
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_concatenation_adds_no_lab043_gap() -> None:
|
||||||
|
first = lab043.shape_frame_like_lab042(np.ones(16, dtype=complex), samples_per_symbol=8)
|
||||||
|
second = lab043.shape_frame_like_lab042(-np.ones(12, dtype=complex), samples_per_symbol=8)
|
||||||
|
continuous = lab043.concatenate_frames_without_new_gaps((first, second))
|
||||||
|
assert len(continuous) == len(first) + len(second)
|
||||||
|
assert np.array_equal(continuous[: len(first)], first)
|
||||||
|
assert np.array_equal(continuous[len(first) :], second)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spectrum_method_finds_two_strong_tones_and_cfo() -> None:
|
||||||
|
sample_rate_hz = 2_400_000.0
|
||||||
|
sample_count = 2 * lab043.CALIBRATION_NFFT
|
||||||
|
indexes = np.arange(sample_count, dtype=np.float64)
|
||||||
|
carrier_offset_hz = 1_250.0
|
||||||
|
clock_scale = 1.0 + 100.0e-6
|
||||||
|
low_hz = carrier_offset_hz - lab043.F_CAL_HZ * clock_scale
|
||||||
|
high_hz = carrier_offset_hz + lab043.F_CAL_HZ * clock_scale
|
||||||
|
random_generator = np.random.default_rng(43)
|
||||||
|
samples = (
|
||||||
|
np.exp(1j * 2.0 * np.pi * low_hz * indexes / sample_rate_hz)
|
||||||
|
+ 0.8 * np.exp(1j * 2.0 * np.pi * high_hz * indexes / sample_rate_hz)
|
||||||
|
+ 0.002
|
||||||
|
* (
|
||||||
|
random_generator.standard_normal(sample_count)
|
||||||
|
+ 1j * random_generator.standard_normal(sample_count)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
estimate = lab043.estimate_calibration(samples, sample_rate_hz)
|
||||||
|
|
||||||
|
assert estimate.valid
|
||||||
|
assert estimate.low_peak_excess_db > 10.0
|
||||||
|
assert estimate.high_peak_excess_db > 10.0
|
||||||
|
assert math.isclose(estimate.carrier_offset_hz, carrier_offset_hz, abs_tol=5.0)
|
||||||
|
assert math.isclose(estimate.sample_clock_error_ppm, 100.0, abs_tol=60.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _synthetic_two_tones(
|
||||||
|
low_hz: float,
|
||||||
|
high_hz: float,
|
||||||
|
seed: int,
|
||||||
|
) -> tuple[np.ndarray, float]:
|
||||||
|
sample_rate_hz = 240_000.0
|
||||||
|
sample_count = 120_000
|
||||||
|
indexes = np.arange(sample_count, dtype=np.float64)
|
||||||
|
random_generator = np.random.default_rng(seed)
|
||||||
|
samples = (
|
||||||
|
np.exp(1j * 2.0 * np.pi * low_hz * indexes / sample_rate_hz)
|
||||||
|
+ 0.8 * np.exp(1j * 2.0 * np.pi * high_hz * indexes / sample_rate_hz)
|
||||||
|
+ 0.003
|
||||||
|
* (
|
||||||
|
random_generator.standard_normal(sample_count)
|
||||||
|
+ 1j * random_generator.standard_normal(sample_count)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return samples, sample_rate_hz
|
||||||
|
|
||||||
|
|
||||||
|
def _phase_refinement_from_spectrum(
|
||||||
|
samples: np.ndarray,
|
||||||
|
sample_rate_hz: float,
|
||||||
|
) -> tuple[lab043.CalibrationResult, radio.TwoTonePhaseRefinement]:
|
||||||
|
spectrum_estimate = lab043.estimate_calibration(samples, sample_rate_hz)
|
||||||
|
assert spectrum_estimate.valid
|
||||||
|
phase_estimate = radio.refine_two_tone_frequencies_from_phase(
|
||||||
|
samples,
|
||||||
|
sample_rate_hz,
|
||||||
|
spectrum_estimate.f_low_hz,
|
||||||
|
spectrum_estimate.f_high_hz,
|
||||||
|
block_samples=240,
|
||||||
|
hop_samples=120,
|
||||||
|
)
|
||||||
|
return spectrum_estimate, phase_estimate
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"tone_shift_hz",
|
||||||
|
[-1.0, -0.5, -0.25, -0.1, 0.1, 0.25, 0.5, 1.0],
|
||||||
|
)
|
||||||
|
def test_phase_refinement_improves_known_sub_hertz_common_shift(
|
||||||
|
tone_shift_hz: float,
|
||||||
|
) -> None:
|
||||||
|
true_low_hz = -lab043.F_CAL_HZ + tone_shift_hz
|
||||||
|
true_high_hz = lab043.F_CAL_HZ + tone_shift_hz
|
||||||
|
samples, sample_rate_hz = _synthetic_two_tones(
|
||||||
|
true_low_hz,
|
||||||
|
true_high_hz,
|
||||||
|
seed=43_000 + int((tone_shift_hz + 2.0) * 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
old, refined = _phase_refinement_from_spectrum(samples, sample_rate_hz)
|
||||||
|
old_max_error_hz = max(
|
||||||
|
abs(old.f_low_hz - true_low_hz),
|
||||||
|
abs(old.f_high_hz - true_high_hz),
|
||||||
|
)
|
||||||
|
refined_max_error_hz = max(
|
||||||
|
abs(refined.low_frequency_hz - true_low_hz),
|
||||||
|
abs(refined.high_frequency_hz - true_high_hz),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert refined_max_error_hz < 0.001
|
||||||
|
assert refined_max_error_hz < old_max_error_hz
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"half_spacing_shift_hz",
|
||||||
|
[-1.0, -0.5, -0.25, -0.1, 0.1, 0.25, 0.5, 1.0],
|
||||||
|
)
|
||||||
|
def test_phase_refinement_improves_known_sub_hertz_spacing_shift(
|
||||||
|
half_spacing_shift_hz: float,
|
||||||
|
) -> None:
|
||||||
|
true_low_hz = -lab043.F_CAL_HZ - half_spacing_shift_hz
|
||||||
|
true_high_hz = lab043.F_CAL_HZ + half_spacing_shift_hz
|
||||||
|
true_offsets = radio.estimate_two_tone_offsets(
|
||||||
|
true_low_hz,
|
||||||
|
true_high_hz,
|
||||||
|
lab043.F_CAL_HZ,
|
||||||
|
)
|
||||||
|
samples, sample_rate_hz = _synthetic_two_tones(
|
||||||
|
true_low_hz,
|
||||||
|
true_high_hz,
|
||||||
|
seed=44_000 + int((half_spacing_shift_hz + 2.0) * 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
old, refined = _phase_refinement_from_spectrum(samples, sample_rate_hz)
|
||||||
|
refined_offsets = radio.estimate_two_tone_offsets(
|
||||||
|
refined.low_frequency_hz,
|
||||||
|
refined.high_frequency_hz,
|
||||||
|
lab043.F_CAL_HZ,
|
||||||
|
)
|
||||||
|
old_error_ppm = abs(
|
||||||
|
old.sample_clock_error_ppm - true_offsets["sample_clock_error_ppm"]
|
||||||
|
)
|
||||||
|
refined_error_ppm = abs(
|
||||||
|
refined_offsets["sample_clock_error_ppm"]
|
||||||
|
- true_offsets["sample_clock_error_ppm"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert refined_error_ppm < 0.001
|
||||||
|
assert refined_error_ppm < old_error_ppm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("sample_clock_error_ppm", [100.0, -100.0])
|
||||||
|
def test_public_refined_calibration_returns_complete_contract(
|
||||||
|
sample_clock_error_ppm: float,
|
||||||
|
) -> None:
|
||||||
|
carrier_offset_hz = 1_234.5
|
||||||
|
scale = 1.0 + sample_clock_error_ppm * 1e-6
|
||||||
|
samples, sample_rate_hz = _synthetic_two_tones(
|
||||||
|
carrier_offset_hz - lab043.F_CAL_HZ * scale,
|
||||||
|
carrier_offset_hz + lab043.F_CAL_HZ * scale,
|
||||||
|
seed=45_000 + int(sample_clock_error_ppm),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = lab043.estimate_refined_calibration(samples, sample_rate_hz)
|
||||||
|
|
||||||
|
assert isinstance(result, lab043.CalibrationResult)
|
||||||
|
assert result.valid
|
||||||
|
assert result.invalid_reason == ""
|
||||||
|
assert math.isclose(result.carrier_offset_hz, carrier_offset_hz, abs_tol=0.01)
|
||||||
|
assert math.isclose(
|
||||||
|
result.sample_clock_error_ppm,
|
||||||
|
sample_clock_error_ppm,
|
||||||
|
abs_tol=0.01,
|
||||||
|
)
|
||||||
|
assert math.isfinite(result.residual_cfo_hz)
|
||||||
|
assert math.isfinite(result.phase_fit_rmse_rad)
|
||||||
|
assert result.peak_margin_low_db >= lab043.MINIMUM_TONE_EXCESS_DB
|
||||||
|
assert result.peak_margin_high_db >= lab043.MINIMUM_TONE_EXCESS_DB
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_refined_calibration_rejects_weak_or_missing_tone_without_exception() -> None:
|
||||||
|
sample_rate_hz = 240_000.0
|
||||||
|
sample_count = 120_000
|
||||||
|
indexes = np.arange(sample_count, dtype=np.float64)
|
||||||
|
random_generator = np.random.default_rng(46_000)
|
||||||
|
noise = 0.003 * (
|
||||||
|
random_generator.standard_normal(sample_count)
|
||||||
|
+ 1j * random_generator.standard_normal(sample_count)
|
||||||
|
)
|
||||||
|
one_tone = np.exp(1j * 2.0 * np.pi * lab043.F_CAL_HZ * indexes / sample_rate_hz)
|
||||||
|
|
||||||
|
weak = lab043.estimate_refined_calibration(noise, sample_rate_hz)
|
||||||
|
missing = lab043.estimate_refined_calibration(one_tone + noise, sample_rate_hz)
|
||||||
|
|
||||||
|
for result in (weak, missing):
|
||||||
|
assert isinstance(result, lab043.CalibrationResult)
|
||||||
|
assert not result.valid
|
||||||
|
assert result.invalid_reason
|
||||||
|
assert math.isnan(result.f_low_hz)
|
||||||
|
assert math.isnan(result.f_high_hz)
|
||||||
|
assert math.isnan(result.carrier_offset_hz)
|
||||||
|
assert math.isnan(result.sample_clock_error_ppm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tones_below_ten_decibels_are_rejected_with_nan() -> None:
|
||||||
|
frequencies = np.linspace(-100_000.0, 100_000.0, 4001)
|
||||||
|
powers = np.ones_like(frequencies)
|
||||||
|
powers[np.argmin(np.abs(frequencies + lab043.F_CAL_HZ))] = 9.0
|
||||||
|
powers[np.argmin(np.abs(frequencies - lab043.F_CAL_HZ))] = 9.0
|
||||||
|
|
||||||
|
estimate = lab043.estimate_calibration_from_spectrum(frequencies, powers)
|
||||||
|
|
||||||
|
assert not estimate.valid
|
||||||
|
assert math.isnan(estimate.f_low_hz)
|
||||||
|
assert math.isnan(estimate.f_high_hz)
|
||||||
|
assert math.isnan(estimate.carrier_offset_hz)
|
||||||
|
assert estimate.failure_reason
|
||||||
|
|
||||||
|
|
||||||
|
def _estimate(carrier_offset_hz: float, valid: bool = True) -> lab043.CalibrationResult:
|
||||||
|
if not valid:
|
||||||
|
return lab043._invalid_calibration(
|
||||||
|
"нет тонов",
|
||||||
|
noise_power=1.0,
|
||||||
|
)
|
||||||
|
return lab043.CalibrationResult(
|
||||||
|
valid=True,
|
||||||
|
invalid_reason="",
|
||||||
|
f_low_hz=-50_000.0 + carrier_offset_hz,
|
||||||
|
f_high_hz=50_000.0 + carrier_offset_hz,
|
||||||
|
carrier_offset_hz=carrier_offset_hz,
|
||||||
|
carrier_offset_ppm=carrier_offset_hz / lab043.CARRIER_HZ * 1e6,
|
||||||
|
clock_scale=1.0,
|
||||||
|
sample_clock_error_ppm=0.0,
|
||||||
|
residual_cfo_hz=0.0,
|
||||||
|
phase_fit_rmse_rad=0.0,
|
||||||
|
peak_margin_low_db=20.0,
|
||||||
|
peak_margin_high_db=21.0,
|
||||||
|
noise_power=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_three_calibrations_keep_individual_failures_and_finite_statistics() -> None:
|
||||||
|
summary = lab043.summarize_calibrations((_estimate(10.0), _estimate(14.0), _estimate(0, False)))
|
||||||
|
|
||||||
|
assert summary["capture_count"] == 3
|
||||||
|
assert summary["failure_count"] == 1
|
||||||
|
carrier = summary["carrier_offset_hz"]
|
||||||
|
assert carrier["mean"] == 12.0
|
||||||
|
assert carrier["minimum"] == 10.0
|
||||||
|
assert carrier["maximum"] == 14.0
|
||||||
|
assert carrier["standard_deviation"] == 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_invalid_calibrations_keep_nan_aggregates() -> None:
|
||||||
|
summary = lab043.summarize_calibrations((_estimate(0, False),) * 3)
|
||||||
|
assert summary["failure_count"] == 3
|
||||||
|
assert math.isnan(summary["carrier_offset_hz"]["mean"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibration_summary_requires_exactly_three_captures() -> None:
|
||||||
|
with pytest.raises(ValueError, match="ровно три"):
|
||||||
|
lab043.summarize_calibrations((_estimate(10.0), _estimate(12.0)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_prbs11_has_full_2047_bit_period() -> None:
|
||||||
|
sequence = lab043.prbs11(2 * lab043.PRBS11_LENGTH)
|
||||||
|
first = sequence[: lab043.PRBS11_LENGTH]
|
||||||
|
second = sequence[lab043.PRBS11_LENGTH :]
|
||||||
|
|
||||||
|
assert np.array_equal(first, second)
|
||||||
|
assert not np.array_equal(first, np.roll(first, 23))
|
||||||
|
assert not np.array_equal(first, np.roll(first, 89))
|
||||||
|
|
||||||
|
|
||||||
|
def test_separate_pilot_is_fixed_balanced_and_independent_from_prbs11() -> None:
|
||||||
|
pilot = lab043.pilot_prbs7()
|
||||||
|
first_period = pilot[:127]
|
||||||
|
second_period = pilot[127:254]
|
||||||
|
|
||||||
|
assert len(pilot) == 1_280
|
||||||
|
assert np.array_equal(first_period, second_period)
|
||||||
|
assert int(np.count_nonzero(first_period)) == 64
|
||||||
|
assert not np.array_equal(pilot, lab043.prbs11(len(pilot)))
|
||||||
|
longest_run = max(
|
||||||
|
len(group)
|
||||||
|
for group in np.split(pilot, np.flatnonzero(np.diff(pilot)) + 1)
|
||||||
|
)
|
||||||
|
assert longest_run <= 7
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"carrier_offset_hz",
|
||||||
|
[-10.0, -5.0, -2.0, -1.2, -1.0, -0.5, 0.5, 1.0, 1.2, 2.0, 5.0, 10.0],
|
||||||
|
)
|
||||||
|
def test_fixed_pilot_estimator_statistics_match_hardware_s_phase_dispersion(
|
||||||
|
carrier_offset_hz: float,
|
||||||
|
) -> None:
|
||||||
|
known = radio.bpsk_modulate(lab043.pilot_prbs7())
|
||||||
|
indexes = np.arange(len(known), dtype=np.float64)
|
||||||
|
estimates: list[float] = []
|
||||||
|
for repetition in range(96):
|
||||||
|
random_generator = np.random.default_rng(
|
||||||
|
43_043_000
|
||||||
|
+ int(round((carrier_offset_hz + 20.0) * 1_000.0))
|
||||||
|
+ repetition
|
||||||
|
)
|
||||||
|
phase_noise = random_generator.normal(0.0, 0.4691, len(known))
|
||||||
|
received = known * np.exp(
|
||||||
|
1j
|
||||||
|
* (
|
||||||
|
2.0 * np.pi * carrier_offset_hz * indexes / lab043.SYMBOL_RATE
|
||||||
|
+ phase_noise
|
||||||
|
)
|
||||||
|
)
|
||||||
|
estimate = radio.estimate_known_pilot_carrier(received, known)
|
||||||
|
assert estimate.valid
|
||||||
|
estimates.append(estimate.frequency_hz)
|
||||||
|
|
||||||
|
errors = np.asarray(estimates) - carrier_offset_hz
|
||||||
|
assert np.all(np.sign(estimates) == np.sign(carrier_offset_hz))
|
||||||
|
assert abs(float(np.mean(errors))) < 0.08
|
||||||
|
assert float(np.std(errors, ddof=1)) < 0.18
|
||||||
|
assert float(np.percentile(np.abs(errors), 95.0)) < 0.35
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixed_pilot_estimator_rejects_noise_only_input() -> None:
|
||||||
|
random_generator = np.random.default_rng(43_043)
|
||||||
|
known = radio.bpsk_modulate(lab043.pilot_prbs7())
|
||||||
|
noise = (
|
||||||
|
random_generator.normal(0.0, 1.0, len(known))
|
||||||
|
+ 1j * random_generator.normal(0.0, 1.0, len(known))
|
||||||
|
)
|
||||||
|
|
||||||
|
estimate = radio.estimate_known_pilot_carrier(noise, known)
|
||||||
|
|
||||||
|
assert not estimate.valid
|
||||||
|
assert math.isnan(estimate.frequency_hz)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("label", "duration_seconds", "expected_bits"),
|
||||||
|
[("S", 0.25, 5_000), ("L", 2.0, 40_000)],
|
||||||
|
)
|
||||||
|
def test_prbs_transmission_plan_is_fixed_and_has_one_initial_marker(
|
||||||
|
label: str,
|
||||||
|
duration_seconds: float,
|
||||||
|
expected_bits: int,
|
||||||
|
) -> None:
|
||||||
|
plan = lab043.build_prbs11_transmission_plan(label, duration_seconds)
|
||||||
|
|
||||||
|
assert len(plan.payload_bits) == expected_bits
|
||||||
|
assert np.array_equal(
|
||||||
|
plan.payload_bits,
|
||||||
|
lab043.prbs11(expected_bits, lab043.PRBS11_INITIAL_STATE),
|
||||||
|
)
|
||||||
|
assert plan.bpsk_start_sample == (
|
||||||
|
plan.calibration_sample_count + plan.fixed_guard_sample_count
|
||||||
|
)
|
||||||
|
assert plan.calibration_sample_count == 600_000
|
||||||
|
assert plan.fixed_guard_sample_count == 240_000
|
||||||
|
assert np.max(np.abs(plan.tx_samples)) <= 1.0
|
||||||
|
if label == "S":
|
||||||
|
assert len(plan.pilot_bits) == lab043.PILOT_SYMBOL_COUNT
|
||||||
|
assert len(plan.pilot_bits) / lab043.SYMBOL_RATE == 0.064
|
||||||
|
else:
|
||||||
|
assert len(plan.pilot_bits) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_noncyclic_tx_buffer_is_held_for_full_waveform_duration() -> None:
|
||||||
|
events: list[object] = []
|
||||||
|
|
||||||
|
class FakePluto:
|
||||||
|
def tx_destroy_buffer(self) -> None:
|
||||||
|
events.append("destroy")
|
||||||
|
|
||||||
|
def tx(self, samples: np.ndarray) -> None:
|
||||||
|
events.append(("tx", samples.copy()))
|
||||||
|
|
||||||
|
samples = np.arange(12, dtype=np.float32).astype(np.complex64)
|
||||||
|
duration = lab043_hardware.transmit_noncyclic_buffer_once(
|
||||||
|
FakePluto(),
|
||||||
|
samples,
|
||||||
|
actual_sample_rate_hz=48.0,
|
||||||
|
sleep_function=lambda seconds: events.append(("sleep", seconds)),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert duration == 0.25
|
||||||
|
assert events[0] == "destroy"
|
||||||
|
assert events[1][0] == "tx"
|
||||||
|
assert np.array_equal(events[1][1], samples)
|
||||||
|
assert events[2] == ("sleep", 0.25)
|
||||||
|
assert events[3] == "destroy"
|
||||||
|
|
||||||
|
|
||||||
|
def _synthetic_prbs_channel(
|
||||||
|
transmitted: np.ndarray,
|
||||||
|
sample_rate_hz: float,
|
||||||
|
carrier_offset_hz: float,
|
||||||
|
clock_scale: float,
|
||||||
|
) -> np.ndarray:
|
||||||
|
received_length = max(1, math.floor(len(transmitted) / clock_scale))
|
||||||
|
receive_indexes = np.arange(received_length, dtype=np.float64)
|
||||||
|
source_positions = np.minimum(receive_indexes * clock_scale, len(transmitted) - 1.0)
|
||||||
|
source_indexes = np.arange(len(transmitted), dtype=np.float64)
|
||||||
|
received = np.interp(source_positions, source_indexes, transmitted.real) + 1j * np.interp(
|
||||||
|
source_positions,
|
||||||
|
source_indexes,
|
||||||
|
transmitted.imag,
|
||||||
|
)
|
||||||
|
return received * np.exp(
|
||||||
|
1j * 2.0 * np.pi * carrier_offset_hz * receive_indexes / sample_rate_hz
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prbs_modes_measure_drift_and_resampling_reduces_it() -> None:
|
||||||
|
samples_per_symbol = 16
|
||||||
|
sample_rate_hz = lab043.SYMBOL_RATE * samples_per_symbol
|
||||||
|
clock_scale = 1.0 - 100.0e-6
|
||||||
|
carrier_offset_hz = 730.0
|
||||||
|
plan = lab043.build_prbs11_transmission_plan(
|
||||||
|
"S",
|
||||||
|
0.25,
|
||||||
|
sample_rate_hz=sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
)
|
||||||
|
transmitted_bpsk = plan.tx_samples[plan.bpsk_start_sample :]
|
||||||
|
received = _synthetic_prbs_channel(
|
||||||
|
transmitted_bpsk,
|
||||||
|
sample_rate_hz,
|
||||||
|
carrier_offset_hz,
|
||||||
|
clock_scale,
|
||||||
|
)
|
||||||
|
|
||||||
|
modes = lab043.analyze_prbs_modes(
|
||||||
|
received,
|
||||||
|
plan.payload_bits,
|
||||||
|
carrier_offset_hz,
|
||||||
|
clock_scale,
|
||||||
|
sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert modes["B"].detected
|
||||||
|
assert modes["C"].detected
|
||||||
|
assert modes["D"].detected
|
||||||
|
assert modes["B"].matched_bit_count == len(plan.payload_bits)
|
||||||
|
assert modes["D"].bit_error_rate == 0.0
|
||||||
|
assert modes["B"].timing_sro_ppm < 0.0
|
||||||
|
assert math.isclose(modes["B"].timing_sro_ppm, -100.0, abs_tol=40.0)
|
||||||
|
assert abs(modes["D"].accumulated_timing_drift_samples) < abs(
|
||||||
|
modes["B"].accumulated_timing_drift_samples
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_pilot_correction_and_unknown_prbs_follow_one_software_path() -> None:
|
||||||
|
samples_per_symbol = 16
|
||||||
|
sample_rate_hz = lab043.SYMBOL_RATE * samples_per_symbol
|
||||||
|
coarse_cfo_hz = 700.0
|
||||||
|
residual_cfo_hz = 1.2
|
||||||
|
plan = lab043.build_prbs11_transmission_plan(
|
||||||
|
"S",
|
||||||
|
0.25,
|
||||||
|
sample_rate_hz=sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
)
|
||||||
|
transmitted_bpsk = plan.tx_samples[plan.bpsk_start_sample :]
|
||||||
|
received = _synthetic_prbs_channel(
|
||||||
|
transmitted_bpsk,
|
||||||
|
sample_rate_hz,
|
||||||
|
coarse_cfo_hz + residual_cfo_hz,
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
modes = lab043.analyze_prbs_modes(
|
||||||
|
received,
|
||||||
|
plan.payload_bits,
|
||||||
|
coarse_cfo_hz,
|
||||||
|
1.0,
|
||||||
|
sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
known_pilot_bits=plan.pilot_bits,
|
||||||
|
)
|
||||||
|
wrong_reference_modes = lab043.analyze_prbs_modes(
|
||||||
|
received,
|
||||||
|
np.zeros_like(plan.payload_bits),
|
||||||
|
coarse_cfo_hz,
|
||||||
|
1.0,
|
||||||
|
sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
known_pilot_bits=plan.pilot_bits,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert set(modes) == {"A", "B", "C", "D"}
|
||||||
|
assert modes["C"].pilot_estimate_valid
|
||||||
|
assert modes["C"].pilot_cfo_applied
|
||||||
|
assert math.isclose(
|
||||||
|
modes["C"].estimated_pilot_cfo_hz,
|
||||||
|
residual_cfo_hz,
|
||||||
|
abs_tol=0.25,
|
||||||
|
)
|
||||||
|
assert abs(modes["C"].residual_pilot_cfo_hz) < 0.05
|
||||||
|
assert modes["C"].bit_error_rate < modes["B"].bit_error_rate
|
||||||
|
assert modes["C"].bit_error_rate == 0.0
|
||||||
|
assert math.isclose(
|
||||||
|
wrong_reference_modes["C"].estimated_pilot_cfo_hz,
|
||||||
|
modes["C"].estimated_pilot_cfo_hz,
|
||||||
|
abs_tol=1e-12,
|
||||||
|
)
|
||||||
|
assert wrong_reference_modes["C"].bit_error_rate > 0.4
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibration_and_bpsk_are_split_from_the_same_capture() -> None:
|
||||||
|
samples_per_symbol = 16
|
||||||
|
sample_rate_hz = lab043.SYMBOL_RATE * samples_per_symbol
|
||||||
|
plan = lab043.build_prbs11_transmission_plan(
|
||||||
|
"S",
|
||||||
|
0.025,
|
||||||
|
sample_rate_hz=sample_rate_hz,
|
||||||
|
samples_per_symbol=samples_per_symbol,
|
||||||
|
)
|
||||||
|
leading = np.zeros(round(0.5 * sample_rate_hz), dtype=np.complex128)
|
||||||
|
trailing = np.zeros(round(0.5 * sample_rate_hz), dtype=np.complex128)
|
||||||
|
capture = np.concatenate((leading, plan.tx_samples, trailing))
|
||||||
|
|
||||||
|
calibration, bpsk, sections = lab043.split_calibration_and_bpsk(
|
||||||
|
capture,
|
||||||
|
plan,
|
||||||
|
sample_rate_hz,
|
||||||
|
sample_rate_hz,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(calibration) > 0.15 * sample_rate_hz
|
||||||
|
assert len(bpsk) > len(plan.tx_samples) - plan.bpsk_start_sample
|
||||||
|
assert sections["calibration_end_sample"] < sections["bpsk_nominal_start_sample"]
|
||||||
|
|
||||||
|
|
||||||
|
def _continuous_synthetic_capture(
|
||||||
|
plan: lab043.PrbsTransmissionPlan,
|
||||||
|
tx_samples: np.ndarray | None = None,
|
||||||
|
) -> np.ndarray:
|
||||||
|
leading = np.zeros(
|
||||||
|
round(lab043.RX_LEADING_MARGIN_SECONDS * lab043.SAMPLE_RATE_HZ),
|
||||||
|
dtype=np.complex64,
|
||||||
|
)
|
||||||
|
trailing = np.zeros(
|
||||||
|
round(lab043.RX_TRAILING_MARGIN_SECONDS * lab043.SAMPLE_RATE_HZ),
|
||||||
|
dtype=np.complex64,
|
||||||
|
)
|
||||||
|
waveform = plan.tx_samples if tx_samples is None else tx_samples
|
||||||
|
return np.concatenate((leading, waveform.astype(np.complex64), trailing))
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_short_s_processing_path_returns_calibration_and_modes() -> None:
|
||||||
|
plan = lab043.build_prbs11_transmission_plan("S", 0.025)
|
||||||
|
capture = _continuous_synthetic_capture(plan)
|
||||||
|
|
||||||
|
result = lab043_hardware.analyze_captured_prbs(
|
||||||
|
capture,
|
||||||
|
plan,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.processing_success
|
||||||
|
assert result.calibration.valid
|
||||||
|
assert set(result.modes) == {"A", "B", "C", "D"}
|
||||||
|
assert result.modes["D"].detected
|
||||||
|
assert result.modes["D"].matched_bit_count == len(plan.payload_bits)
|
||||||
|
assert result.modes["D"].bit_error_rate == 0.0
|
||||||
|
assert math.isfinite(result.modes["D"].evm_percent)
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_short_s_missing_prbs_is_not_reported_as_zero_ber() -> None:
|
||||||
|
plan = lab043.build_prbs11_transmission_plan("S", 0.025)
|
||||||
|
without_prbs = plan.tx_samples.copy()
|
||||||
|
without_prbs[plan.bpsk_start_sample :] = 0.0
|
||||||
|
capture = _continuous_synthetic_capture(plan, without_prbs)
|
||||||
|
|
||||||
|
result = lab043_hardware.analyze_captured_prbs(
|
||||||
|
capture,
|
||||||
|
plan,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.calibration.valid
|
||||||
|
assert not result.processing_success
|
||||||
|
assert not result.modes["D"].detected
|
||||||
|
assert math.isnan(result.modes["D"].bit_error_rate)
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_short_s_invalid_calibration_makes_all_modes_na() -> None:
|
||||||
|
plan = lab043.build_prbs11_transmission_plan("S", 0.025)
|
||||||
|
one_tone_waveform = plan.tx_samples.copy()
|
||||||
|
indexes = np.arange(plan.calibration_sample_count, dtype=np.float64)
|
||||||
|
one_tone_waveform[: plan.calibration_sample_count] = 0.35 * np.exp(
|
||||||
|
1j * 2.0 * np.pi * lab043.F_CAL_HZ * indexes / lab043.SAMPLE_RATE_HZ
|
||||||
|
)
|
||||||
|
capture = _continuous_synthetic_capture(plan, one_tone_waveform)
|
||||||
|
|
||||||
|
result = lab043_hardware.analyze_captured_prbs(
|
||||||
|
capture,
|
||||||
|
plan,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
lab043.SAMPLE_RATE_HZ,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not result.calibration.valid
|
||||||
|
assert result.calibration.invalid_reason
|
||||||
|
assert not result.processing_success
|
||||||
|
for mode in result.modes.values():
|
||||||
|
assert not mode.detected
|
||||||
|
assert math.isnan(mode.bit_error_rate)
|
||||||
|
|
||||||
|
|
||||||
|
def test_raw_survives_processing_exception_and_json_records_failure(tmp_path) -> None:
|
||||||
|
samples = np.asarray([0.25 + 0.5j, -0.5 + 0.25j], dtype=np.complex64)
|
||||||
|
metadata = {
|
||||||
|
"capture_status": "captured",
|
||||||
|
"processing_status": "pending",
|
||||||
|
"processing_error": None,
|
||||||
|
"capture_started_utc": "2026-08-19T12:00:00Z",
|
||||||
|
"tx_parameters": {"gain_db": -30.0},
|
||||||
|
"rx_parameters": {"gain_db": None},
|
||||||
|
"waveform_sha256": "a" * 64,
|
||||||
|
"callback_count": 2,
|
||||||
|
"callback_boundaries": [{"callback_index": 0}, {"callback_index": 1}],
|
||||||
|
"tx_waveform_duration_seconds": 0.25,
|
||||||
|
"expected_intervals": {"calibration": [0.0, 0.1]},
|
||||||
|
}
|
||||||
|
|
||||||
|
iq_path, json_path, result, error = lab043.save_then_process_capture(
|
||||||
|
tmp_path / "failed_after_rx",
|
||||||
|
samples,
|
||||||
|
metadata,
|
||||||
|
lambda _samples: (_ for _ in ()).throw(RuntimeError("synthetic failure")),
|
||||||
|
)
|
||||||
|
|
||||||
|
document = json.loads(json_path.read_text(encoding="utf-8"))
|
||||||
|
assert iq_path.exists()
|
||||||
|
assert json_path.exists()
|
||||||
|
assert result is None
|
||||||
|
assert error == "RuntimeError: synthetic failure"
|
||||||
|
assert document["capture_status"] == "captured"
|
||||||
|
assert document["processing_status"] == "processing_failed"
|
||||||
|
assert document["processing_error"] == error
|
||||||
|
assert document["raw_verified"]
|
||||||
|
assert document["iq_sha256"] == hashlib.sha256(iq_path.read_bytes()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ber_is_measured_before_crc() -> None:
|
||||||
|
expected = lab043.prbs11()
|
||||||
|
received = expected.copy()
|
||||||
|
received[[0, 100, 1000]] ^= 1
|
||||||
|
|
||||||
|
errors, bit_error_rate = lab043.bit_error_rate(expected, received)
|
||||||
|
|
||||||
|
assert errors == 3
|
||||||
|
assert bit_error_rate == 3 / lab043.PRBS11_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
def test_ber_is_nan_for_truncated_known_sequence() -> None:
|
||||||
|
expected = lab043.prbs11()
|
||||||
|
errors, bit_error_rate = lab043.bit_error_rate(expected, expected[:-1])
|
||||||
|
assert errors == 0
|
||||||
|
assert math.isnan(bit_error_rate)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("initial_sample_phase", [0, 3, 7, 15])
|
||||||
|
def test_symbol_sample_phase_is_selected_separately(initial_sample_phase: int) -> None:
|
||||||
|
samples_per_symbol = 16
|
||||||
|
_, marker_symbols = radio.build_frame_marker()
|
||||||
|
taps = radio.root_raised_cosine_taps(0.35, samples_per_symbol, 10)
|
||||||
|
upsampled = np.zeros(len(marker_symbols) * samples_per_symbol, dtype=complex)
|
||||||
|
upsampled[::samples_per_symbol] = marker_symbols
|
||||||
|
transmitted = fftconvolve(upsampled, taps, mode="full")
|
||||||
|
shifted = np.concatenate((np.zeros(initial_sample_phase), transmitted))
|
||||||
|
matched = fftconvolve(shifted, taps, mode="full")
|
||||||
|
|
||||||
|
found = radio.find_radio_frame(matched, marker_symbols, samples_per_symbol)
|
||||||
|
|
||||||
|
assert found["sample_phase"] == initial_sample_phase
|
||||||
|
assert found["score"] > 0.99
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_control_packet_passes_crc() -> None:
|
||||||
|
assert lab043.control_packet_crc_roundtrip(b"known payload")
|
||||||
|
|
||||||
|
|
||||||
|
def test_corrupted_control_packet_fails_crc() -> None:
|
||||||
|
packet = bytearray(build_packet(b"known payload", MESSAGE_TYPE_TEXT, 43))
|
||||||
|
packet[-1] ^= 1
|
||||||
|
bits, _, _ = radio.build_radio_frame(bytes(packet))
|
||||||
|
recovered = radio.parse_radio_frame(bits)
|
||||||
|
assert recovered is not None
|
||||||
|
with pytest.raises(CRCError):
|
||||||
|
parse_packet(recovered)
|
||||||
|
|
||||||
|
|
||||||
|
def test_modes_a_b_c_process_the_same_capture_and_c_recovers_prbs() -> None:
|
||||||
|
clock_scale = 1.0 + 100.0e-6
|
||||||
|
capture, expected = lab043.synthesize_known_bpsk_capture(
|
||||||
|
carrier_offset_hz=730.0,
|
||||||
|
clock_scale=clock_scale,
|
||||||
|
samples_per_symbol=16,
|
||||||
|
)
|
||||||
|
|
||||||
|
results = lab043.process_bpsk_modes(
|
||||||
|
capture,
|
||||||
|
expected,
|
||||||
|
coarse_carrier_offset_hz=700.0,
|
||||||
|
clock_scale=clock_scale,
|
||||||
|
samples_per_symbol=16,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert set(results) == {"A", "B", "C"}
|
||||||
|
assert results["C"].bit_error_rate == 0.0
|
||||||
|
assert results["C"].fine_cfo_applied
|
||||||
|
assert not math.isfinite(results["A"].bit_error_rate) or (
|
||||||
|
results["A"].bit_error_rate > results["C"].bit_error_rate
|
||||||
|
)
|
||||||
|
assert results["B"].bit_error_rate > results["C"].bit_error_rate
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_jpeg_fragment_never_produces_an_image() -> None:
|
||||||
|
fragments = split_image_bytes(bytes(range(250)) * 5, image_id=43, fragment_data_size=128)
|
||||||
|
assert len(fragments) == 10
|
||||||
|
assert lab043.try_reassemble_complete_image(fragments[:-1]) is None
|
||||||
|
|
||||||
|
|
||||||
|
def _complete_acceptance() -> lab043.AcceptanceResult:
|
||||||
|
return lab043.AcceptanceResult(10, 10, 10, 10, True, True, True, True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"result",
|
||||||
|
[
|
||||||
|
lab043.AcceptanceResult(9, 10, 10, 10, True, True, True, True),
|
||||||
|
lab043.AcceptanceResult(10, 10, 9, 10, True, True, True, True),
|
||||||
|
lab043.AcceptanceResult(10, 10, 10, 9, False, False, False, True),
|
||||||
|
lab043.AcceptanceResult(10, 10, 10, 10, True, True, True, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_exit_zero_requires_every_acceptance_layer(result: lab043.AcceptanceResult) -> None:
|
||||||
|
assert lab043.exit_code_for_acceptance(result) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_exit_zero_is_allowed_for_complete_acceptance() -> None:
|
||||||
|
result = _complete_acceptance()
|
||||||
|
assert result.radio_passed
|
||||||
|
assert result.application_passed
|
||||||
|
assert lab043.exit_code_for_acceptance(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_reference_iq_format_contains_reprocessing_metadata(tmp_path) -> None:
|
||||||
|
metadata = {
|
||||||
|
"actual_sample_rate_hz": 2_400_000.0,
|
||||||
|
"center_frequency_hz": 435_000_000.0,
|
||||||
|
"rx_gain_db": 19.7,
|
||||||
|
"capture_started_utc": "2026-08-19T12:00:00Z",
|
||||||
|
"capture_order": 1,
|
||||||
|
"tx_parameters": {"gain_db": -10.0},
|
||||||
|
"calibration": {"carrier_offset_hz": 123.0, "clock_scale": 1.00002},
|
||||||
|
}
|
||||||
|
samples = np.asarray([1 + 2j, 3 + 4j], dtype=np.complex64)
|
||||||
|
|
||||||
|
iq_path, metadata_path = lab043.save_reference_iq_capture(
|
||||||
|
tmp_path / "reference",
|
||||||
|
samples,
|
||||||
|
metadata,
|
||||||
|
)
|
||||||
|
|
||||||
|
restored = np.load(iq_path, allow_pickle=False)
|
||||||
|
document = json.loads(metadata_path.read_text(encoding="utf-8"))
|
||||||
|
assert np.array_equal(restored, samples)
|
||||||
|
assert document["sample_count"] == 2
|
||||||
|
assert document["dtype"] == "complex64"
|
||||||
|
assert len(document["iq_sha256"]) == 64
|
||||||
|
assert document["calibration"]["clock_scale"] == 1.00002
|
||||||
|
|
||||||
|
|
||||||
|
def test_reference_iq_rejects_incomplete_metadata(tmp_path) -> None:
|
||||||
|
with pytest.raises(ValueError, match="Не хватает метаданных"):
|
||||||
|
lab043.save_reference_iq_capture(tmp_path / "reference", np.zeros(4), {})
|
||||||
|
|
||||||
|
|
||||||
|
def test_diagnostic_csv_txt_and_png_are_created_from_supplied_results(tmp_path) -> None:
|
||||||
|
calibrations = (_estimate(10.0), _estimate(12.0), _estimate(14.0))
|
||||||
|
capture, expected = lab043.synthesize_known_bpsk_capture(30.0, 1.0, 16)
|
||||||
|
modes = lab043.process_bpsk_modes(capture, expected, 0.0, 1.0, 16)
|
||||||
|
|
||||||
|
paths = lab043.save_diagnostic_artifacts(
|
||||||
|
tmp_path,
|
||||||
|
calibrations,
|
||||||
|
modes,
|
||||||
|
_complete_acceptance(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(paths) == 5
|
||||||
|
assert all(path.exists() and path.stat().st_size > 0 for path in paths)
|
||||||
|
summary = (tmp_path / "lab043_summary.csv").read_text(encoding="utf-8")
|
||||||
|
assert "radio_passed" in summary
|
||||||
|
assert summary.rstrip().endswith(",0")
|
||||||
|
assert (tmp_path / "lab043_calibration.png").stat().st_size > 1000
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_embedded_lab043_checks_pass_without_hardware() -> None:
|
||||||
|
results = lab043.run_functional_tests()
|
||||||
|
assert len(results) == 10
|
||||||
|
assert all(result.passed for result in results), results
|
||||||
97
tests/test_packet.py
Normal file
97
tests/test_packet.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
"""Быстрые проверки пакетного протокола: protocol/packet.py."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from protocol.packet import (
|
||||||
|
CRC_SIZE,
|
||||||
|
HEADER_SIZE,
|
||||||
|
MAX_PAYLOAD_SIZE,
|
||||||
|
MESSAGE_TYPE_ACK,
|
||||||
|
MESSAGE_TYPE_IMAGE_FRAGMENT,
|
||||||
|
MESSAGE_TYPE_TELEMETRY,
|
||||||
|
MESSAGE_TYPE_TEXT,
|
||||||
|
PROTOCOL_VERSION,
|
||||||
|
SYNC_WORD,
|
||||||
|
CRCError,
|
||||||
|
PacketError,
|
||||||
|
build_packet,
|
||||||
|
parse_packet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MESSAGE_TYPES = (
|
||||||
|
MESSAGE_TYPE_TEXT,
|
||||||
|
MESSAGE_TYPE_TELEMETRY,
|
||||||
|
MESSAGE_TYPE_IMAGE_FRAGMENT,
|
||||||
|
MESSAGE_TYPE_ACK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("message_type", MESSAGE_TYPES)
|
||||||
|
@pytest.mark.parametrize("payload", [b"", b"H", b"HELLO SDR", bytes(range(256)), b"\x00" * 1024])
|
||||||
|
def test_roundtrip_preserves_payload(payload: bytes, message_type: int) -> None:
|
||||||
|
parsed = parse_packet(build_packet(payload, message_type, 7))
|
||||||
|
assert parsed.payload == payload
|
||||||
|
assert parsed.message_type == message_type
|
||||||
|
assert parsed.sequence_number == 7
|
||||||
|
assert parsed.version == PROTOCOL_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
def test_packet_starts_with_sync_word() -> None:
|
||||||
|
packet = build_packet(b"data", MESSAGE_TYPE_TEXT, 1)
|
||||||
|
assert struct.unpack(">H", packet[:2])[0] == SYNC_WORD
|
||||||
|
|
||||||
|
|
||||||
|
def test_length_is_header_plus_payload_plus_crc() -> None:
|
||||||
|
payload = b"x" * 300
|
||||||
|
packet = build_packet(payload, MESSAGE_TYPE_TEXT, 1)
|
||||||
|
assert len(packet) == HEADER_SIZE + len(payload) + CRC_SIZE
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence_number_survives_full_range() -> None:
|
||||||
|
for sequence_number in (0, 1, 65534, 65535):
|
||||||
|
assert parse_packet(build_packet(b"a", MESSAGE_TYPE_TEXT, sequence_number)).sequence_number == sequence_number
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bit_index", [0, 7, 8, 23, 64, 100])
|
||||||
|
def test_single_bit_corruption_is_detected(bit_index: int) -> None:
|
||||||
|
"""CRC32 обязан ловить одиночное искажение бита в любом месте пакета."""
|
||||||
|
|
||||||
|
packet = bytearray(build_packet(b"HELLO SDR TELEMETRY", MESSAGE_TYPE_TELEMETRY, 3))
|
||||||
|
byte_index, offset = divmod(bit_index, 8)
|
||||||
|
assert byte_index < len(packet)
|
||||||
|
packet[byte_index] ^= 1 << offset
|
||||||
|
|
||||||
|
with pytest.raises((CRCError, PacketError)):
|
||||||
|
parse_packet(bytes(packet))
|
||||||
|
|
||||||
|
|
||||||
|
def test_truncated_packet_is_rejected() -> None:
|
||||||
|
packet = build_packet(b"HELLO", MESSAGE_TYPE_TEXT, 1)
|
||||||
|
for cut in (0, 1, HEADER_SIZE - 1, len(packet) - 1):
|
||||||
|
with pytest.raises(PacketError):
|
||||||
|
parse_packet(packet[:cut])
|
||||||
|
|
||||||
|
|
||||||
|
def test_oversized_payload_is_rejected() -> None:
|
||||||
|
with pytest.raises((PacketError, ValueError)):
|
||||||
|
build_packet(b"\x00" * (MAX_PAYLOAD_SIZE + 1), MESSAGE_TYPE_TEXT, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrong_sync_word_is_rejected() -> None:
|
||||||
|
packet = bytearray(build_packet(b"HELLO", MESSAGE_TYPE_TEXT, 1))
|
||||||
|
packet[0] ^= 0xFF
|
||||||
|
with pytest.raises(PacketError):
|
||||||
|
parse_packet(bytes(packet))
|
||||||
|
|
||||||
|
|
||||||
|
def test_payload_is_not_shared_with_caller() -> None:
|
||||||
|
"""Разбор не должен возвращать ссылку на изменяемый буфер."""
|
||||||
|
|
||||||
|
packet = bytearray(build_packet(b"HELLO", MESSAGE_TYPE_TEXT, 1))
|
||||||
|
parsed = parse_packet(bytes(packet))
|
||||||
|
assert isinstance(parsed.payload, bytes)
|
||||||
288
tests/test_session_state.py
Normal file
288
tests/test_session_state.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
"""Проверки автоматов сеанса: protocol/session_state.py.
|
||||||
|
|
||||||
|
Свойства безопасности, перенесённые из функциональных проверок Lab041.
|
||||||
|
До этого они проверялись только при полном запуске лабораторной на 180
|
||||||
|
сочетаний, а пять из них в отчёте вообще не измерялись.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from protocol.control_messages import (
|
||||||
|
ControlStateMessage,
|
||||||
|
MessageContext,
|
||||||
|
ResetRequest,
|
||||||
|
SequenceComparison,
|
||||||
|
compare_sequence,
|
||||||
|
)
|
||||||
|
from protocol.session_state import (
|
||||||
|
GroundSessionController,
|
||||||
|
GroundSessionState,
|
||||||
|
RejectionCause,
|
||||||
|
RoverSessionController,
|
||||||
|
RoverSessionState,
|
||||||
|
deterministic_identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONTROL_STREAM_ID = 20
|
||||||
|
|
||||||
|
|
||||||
|
def active_pair(seed: int) -> tuple[GroundSessionController, RoverSessionController]:
|
||||||
|
"""Довести обе стороны до состояния, в котором разрешено движение."""
|
||||||
|
|
||||||
|
ground = GroundSessionController(100 + seed)
|
||||||
|
rover = RoverSessionController(200 + seed)
|
||||||
|
status = rover.receive_hello(ground.start_synchronization())
|
||||||
|
assert ground.receive_status(status)
|
||||||
|
acknowledgement = rover.authorize_movement(ground.request_movement_authorization(1))
|
||||||
|
assert ground.receive_movement_authorize_ack(acknowledgement)
|
||||||
|
return ground, rover
|
||||||
|
|
||||||
|
|
||||||
|
def control_from(ground, rover, sequence_number: int, speed: float = 1.0) -> ControlStateMessage:
|
||||||
|
context = MessageContext(ground.ground_session_id, rover.rover_boot_id, rover.control_epoch, sequence_number)
|
||||||
|
return ControlStateMessage(context, speed, 0.0, False, True)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ безопасный запуск
|
||||||
|
|
||||||
|
|
||||||
|
def test_both_sides_boot_without_movement() -> None:
|
||||||
|
ground = GroundSessionController(7)
|
||||||
|
rover = RoverSessionController(8)
|
||||||
|
assert ground.state is GroundSessionState.GROUND_BOOT
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
assert rover.state is RoverSessionState.ROVER_BOOT_SAFE
|
||||||
|
assert rover.safe
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_status_alone_does_not_permit_movement() -> None:
|
||||||
|
ground = GroundSessionController(9)
|
||||||
|
rover = RoverSessionController(10)
|
||||||
|
assert ground.receive_status(rover.receive_hello(ground.start_synchronization()))
|
||||||
|
assert ground.state is GroundSessionState.GROUND_SAFE_SYNCHRONIZED
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
assert rover.safe
|
||||||
|
|
||||||
|
|
||||||
|
def test_authorization_ack_does_not_restore_motion() -> None:
|
||||||
|
ground, rover = active_pair(11)
|
||||||
|
assert ground.current_speed_mps == 0.0
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
assert rover.safe
|
||||||
|
|
||||||
|
|
||||||
|
def test_movement_requires_a_new_operator_command() -> None:
|
||||||
|
ground, rover = active_pair(12)
|
||||||
|
assert rover.safe
|
||||||
|
assert rover.receive_control(ground.new_operator_control(1.0), CONTROL_STREAM_ID).accepted
|
||||||
|
assert rover.speed_mps == 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_restarted_rover_is_immediately_safe() -> None:
|
||||||
|
ground, rover = active_pair(13)
|
||||||
|
rover.receive_control(ground.new_operator_control(1.0), CONTROL_STREAM_ID)
|
||||||
|
restarted = RoverSessionController(999)
|
||||||
|
assert restarted.state is RoverSessionState.ROVER_BOOT_SAFE
|
||||||
|
assert restarted.safe
|
||||||
|
|
||||||
|
|
||||||
|
def test_new_ground_session_stops_the_old_command_stream() -> None:
|
||||||
|
old_ground, rover = active_pair(14)
|
||||||
|
old_command = old_ground.new_operator_control(1.0)
|
||||||
|
new_ground = GroundSessionController(1000)
|
||||||
|
rover.receive_hello(new_ground.start_synchronization())
|
||||||
|
decision = rover.receive_control(old_command, CONTROL_STREAM_ID)
|
||||||
|
assert decision.reason is RejectionCause.GROUND_SESSION_ID
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicate_hello_is_idempotent() -> None:
|
||||||
|
ground = GroundSessionController(30)
|
||||||
|
rover = RoverSessionController(31)
|
||||||
|
hello = ground.start_synchronization()
|
||||||
|
first = rover.receive_hello(hello)
|
||||||
|
second = rover.receive_hello(hello)
|
||||||
|
assert first == second
|
||||||
|
assert rover.duplicate_hellos == 1
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ старые пакеты
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"field,expected",
|
||||||
|
[
|
||||||
|
("ground_session_id", RejectionCause.GROUND_SESSION_ID),
|
||||||
|
("rover_boot_id", RejectionCause.ROVER_BOOT_ID),
|
||||||
|
("control_epoch", RejectionCause.CONTROL_EPOCH),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_stale_identifier_is_rejected(field: str, expected: RejectionCause) -> None:
|
||||||
|
"""Пакет предыдущего сеанса, запуска или периода управления не принимается."""
|
||||||
|
|
||||||
|
ground, rover = active_pair(hash(field) % 500)
|
||||||
|
values = {
|
||||||
|
"ground_session_id": ground.ground_session_id,
|
||||||
|
"rover_boot_id": rover.rover_boot_id,
|
||||||
|
"control_epoch": rover.control_epoch,
|
||||||
|
"sequence_number": 1,
|
||||||
|
}
|
||||||
|
values[field] -= 1
|
||||||
|
message = ControlStateMessage(MessageContext(**values), 1.0, 0.0, False, True)
|
||||||
|
assert rover.receive_control(message, CONTROL_STREAM_ID).reason is expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence_zero_is_accepted_in_a_new_session() -> None:
|
||||||
|
ground, rover = active_pair(1)
|
||||||
|
assert rover.receive_control(control_from(ground, rover, 0), CONTROL_STREAM_ID).accepted
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence_wrap_is_treated_as_newer() -> None:
|
||||||
|
ground, rover = active_pair(5)
|
||||||
|
key = (ground.ground_session_id, rover.rover_boot_id, rover.control_epoch, CONTROL_STREAM_ID)
|
||||||
|
rover.last_sequences[key] = 0xFFFFFFFF
|
||||||
|
assert rover.receive_control(control_from(ground, rover, 0), CONTROL_STREAM_ID).accepted
|
||||||
|
assert compare_sequence(0, 0xFFFFFFFF) is SequenceComparison.NEWER
|
||||||
|
|
||||||
|
|
||||||
|
def test_ambiguous_half_range_is_rejected() -> None:
|
||||||
|
ground, rover = active_pair(6)
|
||||||
|
key = (ground.ground_session_id, rover.rover_boot_id, rover.control_epoch, CONTROL_STREAM_ID)
|
||||||
|
rover.last_sequences[key] = 0
|
||||||
|
decision = rover.receive_control(control_from(ground, rover, 0x80000000), CONTROL_STREAM_ID)
|
||||||
|
assert not decision.accepted
|
||||||
|
assert decision.ambiguous_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def test_replayed_command_is_rejected() -> None:
|
||||||
|
ground, rover = active_pair(21)
|
||||||
|
assert rover.receive_control(control_from(ground, rover, 10), CONTROL_STREAM_ID).accepted
|
||||||
|
replay = rover.receive_control(control_from(ground, rover, 10), CONTROL_STREAM_ID)
|
||||||
|
assert not replay.accepted
|
||||||
|
assert replay.reason is RejectionCause.SEQUENCE_NUMBER
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ аварийное намерение
|
||||||
|
|
||||||
|
|
||||||
|
def test_persisted_emergency_intent_survives_a_ground_restart() -> None:
|
||||||
|
ground = GroundSessionController(15, persisted_emergency_intent=True, persisted_emergency_event_id=77)
|
||||||
|
assert ground.emergency_intent
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
hello = ground.start_synchronization()
|
||||||
|
assert hello.emergency_intent
|
||||||
|
assert hello.emergency_event_id == 77
|
||||||
|
|
||||||
|
|
||||||
|
def test_emergency_acknowledgement_does_not_clear_the_intent() -> None:
|
||||||
|
ground, rover = active_pair(16)
|
||||||
|
acknowledgement = rover.receive_emergency(ground.request_emergency(88))
|
||||||
|
assert acknowledgement is not None
|
||||||
|
assert ground.receive_emergency_ack(acknowledgement)
|
||||||
|
assert ground.emergency_intent
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
|
||||||
|
|
||||||
|
def test_ordinary_command_does_not_release_the_latch() -> None:
|
||||||
|
ground, rover = active_pair(20)
|
||||||
|
rover.receive_emergency(ground.request_emergency(96))
|
||||||
|
decision = rover.receive_control(control_from(ground, rover, 100), CONTROL_STREAM_ID)
|
||||||
|
assert not decision.accepted
|
||||||
|
assert rover.state is RoverSessionState.EMERGENCY_LATCHED
|
||||||
|
|
||||||
|
|
||||||
|
def test_movement_authorization_is_refused_while_intent_stands() -> None:
|
||||||
|
ground = GroundSessionController(40, persisted_emergency_intent=True, persisted_emergency_event_id=5)
|
||||||
|
rover = RoverSessionController(41)
|
||||||
|
ground.receive_status(rover.receive_hello(ground.start_synchronization()))
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
ground.request_movement_authorization(1)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ сброс
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_reset_request_is_refused() -> None:
|
||||||
|
ground, rover = active_pair(17)
|
||||||
|
assert rover.receive_emergency(ground.request_emergency(90)) is not None
|
||||||
|
request = ground.request_reset(91)
|
||||||
|
stale_context = MessageContext(
|
||||||
|
request.context.ground_session_id - 1,
|
||||||
|
request.context.rover_boot_id,
|
||||||
|
request.context.control_epoch,
|
||||||
|
request.context.sequence_number,
|
||||||
|
)
|
||||||
|
stale = ResetRequest(stale_context, request.emergency_event_id, request.reset_request_id, 0.0, False)
|
||||||
|
assert not rover.receive_reset(stale).acknowledgement.accepted
|
||||||
|
|
||||||
|
|
||||||
|
def test_repeated_reset_is_idempotent() -> None:
|
||||||
|
ground, rover = active_pair(18)
|
||||||
|
rover.receive_emergency(ground.request_emergency(92))
|
||||||
|
request = ground.request_reset(93)
|
||||||
|
|
||||||
|
first = rover.receive_reset(request)
|
||||||
|
assert first.performed
|
||||||
|
|
||||||
|
repeated = rover.receive_reset(request)
|
||||||
|
assert repeated.duplicate
|
||||||
|
assert not repeated.performed
|
||||||
|
assert repeated.acknowledgement.accepted
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_requires_an_emergency_intent() -> None:
|
||||||
|
ground, _ = active_pair(50)
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
ground.request_reset(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reset_does_not_restore_the_previous_command() -> None:
|
||||||
|
ground, rover = active_pair(51)
|
||||||
|
rover.receive_control(ground.new_operator_control(2.0), CONTROL_STREAM_ID)
|
||||||
|
rover.receive_emergency(ground.request_emergency(60))
|
||||||
|
assert rover.speed_mps == 0.0
|
||||||
|
|
||||||
|
decision = rover.receive_reset(ground.request_reset(61))
|
||||||
|
assert decision.performed
|
||||||
|
assert ground.receive_reset_ack(decision.acknowledgement)
|
||||||
|
assert not ground.movement_permitted
|
||||||
|
assert ground.current_speed_mps == 0.0
|
||||||
|
assert rover.safe
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ идентификаторы
|
||||||
|
|
||||||
|
|
||||||
|
def test_identifiers_are_reproducible_and_nonzero() -> None:
|
||||||
|
assert deterministic_identifier(42, "ground") == deterministic_identifier(42, "ground")
|
||||||
|
assert deterministic_identifier(42, "ground") != deterministic_identifier(42, "rover")
|
||||||
|
assert deterministic_identifier(42, "ground") != deterministic_identifier(43, "ground")
|
||||||
|
for seed in range(50):
|
||||||
|
assert 0 < deterministic_identifier(seed, "ground") <= 0xFFFFFFFFFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def test_identifier_rejects_a_non_integer_seed() -> None:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
deterministic_identifier("не число", "ground")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ границы скорости
|
||||||
|
|
||||||
|
|
||||||
|
def test_negative_operator_speed_is_refused() -> None:
|
||||||
|
"""Свойство, которое в отчёте Lab041 стояло литеральным нулём."""
|
||||||
|
|
||||||
|
ground, _ = active_pair(70)
|
||||||
|
for speed in (-0.1, -5.0, 0.0):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ground.new_operator_control(speed)
|
||||||
|
|
||||||
|
|
||||||
|
def test_speed_above_the_limit_is_refused_by_encoding() -> None:
|
||||||
|
from protocol.control_messages import ControlMessageError, encode_message
|
||||||
|
|
||||||
|
ground, rover = active_pair(71)
|
||||||
|
with pytest.raises(ControlMessageError):
|
||||||
|
encode_message(control_from(ground, rover, 1, speed=15.1))
|
||||||
189
tools/quick_gate.py
Normal file
189
tools/quick_gate.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
"""
|
||||||
|
Быстрый шлюз проверки SDR Rover Link.
|
||||||
|
|
||||||
|
Проверяет за одну-две секунды то, что ломается чаще всего:
|
||||||
|
|
||||||
|
1. Синтаксис всех файлов в protocol, tests, experiments и tools.
|
||||||
|
2. Импорт каждого модуля protocol.
|
||||||
|
3. Быстрые проверки ядра из tests под pytest.
|
||||||
|
4. Функциональные проверки тех лаб, которым не нужны данные эксперимента.
|
||||||
|
|
||||||
|
Полный прогон лабы шлюз не заменяет.
|
||||||
|
Он отвечает на один вопрос: не сломана ли кодовая база прямо сейчас.
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
python tools/quick_gate.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
CHECKED_DIRECTORIES = ("protocol", "tests", "experiments", "tools")
|
||||||
|
|
||||||
|
|
||||||
|
def check_syntax() -> list[str]:
|
||||||
|
"""Разобрать все файлы Python и вернуть список сообщений об ошибках."""
|
||||||
|
|
||||||
|
failures: list[str] = []
|
||||||
|
|
||||||
|
for directory in CHECKED_DIRECTORIES:
|
||||||
|
for path in sorted((PROJECT_ROOT / directory).glob("*.py")):
|
||||||
|
try:
|
||||||
|
ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||||
|
except SyntaxError as error:
|
||||||
|
relative = path.relative_to(PROJECT_ROOT)
|
||||||
|
failures.append(f"{relative}:{error.lineno}: синтаксис: {error.msg}")
|
||||||
|
|
||||||
|
return failures
|
||||||
|
|
||||||
|
|
||||||
|
def check_protocol_imports() -> list[str]:
|
||||||
|
"""Импортировать каждый модуль protocol и вернуть список сообщений об ошибках."""
|
||||||
|
|
||||||
|
failures: list[str] = []
|
||||||
|
|
||||||
|
for path in sorted((PROJECT_ROOT / "protocol").glob("*.py")):
|
||||||
|
if path.name == "__init__.py":
|
||||||
|
continue
|
||||||
|
|
||||||
|
module_name = f"protocol.{path.stem}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
importlib.import_module(module_name)
|
||||||
|
except Exception:
|
||||||
|
failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}")
|
||||||
|
|
||||||
|
return failures
|
||||||
|
|
||||||
|
|
||||||
|
def check_core_tests() -> tuple[list[str], int]:
|
||||||
|
"""
|
||||||
|
Прогнать быстрые проверки ядра из tests под pytest.
|
||||||
|
|
||||||
|
Возвращает:
|
||||||
|
failures:
|
||||||
|
Список сообщений об ошибках.
|
||||||
|
|
||||||
|
passed:
|
||||||
|
Количество пройденных проверок, ноль если разобрать вывод не удалось.
|
||||||
|
"""
|
||||||
|
|
||||||
|
environment = dict(os.environ, PYTHONIOENCODING="utf-8")
|
||||||
|
|
||||||
|
completed = subprocess.run(
|
||||||
|
[sys.executable, "-m", "pytest", "tests", "-q"],
|
||||||
|
cwd=PROJECT_ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
env=environment,
|
||||||
|
)
|
||||||
|
|
||||||
|
output = (completed.stdout + completed.stderr).strip()
|
||||||
|
|
||||||
|
if completed.returncode != 0:
|
||||||
|
return [f"pytest: тесты ядра не пройдены:\n{output}"], 0
|
||||||
|
|
||||||
|
match = re.search(r"(\d+) passed", output)
|
||||||
|
passed = int(match.group(1)) if match else 0
|
||||||
|
|
||||||
|
return [], passed
|
||||||
|
|
||||||
|
|
||||||
|
def check_functional_tests() -> tuple[list[str], int, int]:
|
||||||
|
"""
|
||||||
|
Выполнить функциональные проверки лаб, не требующие данных эксперимента.
|
||||||
|
|
||||||
|
Возвращает:
|
||||||
|
failures:
|
||||||
|
Список сообщений об ошибках.
|
||||||
|
|
||||||
|
executed:
|
||||||
|
Количество выполненных лаб.
|
||||||
|
|
||||||
|
skipped:
|
||||||
|
Количество пропущенных лаб.
|
||||||
|
"""
|
||||||
|
|
||||||
|
failures: list[str] = []
|
||||||
|
executed = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
for path in sorted((PROJECT_ROOT / "experiments").glob("lab*.py")):
|
||||||
|
source = path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
if "def run_functional_tests" not in source:
|
||||||
|
continue
|
||||||
|
|
||||||
|
module_name = f"experiments.{path.stem}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = importlib.import_module(module_name)
|
||||||
|
except Exception:
|
||||||
|
failures.append(f"{module_name}: импорт: {traceback.format_exc(limit=3).strip()}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
run_functional_tests = module.run_functional_tests
|
||||||
|
parameters = inspect.signature(run_functional_tests).parameters
|
||||||
|
required = [name for name, parameter in parameters.items() if parameter.default is parameter.empty]
|
||||||
|
|
||||||
|
if required:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_functional_tests()
|
||||||
|
except Exception:
|
||||||
|
failures.append(f"{module_name}: проверки: {traceback.format_exc(limit=3).strip()}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
executed += 1
|
||||||
|
|
||||||
|
return failures, executed, skipped
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""Выполнить шлюз и вернуть код возврата процесса."""
|
||||||
|
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
syntax_failures = check_syntax()
|
||||||
|
|
||||||
|
if syntax_failures:
|
||||||
|
print("Шлюз не пройден: ошибки синтаксиса")
|
||||||
|
for failure in syntax_failures:
|
||||||
|
print(f" {failure}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
import_failures = check_protocol_imports()
|
||||||
|
core_failures, passed = check_core_tests()
|
||||||
|
test_failures, executed, skipped = check_functional_tests()
|
||||||
|
failures = import_failures + core_failures + test_failures
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print("Шлюз не пройден")
|
||||||
|
for failure in failures:
|
||||||
|
print(f" {failure}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Шлюз пройден: синтаксис в порядке, protocol импортируется, "
|
||||||
|
f"тестов ядра пройдено {passed}, "
|
||||||
|
f"функциональных проверок лаб выполнено {executed}, пропущено {skipped}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user