
«Где мы, папа», — спросил меня 5-летний сын.
«Мы приземлимся примерно через час», — ответил я.
«Да нет, я имею в виду, где мы? Мы ещё не пролетаем Италию?»
Точно ответить я не мог. Это был недолгий перелёт по низкому тарифу без удобств в виде встроенных в подголовники кресел экранов. Тогда я огляделся по сторонам и заметил наклейку с призывом подключиться к WiFi-сети самолёта. Должно сработать. Думаю, что сайт вроде FlightRadar ответит на вопрос моего сынишки с точностью до нескольких метров.
Но, к его сожалению, я разработчик PySkyWiFi («абсолютно дурацкого WiFI-инструмента для бесплатного доступа в интернет при длительных перелётах»). Не платить за интернет авиакомпании — это моя личная фишка. Здесь же нам потребуется иная, офлайн-стратегия.
Я рассуждал так. Когда ты подключаешься к WiFi-сети самолёта, то обычно попадаешь на страницу оплаты, где покупаешь доступ в интернет. На этой же странице обычно предоставляется та же информация о полёте, что и на экранах подголовников — скорость, направление и расчётное время прибытия (Estimated Arrival Time, ETA). Поначалу я думал, что там будет ещё и карта.
В итоге я достал ноутбук, подключился к сети и загрузил платёжную страницу. На ней действительно показывалась воздушная скорость судна, направление полёта и расчётное время до прибытия. Но карты не было.
(Тогда я не додумался сделать скрин этой страницы, так что вот вам моя художественная интерпретация):

«Возможно, сервер, который отправляет нам эти данные, по факту отправляет и наше местоположение, но веб-страница этого не показывает», — подумал я. Открыв инструменты разработчика Chrome, я увидел, что мой браузер выполняет регулярные запросы к конечной точке /info
.

Я развернул один из этих запросов. Конечная точка /info
действительно отправляла большие объёмы данных, включая поля для ground_speed
, wind_speed
и estimated_arrival_time
.
В нижней части ответа я заметил поля для latitude
и longitude
. Мой пульс участился. Но при внимательном рассмотрении оказалось, что они оба null
. Облом.
Казалось, что на этом всё. Я уже собирался сдаться и сказать сыну, что мы летим где-то севернее Италии… над какой-то частью Европы. Но затем меня осенили две фантастических идеи.
Идея №1: конечная точка /info
не сообщала наши координаты, но предоставляла точную и регулярно обновляемую информацию о скорости и направлении движения. Тогда в течение всего нашего обратного полёта я смогу отслеживать и сохранять эти данные примерно раз в секунду. Используя эту информацию, я буду определять, насколько далеко и в каком направлении мы ежесекундно перемещаемся. То есть смогу динамически вычислять наше местоположение, начав с координат аэропорта и поступательно их корректируя.

Идея №2. Даже если бы я смог найти в ответе /info
координаты нашей широты и долготы, ни мне, ни моему сыну они бы ничего полезного не сообщили. Однако я смог создать веб-приложение, которое выполняется на моём ноутбуке и в реальном времени показывает динамически вычисляемое местонахождение на карте. Это приложение может автоматически обновлять график расчётного времени прибытия, направления ветра, скорости, высоты и так далее. Ах да, ещё в нём есть интерфейс для выполнения произвольных запросов по этим данным и обратные вызовы событий, позволяющие программно активировать код, исходя из информации о полёте («когда ETA составит ровно 2 часа, программа будет блокировать мне доступ к Netflix.com и открывать последний черновик моего незаконченного романа»). Мой сын будет понимать, где он находится, а я проявлю себя как хороший папа.
Я решил назвать приложение PyMyFlySpy, чтобы прослеживалась связь с PySkyWiFi. Мне не терпелось начать. К сожалению, в тот момент я был зажат между двумя детьми, 5-ти и 2-х лет, и мы все очень плохо разбирались в JavaScript. Оставалось с нетерпением ждать.
PyMyFlySpy
Наконец, мы приземлились. Я написал PyMyFlySpy за время праздников, посвятив этому поздние вечера и несколько послеобеденных часов, пока остальные члены семьи развлекались какими-то обыденными способами. Я не понимал, окажется ли дурным тоном использование ноутбука в традиционных итальянских кофейнях, и в каких из них есть WiFi, поэтому к своему бесконечному стыду нагуглил «starbucks рядом». Найдя подходящее заведение, я заказал себе моккачино и, усевшись в укромном уголке, принялся печатать.
В итоге я закончил PyMyFlySpy за день до нашего отлёта. Его код доступен на GitHub. Настроить и запустить это приложение несложно. В нём даже есть «холостой» режим, который позволяет симулировать полёт, не находясь в самолёте.
Что же конкретно умеет мой PyMyFlySpy?
▍ Карты и графики
PyMyFlySpy показывает карту с пройденным вами на данный момент путём. Он также показывает текущие метрики полёта и тренд их изменения за всё его время. Делает он это для всех данных, доступных с бортового WiFi, даже тех, которые на сайте или экране подголовника не отображаются. С его помощью вы можете точно видеть, где находитесь, и в некоторой степени ощущать себя пилотом.


