SkiSport

Техническая сторона

Как это устроено

Всё, что обычно прячут за словом «профессиональный»: путь одного времени от калитки до протокола, что летит по радио, чем закрыт файл с фамилиями детей и где у комплекса SkiSport границы. Без цифр такой рассказ ничего не стоит, поэтому цифры здесь есть.

1. Путь одного времени

Время рождается дважды: в момент, когда спортсмен раскрывает калитку, и в момент, когда пересекает створ финиша. Всё остальное — доставка. Вопрос только в том, что происходит с точностью по дороге, и ответ зависит от того, как соединены старт и финиш.

Проводом

≈0,3 мс

Импульс старта идёт по витой паре, и оба фронта — старт и финиш — размечает один кварц финишного узла. Ошибка здесь масштабная: она растягивает время всем одинаково и никого не может переставить местами.

По радио

1–3 мс

Двое часов, которые подводятся друг под друга каждые десять минут. Ошибка случайная и своя у каждого — сумма рассинхронизации и ухода между сверками. Двое, стоящие в сотой доле, действительно могут поменяться местами.

Рукой

десятые

Человек и секундомер — или человек, печатающий время в таблицу. Исправленное руками время понижается до этой категории, даже если сначала его измерил провод.

Разница между ними — два порядка, но важнее, что она разная по природе. Поэтому происхождение времени хранится вместе с самим временем и печатается в протоколе. Беспроводная детская гонка — это нормально. Беспроводное время, приехавшее в протокол под видом проводного, — нет.

Окно результатов: колонка «Хронометраж» показывает «Провод» у всех строк, кроме одной, где стоит «Вручную»
Колонка «Хронометраж» — то самое обещание в интерфейсе: у одной строки время правили руками, и это видно, не открывая журнал. Данные на снимке выдуманы.

2. Что летит по радио

18 байт

Не «пакет данных», а конкретная структура, в которой каждое поле пришлось заслужить:

ПолеБайтЗачем
magic, version2Чужой передатчик на той же частоте не должен быть принят за свой
type1Результат, время старта, отзыв старта, подтверждение, пульс узла…
run1Первый заезд или второй
bib2Номер участника; у пульса узла здесь напряжение батареи
seq2Номер пакета — по нему отзывают конкретный старт, а не «последний»
value4Время в миллисекундах; в сверке часов — отметки t0…t3
speed_cms2Скорость в створе, см/с
src1Кто отправил: старт, финиш, база, табло
hops1Сколько раз пакет ретранслировали; дальше двух не живёт
crc2CRC-16/CCITT-FALSE поверх всего предыдущего

Своя контрольная сумма — при том, что у радио есть собственная. Радийная проверяет кадр в эфире; эта проверяет пакет и ловит то, что случилось с ним по дороге через ретранслятор и очереди. Стоит два байта.

Отправитель в пакете появился не сразу: до третьей версии приёмник угадывал его по типу — результат шлёт финиш, время старта шлёт старт. С подтверждениями так уже нельзя, надо знать, кому отвечать, а ретранслятору нужно ещё и отличать свой пакет от собственного эха. Два байта на отправителя и счётчик пересылок — и петли стали невозможны.

Радио

LoRa, 868 МГц, SF7, полоса 125 кГц. Не самый дальний из возможных режимов — самый быстрый из достаточных: пакет уходит за десятки миллисекунд, а не за полсекунды, и склон он перекрывает.

Часы подводятся сами

Раз в десять минут. Обмен из четырёх отметок (t0…t3) — тот же приём, что у сетевого протокола времени: путь туда и обратно симметричен, поэтому половина оборота и есть поправка.

Ретранслятор

Один на склоне — стартовая калитка между ноутбуком и финишем. Дальше двух пересылок пакет не живёт: счётчик в пакете и есть то, что не даёт эху ходить по кругу.

3. Кто это проехал

Спортсмена узнаёт метка — маленький BLE-маячок, который едет на нём. Узлы читают номер прямо из рекламного кадра, и форматов ровно два: свой, шестибайтовый, и обычный iBeacon — тот, что продаётся готовым за две сотни рублей, не требует ни пайки, ни прошивки, а номер спортсмена кладётся в поле major из приложения.

4. Старт по правилам

