Учебные потоки NMEA: kshana nmea-scenario
Морские академии и центры тренажёров могут подавать данные NMEA 0183, сформированные Kshana, на мостиковый тренажёр или картплоттер, чтобы учить экипажи распознавать подавление и спуфинг ГНСС.
Только для обучения и испытаний. Формируемые потоки синтетические. Их ни в коем случае нельзя подавать на действующие навигационные системы судна. Kshana носит консультативный характер и не является навигационным оборудованием с утверждённым типом. Каждый поток начинается с фирменного маркерного предложения
$PKSHT,TRAINING,SYNTHETIC,NOT-FOR-NAVIGATIONи повторяет его каждые десять секунд; приёмники его игнорируют, а--no-markerубирает его для тренажёра, который отклоняет неизвестные предложения. Формируется только текст NMEA: нет ни РЧ-выхода, ни IQ, ни формы сигнала, и ничего не передаётся, кроме текста по указанному вами адресу.
Рисунок построен по журналу инструктора scenarios/training/coastal-drag-off.toml.
Применение#
# write a file (default <name>.nmea) and the instructor log next to it
kshana nmea-scenario scenarios/training/coastal-drag-off.toml --out drag.nmea
# serve over TCP in real time on this machine only (waits for the simulator to connect)
kshana nmea-scenario scenarios/training/open-sea-jamming.toml --tcp 10110
# reachable from the bridge-simulator host: name the address in full
kshana nmea-scenario scenarios/training/open-sea-jamming.toml --tcp 192.168.1.20:10110
# UDP, one sentence per datagram, ten times faster than real time
kshana nmea-scenario scenarios/training/combined-event.toml --udp 192.168.1.50:10110 --speed 10
# UDP broadcast
kshana nmea-scenario scenarios/training/combined-event.toml --udp 192.168.1.255:10110 --broadcast
| Параметр | Значение |
|---|---|
--out <file> |
записать поток в файл; без сетевого вывода по умолчанию используется <name>.nmea |
--instructor-log <prefix> |
записать <prefix>.instructor.json и <prefix>.instructor.txt (по умолчанию рядом с файлом) |
--tcp <addr:port> или <port> |
сервер TCP; прогон начинается, когда подключился клиент (--wait-clients <n>). Просто порт прослушивается только на 127.0.0.1 |
--udp <addr:port> или <port> |
UDP, одно предложение в датаграмме; --broadcast разрешает широковещательный адрес. Просто порт отправляет на 127.0.0.1 |
--realtime, --speed <x> |
задать темп потока; --speed 0 означает как можно быстрее. Сетевой вывод по умолчанию идёт в реальном времени, файлы — как можно быстрее |
--seed <n> |
заменить начальное значение (seed) сценария |
--no-marker |
опустить маркерное предложение |
Аутентификации нет. Сервер TCP, получивший нелокальный адрес, отдаёт поток любому, кто может до него добраться, а UDP отправляет на любой указанный вами адрес. Клиент TCP, который не забирает свои данные в течение двух секунд, отключается, чтобы один застрявший потребитель не задерживал остальных.
Одно и то же начальное значение даёт одни и те же байты на одной платформе, а темп никогда не меняет байты. На разных платформах собственная арифметика генератора побитово идентична (используется переносимый математический слой), но положения спутников и углы визирования берутся из функций движка, которые вызывают математическую библиотеку хоста, поэтому в редком пограничном случае округлённая цифра возвышения, азимута или C/N0 в GSV может различаться между платформами.
Что записывается#
Каждая эпоха (1, 2, 5 или 10 в секунду) содержит: RMC, GGA, GNS (решение, положение, время), GSA (используемые спутники и DOP, по одному на орбитальную группировку), GSV (видимые спутники с возвышением, азимутом и SNR в дБ-Гц, по одному набору на орбитальную группировку, идентификатор сигнала NMEA 4,10), VTG (курс и скорость относительно грунта), ZDA (время и дата), HDT (курс по гирокомпасу) и VBW (скорость относительно воды и грунта по лагу). Источник сообщения (talker) — GN при нескольких орбитальных группировках, иначе собственный идентификатор группировки. Строки оканчиваются на CRLF, контрольные суммы верны.
HDT и VBW заменяют независимые датчики (гирокомпас и доплеровский лаг). Ни одно событие их не затрагивает, и это сделано намеренно: расхождение между тем, что сообщает ГНСС, и тем, что показывают гирокомпас и лаг, служит подсказкой для разбора.
Когда решение теряется, качество GGA равно 0, статус RMC — V, поля положения пусты, а поля времени продолжаются (часы приёмника идут), как у настоящих приёмников.
Судно#
[vessel] и [[waypoint]] задают начальное положение, скорость и точки, через которые нужно следовать. Судно направляется к следующей путевой точке с учётом предела скорости поворота (max_rot_deg_per_min, по умолчанию 30), предела того, как быстро нарастает скорость поворота (max_rot_accel_deg_per_s2), и предела изменения скорости (max_accel_kn_per_min). Скорость участка — это speed_kn путевой точки. [environment] добавляет течение, из-за чего скорость относительно грунта отличается от скорости относительно воды, а курс отличается от курса относительно грунта, и магнитное склонение. stop_at_end доводит судно до остановки в последней путевой точке.
Геометрия спутников#
Спутники берутся из собственных номинальных орбитальных группировок движка (таблица слотов GPS, схемы Уокера MEO для Galileo и BeiDou, ICD ГЛОНАСС), распространяются функцией constellation::satellite_positions_fixed (задача двух тел плюс вековой J2, вращение Земли) и наблюдаются с судна по углам визирования движка в WGS-84. Число видимых спутников, возвышения и азимуты в GSV правдоподобны для места и времени суток. Это номинальное небо, а не эфемериды на дату потока: PRN назначаются в порядке слотов, и каждая орбитальная группировка сохраняет свою опубликованную схему слотов, непрерывно распространяемую от эпохи таблицы слотов GPS (2016-12-31) с учётом J2, так что схема цела, но её ориентация не совпадает с ориентацией реального неба на дату потока. C/N0 следует за возвышением (номинальный уровень в зените задаётся в [receiver]) с фиксированным смещением для каждого спутника, медленными замираниями и шумом. У спутника ниже track_threshold_dbhz поле SNR пусто; спутник ниже use_threshold_dbhz отслеживается, но не используется. Используется до max_used (по умолчанию 12, предел NMEA GGA) пригодных спутников, выбранных по геометрии (сначала самый высокий спутник в каждом азимутальном секторе 30 градусов). При менее чем четырёх пригодных спутниках решение теряется, а возвращается через reacquire_s пригодного сигнала.
События#
Каждое [[event]] имеет kind, момент начала start_s, длительность duration_s (от начала до начала снятия), нарастание в начале ramp_s и спад при снятии recovery_s. Сила растёт линейно в течение ramp_s, держится, затем падает в течение recovery_s; 0 означает скачок. Все эффекты масштабируются этой силой. Параметры, не относящиеся к данному виду, отклоняются.
| вид | параметры | эффект |
|---|---|---|
jamming |
cn0_drop_db, spread_db (по умолчанию 6) |
C/N0 каждого спутника падает не более чем на cn0_drop_db (разброс по спутникам), спутники выпадают, начиная с самого слабого, решение теряется, когда пригодных меньше четырёх, и восстанавливается после снятия и reacquire_s |
drag-off |
final_offset_m, одно из bearing_deg (истинный) или relative_bearing_deg (относительно курса в момент начала, затем удерживается), необязательные max_accel_mps2, counterfeit_cn0_dbhz |
приёмник сохраняет действительное решение, положение которого уходит от истины; SOG и COG включают увод. Увод следует сглаженной ступени (smoothstep) на участках нарастания, поэтому ложный путь начинается и прекращается без скачка скорости; его пики равны 1.5 D/T м/с и 6 D/T^2 м/с в квадрате при нарастании T с на D м (оба записываются в журнал), а сценарий, превышающий max_accel_mps2, отклоняется. Скачок (ramp_s = 0) смещает положение, не затрагивая скорость |
time-spoof |
offset_s, counterfeit_cn0_dbhz |
время RMC, GGA, GNS и ZDA смещается относительно истинного UTC; положение остаётся истинным |
replay-delay |
delay_s, affect_time (по умолчанию true), counterfeit_cn0_dbhz |
положение, скорость (и время) — те, что были у судна delay_s назад, причём задержка следует сглаженной ступени (smoothstep) на участках нарастания, а сообщаемая скорость умножается на 1 - d(delay)/dt, пока она меняется: упрощённая абстракция в духе meaconing, а не модель распространения |
counterfeit_cn0_dbhz: пока активен увод, подмена времени или задержка повтора, каждый отслеживаемый спутник показывает этот единый повышенный уровень — классический признак. Опустите его для малозаметного события, в котором сохраняется подлинная картина C/N0. Пока активно любое поддельное событие, приёмник следует за поддельным сигналом, поэтому подавление его не сбрасывает; решение, потерянное из-за более раннего подавления, возвращается через reacquire_s на поддельном сигнале (см. combined-event). Изменение времени, при котором сообщаемое время остановилось бы или пошло назад (скорость 1 с в секунду и более), отклоняется, если только оно не является скачком (ramp_s = 0).
События могут перекрываться; их эффекты складываются (смещения, изменения времени, задержки) или берётся сильнейший (подавление). onset на временной шкале — это первая эпоха, в которой событие оказывает действие, на одну эпоху позже заданного сценарием start_s, когда у события есть нарастание.
Журнал инструктора#
Вместе с потоком <prefix>.instructor.json и <prefix>.instructor.txt содержат:
- внесённые события простым языком со всеми параметрами и временем их начала по UTC;
- временную шкалу: начало, полный эффект, начало восстановления, восстановление и собственные
fix-lostиfix-regainedприёмника; - истинный путь в сопоставлении с сообщаемым с шагом
log_interval_s: истинное и сообщаемое положение, скорость и курс, ошибка положения в метрах, смещение времени, число используемых и отслеживаемых спутников, средний C/N0 и перечень активных событий; - пиковые скорость и ускорение истинного и сообщаемого путей;
trainer_noteсценария.
Показывайте журнал только на разборе.
поля JSON (схема kshana-nmea-training/1)#
| поле | тип, единица | значение |
|---|---|---|
schema |
строка | kshana-nmea-training/1 |
warning |
строка | заявление о том, что это только для обучения |
scenario, description, trainer_note |
строка | из сценария |
seed |
целое число | использованное начальное значение |
start_utc |
ISO 8601 UTC | начало прогона |
duration_s |
s | длительность прогона |
summary.true_peak_sog_kn, summary.reported_peak_sog_kn |
узлы | наибольшая истинная и сообщаемая скорость относительно грунта |
summary.true_peak_accel_mps2, summary.reported_peak_accel_mps2 |
м/с в квадрате | наибольшее изменение скорости в секунду: истинное и между последовательными сообщаемыми определениями места |
events[] |
по одному на каждое заданное событие | |
events[].id |
целое число | нумерация с 1; используется в timeline[].event и track[].active_events |
events[].description |
строка | описание простым языком с параметрами |
events[].start_utc |
ISO 8601 UTC | заданное начало |
events[].parameters |
объект | событие в точности как записано (kind, start_s, duration_s, ramp_s, recovery_s и собственные параметры вида; отсутствующие опускаются) |
events[].resolved_bearing_deg |
градусы истинные или null | увод: применённое направление (относительный пеленг определяется в момент начала) |
events[].peak_drag_speed_mps, events[].peak_drag_accel_mps2 |
м/с, м/с в квадрате или null | увод: пики, которые увод добавляет к ложному пути |
timeline[] |
моменты для разбора в хронологическом порядке | |
timeline[].t_s, .utc |
с от начала, ISO 8601 UTC | когда (истинное время) |
timeline[].event |
целое число или null | идентификатор события; null для изменений состояния приёмника |
timeline[].what |
строка | onset, full-effect, recovery-begins, recovered (события); fix-lost, fix-regained (приёмник) |
timeline[].text |
строка | пояснение |
track[] |
одна строка на каждые log_interval_s |
|
track[].t_s, .utc |
с, ISO 8601 UTC | когда (истинное время) |
track[].true_lat_deg, .true_lon_deg |
градусы | истинное положение |
track[].true_sog_kn, .true_cog_deg, .true_heading_deg |
узлы, градусы истинные | истинные скорость и курс относительно грунта и курс по гирокомпасу |
track[].fix_valid |
логическое значение | приёмник сообщил о решении |
track[].reported_lat_deg, .reported_lon_deg, .reported_sog_kn, .reported_cog_deg |
градусы, узлы, градусы истинные или null | что сообщил приёмник; null, пока решение потеряно |
track[].position_error_m |
м или null | горизонтальное расстояние от истинного до сообщаемого положения |
track[].time_offset_s |
s | сообщаемое UTC минус истинное UTC |
track[].n_used, .n_tracked |
целые числа | спутники, использованные в решении, и отслеживаемые |
track[].mean_cn0_dbhz |
дБ-Гц или null | средний C/N0 отслеживаемых спутников |
track[].active_events |
целые числа | идентификаторы событий, действующих в этот момент |
Добавление поля не меняет метку схемы. Переименование или удаление поля, а также изменение смысла или единицы поля увеличивает номер (/2).
Библиотека сценариев#
scenarios/training/, каждый — несколько минут перехода с примечанием тренера в TOML:
| файл | что показывает |
|---|---|
open-sea-jamming.toml |
C/N0 падает у всех сразу, спутники выпадают, решение теряется и возвращается |
coastal-drag-off.toml |
действительное, здоровое на вид решение уводится на 1,4 км к берегу; единый повышенный C/N0 |
port-approach-time-spoof.toml |
положение верно, UTC уходит вперёд на 45 с при медленном заходе в порт |
combined-event.toml |
подавление, затем поддельный захват с уводом и смещением времени, затем задержка повтора |
fjord-natural-degradation.toml |
закрытое небо, а не помехи: стены фьорда (REPRESENTATIVE пресет контура горизонта fjord) скрывают спутники на востоке и западе, затем канал снова открывается (событие natural-degradation с таблицей skyline) |
Положения, даты и пути вымышлены; библиотека не содержит ни реального судна, ни оператора, ни записи. Она ничего не утверждает о том, как какой-либо приёмник или система поведёт себя при помехах.
Допущения моделирования#
Генератор — это учебный инструмент без внешнего оракула, а его числа — допущения моделирования, а не измерения какого-либо приёмника. Они выбраны так, чтобы выглядеть как обычный журнал приёмника: наклон C/N0 в 14 дБ от зенита к горизонту ниже зенитного уровня (по умолчанию 47 дБ-Гц) и смещение для каждого спутника ±1,5 дБ, медленные замирания 0,8 дБ с периодом 97 с и 0,5 дБ с периодом 31 с, дрожание 0,3 дБ; пороги для слежения (20 дБ-Гц) и использования (28 дБ-Гц); шум положения (1,5 м при HDOP 1, коррелированный на 30 с, по вертикали вдвое больше); шум гирокомпаса 0,05 градуса и шум дрейфа 0,03 уз; постоянная времени управления 20 с и радиусы прихода в путевые точки в модели судна; а также выбор спутников по азимутальным секторам. Значения по умолчанию находятся в [receiver] и [vessel] и все могут быть изменены в сценарии.
Обратное чтение#
kshana receiver-trust может принимать эти файлы (format = "nmea"). Тесты проверяют каждое предложение каждого сценария библиотеки по его собственным правилам числа полей и контрольной суммы и прогоняют файлы через этот модуль чтения, который интерпретирует GGA, RMC и GSV и пропускает остальные типы, ничего не отбрасывая. Тесты фиксируют эталонный фрагмент для каждого сценария (tests/fixtures/nmea_training/); после намеренного изменения воссоздайте его с помощью KSHANA_BLESS_GOLDEN=1.