▍ Интерфейс запросов
PyMyFlySpy сохраняет всю регистрируемую информацию в базе данных. В его UI есть страница, которая позволяет писать запросы к данным для определения, например «максимальной скорости за всё время полёта и момента её достижения» или «скорости ветра во время только что пройденной турбулентности».

Я не утверждаю, что это прям очень полезно, но, как минимум, просто круто.
▍ Поддержка нескольких авиалиний
У разных авиакомпаний разные системы WiFi. Регистратор для JetBlue не будет работать на авиалиниях AirFrance. К счастью, PyMyFlySpy позволяет с лёгкостью добавлять и использовать регистраторы для разных систем. Достаточно загрузить стартовую страницу подключения WiFi, открыть инструменты разработчика в браузере и найти способ спарсить данные с этой страницы, как сделал я выше. Затем нужно добавить новый код в конфигурацию PyMyFlySpy и указать на него регистратору. Всё остальное продолжит работать так же.
Проектирование системы
Система очень проста и состоит из 4 частей:
- Расширение Firefox — считывает данные полёта с сайта авиалинии и отправляет их на сервер PyMyFlySpy.
- Локальный сервер — сохраняет присылаемые расширением данные и делает их доступными для фронтенда.
- База данных SqLite — сохраняет всю эту информацию.
- Фронтенд — выводит данные, используя карты и графики.

В ходе разработки я принял одно неординарное решение — использовать для считывания данных полёта расширение Firefox вместо того, чтобы написать скрейпер, который бы напрямую выполнял собственные запросы. Такой скрейпинг информации получился бы более гибким и простым, да и полностью безвредным. Сотни людей и так подключены к WiFi, а базовая страница бортовой WiFi-системы связывается с конечной точкой /info
каждые пару секунд. Добавление дополнительного запроса от скрейпера никак бы не навредило.

Тем не менее авиакомпания вряд ли приветствовала бы вмешательство в свои бортовые сервисы посторонних, даже если совершающие его люди очень осторожны, доброжелательны и благородны. И чтобы их не тревожить, я придумал ещё более рациональный подход.
Вместо скрейпинга конечной точки данных я написал расширение для Firefox. Это расширение ожидает, когда базовая страница WiFi-системы запросит последние данные с /info
. После запроса она просматривает полученные данные и отправляет их на сервер PyMyFlySpy, который записывает эти данные в БД для передачи фронтенду. Подобное использование расширения означает, что PyMyFlySpy никогда не взаимодействует с информацией сервера самолёта напрямую и навредить этому серверу никак не может.
Мне пришлось написать расширение для Firefox, а не Chrome, потому что в Chrome планируют урезать возможность взаимодействия расширений с запросами сайта (вроде тех, которые совершаются к конечной точке /info
). В частности, разработчики этого браузера планируют запретить расширениям считывать ответы на HTTP-запросы сайта, а значит, PyMyFlySky не сможет считывать данные, возвращаемые /info
. Насколько я понимаю, эти ограничения частично вызваны заботой о безопасности, а отчасти служат для усложнения разработки блокировщиков рекламы. Как бы то ни было, но PyMyFlySpy требует Firefox.
Дальнейшие планы — подписка на события
PyMyFlySpy даёт нам программный доступ к данным о полёте. И будет здорово использовать их для активации различных событий вроде таких:
- «В течение первой половины полёта позволять мне открывать только большой отчёт, который нужно закончить к 17:00».
- «Когда мы окажемся в радиусе 10 миль от Великого Каньона, отправить детям сообщение в Slack с призывом выглянуть в иллюминатор. Также отправить сообщение Slack и мне, чтобы я сам им об этом сказал.»
- «Если высота начнёт падать со скоростью больше 300 футов/секунду, воспроизводить на всех моих устройствах спокойный, но очень назойливый звук ».
Пожалуй, займусь этим в следующие праздники.
Полёт домой
Обратный перелёт был вечерним. Мы взобрались на борт и взлетели. Я достал ноутбук, подключился к WiFi, загрузил PyMyFlySpy и повернулся к сыну, чтобы показать, где мы находимся. Я показывал ему прототип программы всю прошлую неделю, и его реакция была где-то между «безразличием» и «лёгким интересом». Но он уже уснул. Тогда я сделал несколько скриншотов, чтобы показать их ему позже.
Следующие несколько часов я провёл за мониторингом и отладкой регистратора, чтобы обеспечить его устойчивую работу. Мой двухлетний сын кричал на протяжении всего полёта, то и дело норовя вывалиться на пол. Я поддерживающим взглядом посматривал на жену, которая сидела с ним через проход от меня, и давал понять, что готов его взять. Но супруга мотала головой — она понимала, что у меня важное дело.
Я просматривал графики. Температура была в норме, воздушная скорость стабильна, и тут внезапно высота упала на пятьдесят футов. Мне пришла мысль сообщить об этом пилотам, но я решил, что у них наверняка всё под контролем. Хотя наблюдение всё же продолжил — мало ли.
Автор: Bright_Translate