Стартовые часы отсчитывают пять секунд: низкий тон на каждой из последних пяти, высокий — в ноль. Так это описано в Timing Booklet FIS, и так это здесь и сделано. Окно, в котором старт считается действительным, — пять секунд по обе стороны от нуля (ICR 613.7).

5. Тренировка без ноутбука

Разворачивать ноутбук ради вторничной прикидки никто не станет, поэтому базовая станция умеет работать одна. Она держит журнал на 250 записей в собственной файловой системе, раздаёт точку доступа и отдаёт день по адресу /api/today; телефон тренера открывает страницу без интернета вовсе.

Когда связь появляется, день уезжает на сайт — по ключу тренера и идемпотентно: повторная выгрузка того же дня не создаёт вторых копий, потому что днём и служит ключ. Это не мелочь: сеть на склоне рвётся ровно в тот момент, когда выгрузка почти прошла, и «отправить ещё раз» — первое, что делает человек.

Метки на тренировке именные и постоянные: у каждого своя, привязанная раз, — поэтому тренеру не нужно каждый вторник раздавать номера и записывать, кому какой достался. Соревновательные метки живут иначе, вшитыми в майку с номером, и это разные роли одного и того же железа. Ростер команды хранится на сайте и служит источником правды: программа сверяет с ним номер, а расхождение не проглатывает, а пишет в журнал.

В конце тренировки коробка раздаёт времена сама. Спортсмен подключается к её точке доступа, вводит на странице /mine свой номер — тот, что на груди, — и уезжает со своими спусками: сколько их, лучший, разброс между лучшей и худшей попыткой и список по порядку, как ехал. Ждать, пока тренер найдёт сеть и выгрузит день, ему больше не нужно; он уже в машине. Фамилий коробка при этом не знает и не узнает: она возит номера, а подписывает их сайт.

6. Свой сервер на ноутбуке

Пока идёт соревнование, программа секретаря сама раздаёт страницы по локальной сети — без интернета, которого на склоне нет. Один ноутбук обслуживает всех, кому нужно смотреть, и каждому даёт свою страницу вместо общей «таблицы для всех».

Экран склона

Для телевизора в кафе и монитора на финише: приехавший появляется крупно и подсвечивается, экран сам поворачивается к его группе, рядом — кого ждут на трассе и какое время ему надо побить. В углу код: тот же экран уносят в телефоне. Такой же экран отдаёт и сайт, когда на объекте есть интернет.

Табло и протоколы

Живые результаты, стартовый протокол, альтернативный вид. Колонки настраиваются отдельно для каждого режима — стартовый список, первый заезд, второй, итог, — потому что в этих четырёх случаях нужны разные колонки.

Оглавление

Адрес ноутбука открывает список всех страниц сервера — с подписью, кому они и нужен ли вход, и QR к каждой. Один лист, распечатанный и повешенный у судейского домика, заменяет вопрос «а какой там адрес» на весь день.

Судья с телефона

Отдельный вход под паролем: посмотреть спортсмена по номеру, ввести время. Сессия, ограничение попыток входа, и запись, сделанная с телефона, приходит в ту же очередь, что и любая другая, — модель меняется только в главном потоке.

Комментатор

Своя выдача, собранная вокруг человека, а не вокруг строки таблицы: кто сейчас на трассе и кто следующий, клуб, тренер, возраст, как едет относительно лидера и относительно своей первой попытки — и заметка секретаря, которой нет больше нигде. У комментатора примерно четыре секунды, чтобы сказать о человеке что-то правдивое.

Голос по громкой связи

Программа объявляет приехавшего вслух: номер, фамилия, время, а если мерили — и скорость на выкате. Синтез системный, поэтому на горе он работает так же, как в судейской: облачный там просто молчит. Место не называется — пока группа едет, объявленное третье через минуту окажется пятым, и это услышит вся поляна. Шаблон выбирается: от «номер и время» до «на старте такой-то, на трассе такой-то из такого-то клуба, результат такого-то». Тот же голос и те же шаблоны умеет экран на сайте, уже средствами браузера.

Титры для трансляции

Страница на однотонном фоне, который видеомикшер вырезает по цвету, — не отчёт, а видеоисточник. Плашка сама следует за тем, кто только что финишировал, держится заданное число секунд и уходит; номер можно приколоть вручную для интервью или пьедестала.

Титры устроены хитрее, чем кажется, и вот в чём соль: страница следит только за собственным временем каждого спортсмена. Место и отставание нарочно исключены — новый лидер переписывает место всему полю, и плашка побежала бы за тем, кто просто оказался последним в сортировке, ровно в тот момент, когда гонка становится интересной.

7. Связь: боты вместо почты

Сайт не отправляет ни одного письма — и это решение, а не недоделка. Транзакционная почта с маленького сервера уезжает в спам, а подтверждение, которое не пришло, выглядит как сломанная регистрация. Вместо неё три независимых канала — Telegram, MAX и Matrix, — каждый включается отдельно в админке.

8. Чем закрыты данные

В файле соревнования лежат фамилии, даты рождения и разряды несовершеннолетних. Забытый в машине ноутбук должен означать украденный ноутбук, а не украденную заявку.

Тело файла закрыто AES-256-GCM — тем же алгоритмом, каким закрыт обмен между судейскими ноутбуками. Выбран он не за громкое имя: 256-битный ключ подобрать перебором нельзя никакими вычислениями, а режим GCM, в отличие от простого шифрования, отвечает ещё и за целостность. Изменённый в файле байт не превратится в другую фамилию — файл просто не откроется, и это будет сказано вслух. Открытый заголовок входит в шифрование как связанные данные, поэтому подменить в нём список судей, оставив тело прежним, тоже не выйдет.

Вся стойкость этой схемы держится на двух правилах, и оба они — про то, как алгоритм применён, а не про сам алгоритм: одноразовый номер (nonce) не должен повторяться дважды под одним ключом, а ключ должен быть надёжно сохранён. Повторённый nonce в GCM — это не «чуть слабее»: он выдаёт разницу двух сообщений и позволяет подделывать подписи к следующим. Ниже — что здесь сделано ради каждого из двух.

Файл соревнования

AES-256-GCM на тело файла. Ключ у файла свой и случайный, а под пароль каждого судьи заворачивается отдельная его копия — поэтому судью можно добавить или убрать, не перешифровывая файл и не меняя пароль остальным.

Номер, который не повторяется

12 случайных байт на каждое сохранение и на каждый пакет между ноутбуками — из системного источника случайности, а не из счётчика. Счётчик пришлось бы хранить и переносить вместе с файлом, а файл копируют на флешку, восстанавливают из истории сохранений и открывают на втором ноутбуке — то есть счётчик рано или поздно откатился бы назад.

Где лежит ключ

Ключ файла в открытом виде не лежит нигде — ни в файле, ни в настройках. В заголовке хранятся только его копии, завёрнутые под пароли судей через scrypt; чтобы развернуть любую, нужен пароль. Поэтому украденный файл сам по себе не даёт ничего, а забытый пароль означает потерянный файл — по-настоящему, а не «мы восстановим».

Пароль в ключ

scrypt, N = 215, r = 8, p = 1. Около ста миллисекунд на судейском ноутбуке — и ровно столько же на каждую попытку у того, кто подбирает пароль по украденному файлу. Дорого намеренно.

Пароль не покидает браузер

Программе секретаря пароль от сайта не нужен: она показывает шестизначный код, судья вводит его на сайте — там, где уже вошёл, — и сайт выдаёт программе ключ. Пароль открывает все соревнования организации, а ноутбук в конце дня передают другому. Разрешение отзывается в кабинете, и там же видно, когда каждая программа обращалась в последний раз.

Капча своя, а не чужая

Форма обратной связи открыта всем, а значит, видна рассылкам. Проверка «человек ли это» рисуется самим сайтом: чужой скрипт сюда не загрузится — политика содержимого пускает только свой домен, — да и отдавать рекламной сети каждого, кто зашёл посмотреть результаты ребёнка, ради формы обратной связи незачем.

Что сайт не хранит

Год рождения вместо полной даты. Ни кодов FIS, ни кодов РУС: как сквозной идентификатор человека они опаснее всего остального — по ним две чужие базы связываются в одну. Фотографий спортсменов нет.

Согласия

Хранится не галочка, а версия текста, его отпечаток, кто согласился, о чьих данных речь и когда согласие отозвали. Галочка помнит, что её поставили; она не помнит, что было написано над ней.

9. Протокол по правилам

Официальный протокол результатов обязан содержать тринадцать позиций — пункт 33.3.4 Правил вида спорта «горнолыжный спорт», он же 617.3.4 ICR. Российские правила здесь дословный перевод международных: пункты идут парами, и требования к содержанию у них одни.

Программа печатает все тринадцать: проводящую организацию, дисциплину, категорию и пол, судейскую бригаду целиком, постановщиков и открывающих по каждому заезду, время старта заездов, данные трассы с номером омологации, погоду, состояние снега и температуру воздуха на старте и финише, коды и очки участников, списки не стартовавших, не финишировавших и снятых, номер из календаря и коэффициент F, расчёт константы соревнований и место под подпись технического делегата.

Форма протокола — не то же самое, что допуск системы хронометража. Первое здесь сделано целиком; про второе честно сказано ниже, в разделе «Где границы».

Расчёт, а не итог

Надбавку правила требуют не назвать, а показать: суммы A, B и C и частное. Она считается и печатается рядом с применённой — применяет надбавку жюри, и расхождение должно быть видно, а не спрятано.

Код, а не фамилия

Код участника стоит и в строке результата, и в списках снятых. По фамилии человека в базе не находят, по коду находят.

Место под подпись

Технический делегат протокол подписывает. Напечатанная фамилия подписью не является, поэтому под ней линия.

10. Температура — с табло

Температура воздуха в зоне старта и в зоне финиша — одна из тринадцати позиций, и единственная, которую нельзя просто напечатать: её надо измерить. Поэтому термодатчик стоит в стартовом и финишном табло, а показания приходят в программу по нажатию и сразу попадают в соревнование.

Датчик снаружи

DS18B20 на проводе, вынесенном за корпус в тень. Внутренний датчик процессора меряет кристалл, а он в работающем табло теплее воздуха на десяток градусов; табло на солнце показало бы солнце.

По запросу, а не потоком

Радио занято временами, а температура нужна дважды за гонку. И та, что приезжает сама, измерена неизвестно когда — тогда как в протокол пишут температуру на момент заезда.

Ноль — это ноль

Ноль градусов бывает, и подменять им «не измеряли» нельзя. Отказ датчика — отсутствие ответа, а не ноль; показание старше четверти часа видно серым.

11. Три проверки за день

Соревнование можно испортить трижды, и вопросы в этих трёх точках разные. Поэтому проверок тоже три, и каждая спрашивает своё.

Перед стартом

Можно ли вообще ехать: одинаковые номера, участник без группы, молчащая станция, садящаяся батарея, выключенное автосохранение.

Перед второй попыткой

Цел ли результат первой и верен ли составленный из него список. Нет времени первой попытки — этого человека во второй не окажется, и узнает он об этом в калитке.

После финиша

Можно ли подписывать: незаконченные вторые попытки, отсутствующие места, снятия без причины, ручные времена к сверке с бумагой, группы с неполным пьедесталом.

12. Порядок второй попытки

Вторая попытка едет тем же порядком, что и первая, — по стартовым номерам. Меняется только голова списка: лучшие по результату первой попытки стартуют развёрнуто, n-й первым, победитель первой попытки n-м. Сколько мест разворачивать, решает жюри (п. 36.11.3 Правил разрешает уменьшить это число), а без разворота вторая попытка повторяет первую.

Номер называют один раз

Порядок печатают и вывешивают на старте. Ввод очередного времени его не двигает: если бы двигал, узнавали бы об этом не в программе, а в калитке.

Не допущен — значит без номера

Снятый, сошедший и не стартовавший во второй попытке не участвуют. Номер для них — не «поедет последним», а неправда, произнесённая стартёром вслух.

Равные времена — по номеру

В протокол результатов при равном времени первым вносится больший стартовый номер (п. 33.3.3). Разворот переставляет их обратно, и на границе первым едет меньший — как требует п. 36.11.2.

13. Где границы

Комплекс SkiSport говорит на языке профессиональных систем — понимает построчный вывод ALGE Timy и совместимых приборов TAG Heuer, — и умеет работать рядом с ними третьим каналом. Своё железо честно называет свои пределы: для тренировок, прикидок и детских стартов оно годится, а для разрядных и FIS-стартов нужна гомологированная система.

Это не оговорка мелким шрифтом, а часть устройства: везде, где можно было выбрать между тихой ошибкой и громкой, выбрана громкая. Перерезанный кабель узел замечает сам и кричит. Задетая калитка отзывается. Расхождение номера с протоколом пишется в журнал. Время, измеренное по радио, приходит в протокол с пометкой, что оно измерено по радио.

Назад к обзору комплекса SkiSport