Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
На карантине мне предложили поучаствовать в разработке устройства измерения скорости LTE-модемов для нескольких операторов сотовой связи.
Заказчик хотел оценить скорость всевозможных операторов связи в различных географических точках, для того чтобы можно было понять, какой оператор сотовой связи ему наиболее оптимален при установке оборудования, использующее LTE-соединение, например, для видеотрансляций. При этом задачу нужно было решить максимально просто и дешево, без дорогостоящего оборудования.
Сразу скажу, что задача не самая простая и наукоемкая, расскажу, какие проблемы мне встретились и как я их решал. Итак, поехали.
Измерение скорости LTE-соединение дело весьма сложное: необходимо правильно выбрать оборудования и методику измерения, также хорошо представлять топологию и работу сотовой сети. Плюс на скорость может влиять несколько факторов: количество абонентов на соте, погодные условия, даже от соты к соте скорость может разительно отличаться из-за топологии сети. В общем, эта задача с огромным количеством неизвестных, и ее правильно решить может только оператор связи.
Изначально заказчик хотел просто гонять курьера с телефонами операторов, проводить измерения прямо на телефоне и далее в тетрадку записывать результаты измерения скорости. Мое решение измерения скорости сетей lte, хоть не идеально, но решает поставленную задачу.
Из-за нехватки времени, я принимал решения не в пользу удобства или практичности, а в пользу скорости разработки. Например, для удаленного доступа поднимался обратный ssh, вместо более практичного vpn, ради экономии времени на настройку сервера и каждого отдельного клиента.
Как сказано в статье Без ТЗ: почему клиент не хочет его: Не работайте без ТЗ! Никогда, нигде!
Техническое задание было достаточно простое, я немного его расширю для понимания конечного пользователя. Выбор технических решений и оборудования был продиктован заказчиком. Итак, само ТЗ, после всех согласований:
ТЗ я описал в вольной форме, после множества согласований. Но смысл задачи уже виден. Срок на все про все был дан неделя. Но в реальности он растянулся на три недели. Это с учетом того, что я делал это только после основной работы и по выходным.
Здесь я хочу еще раз обратить внимание, что заказчиком было заранее оговорено использование сервиса измерения скорости и аппаратное обеспечение, что сильно ограничило мои возможности. Был так же ограничен бюджет, поэтому особо ничего не докупалось. Так что пришлось играть по данным правилам.
Схема проста и очевидна. Поэтому оставлю ее без особых комментариев.
Весь проект решил реализовать на python, несмотря на то, что опыта разработки на этом языке у меня не было совсем. Выбрал его, так как была куча готовых примеров и решений, которые могли ускорить разработку. Поэтому, прошу всех профессиональных программистов не ругать мой первый опыт разработки на python, и всегда с удовольствием готов услышать конструктивную критику, для повышения своего скилла.
Также в процессе открыл, что python имеет две ходовые версии 2 и 3, в результате остановился на третьей.
В качестве основной машины мне был дан одноплатник vim2
Отличный, мощный медиакомбайн для умного дома и SMART-TV, но на редкость неподходящий для данной задачи, или скажем так, слабо подходящий. Например, его главная ОС — это Android, а Linux — это попутная ОС, и соответственно никто не гарантирует качественной работы всех узлов и драйверов под Linux. И я предполагаю, что часть проблем была связана с драйверами USB данной платформы, поэтому модемы работали на данной плате не так как ожидал. Так же у него очень плохая и разрозненная документация, поэтому каждая операция занимала много времени копания в доках. Даже рядовая работа с GPIO попила много крови. Например, чтобы настроить работу со светодиодом, мне понадобилось несколько часов. Но, если быть объективным, то принципиально не было важно, что за одноплатник, главное, чтобы работал и были USB-порты.
Для начала мне нужно установить Linux на данную плату. Чтобы не рыскать всем по дебрям документации, а также для тех, кто будет разбираться с этим одноплатником, пишу данную главу.
Есть два варианта установить линукс: на внешнюю SD-карту, либо на внутреннюю MMC. С картой я побился вечерок, так и не вкурил как же заставить работать, поэтому решил устанавливать на MMC, хотя без сомнения с внешней картой много проще было бы работать.
О прошивке криво рассказано тут. Перевожу со странного на русский. Для того, чтобы прошить плату, мне необходимо подключить аппаратный UART. Подключал его следующим образом.
После чего, я скачал прошивку отсюда. Конкретная версия прошивки VIM1_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.
Для того, чтобы залить данную прошивку, мне необходимы нужны утилиты. Более подробно об этом рассказано тут. Под Windows не пробовал прошивать, а вот о прошивке под Linux надо пару слов рассказать. Для начала установлю утилиты, согласно инструкции.
Иии… Ничего не работает. Потратил пару часов занимаясь правками установочных скриптов, чтобы все корректно установилось у меня. Что там делал не помню, но тоже еще тот цирк с конями. Так что будьте осторожны. Но без этих утилит дальше мучить vim2 смысла нет. Лучше с ним вообще не связываться!
После семи кругов ада, конфигурации скриптов и установки получил пакет работающих утилит. Подключил плата по USB к моему компьютеру линукс, и так же подключен UART по схеме выше.
Настраиваю мой любимый терминал minicom на скорость 115200, без аппаратного и программного контроля ошибок. И приступаем.
При загрузке VIM2 в терминале UART нажимаю какую-либо клавишу, например пробел, чтобы остановить загрузку. После того, как появится строка
Ввожу команду:
На хосте, откуда загружаем, выполняю:
Все, фух. Прошил, на плате есть Linux. Логин/пароль khadas:khadas.
После этого небольшие первичные настройки. Для дальнейшей работы отключаю пароль у sudo (да, не безопасно, но удобно).
Редактирую строку до вида и сохраняем
После чего меняю текущую локаль, чтобы время было по Москве, иначе будет по Гринвичу.
либо
Если вам показалось сложно, то не пользуйтесь данной платой, лучше Raspberry Pi. Честно.
Данный модем у меня попил крови знатно, и, по сути, он и стал самым узким местом всего проекта. Вообще, название “модем” для данных устройств совершенно не отражает суть работы: это мощнейший комбайн, эта железяка имеет составное устройство, которое прикидывается CD-ROM для того, чтобы установить драйвера, а потом переходит в режим сетевой карты.
Архитектурно, с точки зрения пользователя Linux после всех настроек, выглядит так: после подключения модема, у меня появляется сетевой интерфейс eth*, который по dhcp получает ip адрес 192.168.8.100, и шлюз по умолчанию 192.168.8.1.
И самый главный момент! Данная модель модема, не умеет работать в режиме именно модема, который управляется АТ-командами. Все было бы сильно проще, создать ppp-соединения на каждый модем и дальше уже оперировать с ними. Но в моем случае «сам» (точнее дайвера Linux согласно правилам udev), создает eth-интерфейс и по dhcp назначают ему ip-адрес.
Чтобы дальше не путаться, предлагаю забыть слово «модем» и говорить сетевая карта и шлюз, ибо по сути, это как подключение новой сетевой карты со шлюзом.
Когда один модем, это не вызывает особых проблем, но когда их больше одного, а именно n-штук, то возникает следующая картина сети.
То есть n сетевых карт, с одним IP-адресом, у каждого один и тот же шлюз по умолчанию. Но по факту, каждый из них подключен к своему оператору.
Изначально у меня было простое решение: с помощью команды ifconfig или ip гасить все интерфейсы и просто включать по очереди один и тестировать его. Решение было всем хорошо, кроме того, что в моменты коммутации я не имел возможности подключиться к устройству. А поскольку коммутации частые и быстрые, то фактически у меня не было возможности подключиться вообще.
Поэтому я выбрал путь менять «вручную» ip-адреса модемов и дальше гонять трафик с помощью настроек маршрутизации.
На этом у меня проблемы с модемами не закончились: в случае проблем с питанием, они отваливались, требовалось хорошее стабильное питание USB-хаба. Эту проблему решил жестко припаяв питание прямо к хабу. Другая проблема, с которой я столкнулся и которая погубила весь проект: после перезагрузки или холодного старта устройства определялись не все модемы и не всегда, и почему это происходило и по какому алгоритму мне установить не удалось. Но обо всем по порядку.
Для корректной работы модема, я установил пакет usb-modeswitch.
После чего, модем после подключения будет корректно определяться и конфигурироваться подсистемой udev. Проверяю, просто подключив модем и убедившись, что сеть появилась.
Еще одна проблема, которую я не смог решить: это как из этого модема получить имя оператора, с которым мы работаем? Имя оператора содержится в веб-интерфейсе модема по адресу 192.168.8.1. Это динамическая веб-страница, которая получает данные посредством ajax-запросов, поэтому просто wget-тнуть страницу и спарсить имя не получится. Поэтому начал смотреть, как отработать web-страницу и т.п., и понял, что занимаюсь какой-то ерундой. В результате плюнул, и оператора начал получать с помощью API самого Speedtest.
Многое было бы проще, если бы у модема был бы доступ через AT-команды. Можно было бы его переконфигурировать, создавать ppp-соединение, назначать IP, получать оператора связи и т.д. Но увы, работаю с тем что дали.
GPS-приемник, который мне выдали, имел интерфейс UART и питание. Это было не самое лучшее решение, но тем не менее рабочее и простое. Приемник был примерно такого вида.
Честно говоря, впервые работал с GPS-приемником, но как предполагал, все давно придумано за нас. Так что просто пользуемся готовыми решениями.
Для начала включаю uart_AO_B (UART_RX_AO_B, UART_TX_AO_B) для подключения GPS.
После проверяю успешность операции.
Данная команда, судя по всему, на лету редактирует devtree, что весьма удобно.
После успеха этой операции перезагружаемся и устанавливаем gps-демон.
Установка gps-демона. Устанавливаю все и отрубаю его сразу для дальнейшей конфигурации.
Редактирую файл настроек.
Устанавливаю UART, на котором будет висеть GPS.
И после все включаем и стартуем.
После чего, подключаю GPS.
В руках провод GPS, под пальцами видны провода UART отладчика.
Перезагружаюсь, и проверяю работу GPS с помощью программы gpsmon.
На этом скриншоте спутников не видать, но видно общение с GPS-приемником, и это говорит, что все хорошо.
На python опробовал много вариантов работы с данным демоном, но я остановился на том, который корректно работал с python 3.
Устанавливаю необходимую библиотеку.
И ваяю код работы.
Если мне нужно получить координаты, то делается это следующим вызовом:
И в течении 1-10 секунд я либо получу координату, либо нет. Да, у меня попыток получить координаты было десять. Не оптимально, криво и косо, но работает. Я решил так сделать, потому что GPS может ловить плохо и не всегда получать данные. Если ждать получения данных, то в случае работы в глухом помещении, программа зависнет в это месте. Поэтому реализовал такой не элегантный вариант.
В принципе, было бы больше времени, можно было бы напрямую по UART получать данные с GSP, парсить их в отдельном потоке и работать с ними. Но времени не было совсем, отсюда лютый некрасивый код. И да, мне не стыдно.
С подключением светодиода было все просто и сложно одновременно. Главная сложность в том, что номер пина в системе не соответствует номеру пина на плате и потому что документация написана левой пяткой. Чтобы сопоставить номер аппаратного пина и номер пина в ОС, надо выполнить команду:
Будет выведена таблица соответствия пина в системе, и на плате. После чего я уже могу оперировать пином в самой ОС. В моем случае светодиод подключен к GPIOH_5.
Перевожу пин GPIO в режим вывода.
Записываю нуль.
Записываю единицу.
Все горит, после записи «1»
Теперь, в случае ошибок я вызываю error_blink() и светодиод нам красиво помигает.
Большая радость, что у сервиса speedtest.net есть свой собственный python-API, посмотреть можно на Github.
Чем хорошо, что есть исходные коды, которые тоже можно посмотреть. Как работать с данным API (простейшие примеры) можно посмотреть в соответствующем разделе.
Устанавливаю python-библиотеку следующей командой.
Для примера вы можете вообще поставить спидтестер в Ubuntu прямо из реп. Это тоже самое python-приложение, которое потом можно запустить прямо из консоли.
И произвести замеры скорости вашего интернета.
В результате, как сделал это я. Мне пришлось влезть в исходные коды этого спидтеста, чтобы более полно внедрить их в мой проект. Одна из важнейших задач — это получать еще имя оператора связи, для подстановки его в табличку.
Здесь тоже оказалось не так все просто, хотя, казалось бы, куда проще. Изначально параметр servers у меня был равен [], мол выбери лучший сервер. В результате у меня были случайные сервера, и как нетрудно догадаться, плавающая скорость. Это достаточно сложная тема, использовать фиксированный сервер, если да, то какой или динамический, требует исследования. Но вот пример графиков замеров скорости оператора Билайна при динамическом выборе тестового сервера и статически зафиксированного.
Результат измерения скорости при выборе динамического сервера.
Результат тестирования скорости, при одном строго выбранном одном сервере.
«Шерсть» при тестировании есть и там и там, и ее нужно убирать математическими методами. Но при фиксированном сервере ее немного меньше и амплитуда стабильнее.
Вообще это место больших исследований. И я бы проводил замеры скорости к своему серверу, по средством утилиты iperf. Но мы не отходим от ТЗ.
Для отправки почты попробовал несколько десятков различных вариантов, но в результате остановился на следующем. Зарегистрировал почтовый ящик на yandex и далее взял данный пример отправки почты. Проверил его и внедрил в программу. В этом примере разбираются различные варианты, в том числе отправка с gmail и т.п. Возится с поднятием своего почтового сервера мне не хотелось и не было времени на это, но как потом оказалось тоже напрасно.
Отправка логов производил по планировщику, при наличии связи, каждые 6 часов: в 00 часов, 06 утра, 12 дня и 18 вечера. Отправлял следующим образом.
Ошибки тоже изначально отправлялись. Для начала они накапливались в списке, и потом отправлял также с помощью планировщика, при наличии связи. Однако потом возникли проблемы с тем, что yandex имеет ограничение на количество отправляемых сообщений в сутки (это боль, печаль и унижение). Поскольку ошибок даже в минуту могло быть огромное количество, соответственно от отправки ошибок по почте пришлось отказаться. Так что имейте в виду, при автоматической отправки через сервисы яндекса о такой проблеме.
Для того, чтобы иметь доступ к удаленной железке и иметь возможность ее донастроить и переконфигурировать мне понадобился внешний сервер. Вообще, справедливости говоря, правильно было бы все данные отправлять на сервер и в веб-интерфейсе строить все красивые графики. Но не все сразу.
В качестве VPS я выбрал ruvds.com. Можно было бы взять самый простой сервер. И в целом для моих бы целей этого хватило бы за глаза. Но поскольку платил за сервер не из своего кармана, решил взять с небольшим запасом, чтобы хватило, если будем разворачивать web-интерфейс, свой SMTP-сервер, vpn и т.д. Плюс иметь возможность настроить Telegram-бота и не иметь проблем с его блокировками. Поэтому выбрал Amsterdam и следующие параметры.
В качестве способа связи с железкой vim2 выбрал обратное ssh соединение и как показала практика — не самое лучшее. При разрыве соединения, сервер удерживает порт и по нему невозможно подключиться некоторое время. Поэтому, все же лучше использовать другие способы связи, например vpn. В будущем хотел перейти на vpn, но не успел.
Не буду вдаваться в подробности настройки файрвола, ограничения прав, отключения ssh соединения root и прочие прописные истины настройки VPS. Хочется верить, что вы и так все знаете. Для удаленного соединения, создаю нового пользователя на сервере.
На нашей железке генерирую ключи ssh соединения.
И копирую их на наш сервер.
На нашей железке создаю автоматическое подключение обратного ssh при каждой загрузке.
Обратите внимание на порт 8083: он и определяет по какому порту у меня будет осуществляется подключение через обратный ssh. Добавляем в автозагрузку и стартуем.
Можно даже посмотреть статус:
Теперь, на нашем VPS-сервере, если выполнить:
То я попадаю на мою тестовую железку. И с железки могу так же отправлять логи и любые данные по ssh на мой сервер, что весьма удобно.
Включение, приступаем к разработке и отладке
Фух, ну вроде все, описал все узлы. Теперь пришло время собрать все это в единую кучу. Код можно посмотреть вот тут.
Важный момент с кодом: Данный проект вот так вот «влоб» может не запуститься, так как затачивался на определенную задачу, определенной архитектуры. Хоть я и даю исходники, но все же самое ценное разберу вот тут, прямо в тексте, иначе совершенно непонятно.
В начале у меня идет инициализация gps, gpio и запуск отдельного потока планировщика.
Планировщик достаточно прост: он смотрит не пришло ли время отправки сообщений и какой сейчас стоит статус ошибок. Если есть флаг ошибки, то мигаем светодиодом.
Самый сложный момент в данном проекте — это сохранять обратное ssh-соединение при каждом тесте. В каждом тесте идет заново настройка шлюза по умолчанию и dns-сервера. Поскольку все равно никто не читает, то знайте, что поезд не катается по деревянным рельсам. Кто найдет пасхалку, тому конфетка.
Для этого я создаю отдельную таблица маршрутизации --set-mark 0x2 и правило для перенаправления трафика.
Подробнее о том, как это работает можно прочитать в этой статье.
После чего перехожу в бесконечный цикл, где каждый раз получаем список подключенных модемов (чтобы узнать, вдруг конфигурация сети изменилась).
Получение списка сетевых интерфейсов достаточно простое.
После получения списка, задаю IP-адреса всем интерфейсам, как я приводил на картинке в главе про модем.
Далее просто в цикле иду по каждому интерфейсу. И конфигурирую каждый интерфейс.
Проверяю интерфейс на работоспособность, если сети нет, то формирую ошибки. Если сеть есть, то время действовать!
Здесь я настраиваю ssh маршрутизацию на данный интерфейс (если не было сделано), отправляю ошибки на сервер, если время пришло, отправляю логи и в конце концов провожу speedtest и сохраняем логи в csv-файл.
Разве что стоит сказать о функции настройки обратного ssh.
Ну и конечно же, необходимо всю эту красоту добавить в автозагрузку. Для этого создаю файл:
И записываю в него:
Включаю автозагрузку и стартую!
Теперь я могу смотреть логи того, что происходит с помощью команды:
Ну теперь самое главное, что же получилось в результате? Приведу несколько графиков, которые мне удалось заснять в процессе разработки и отладки. Графики строились с помощью gnuplot следующим скриптом.
Первый опыт был оператора Tele2, который я проводил в течении нескольких дней.
Здесь я использовал динамический измеряющий сервер. Замеры скорости работают, но очень сильно плавают, однако все же видна некоторая средняя величина, и ее можно получить, произведя фильтрацию данных, например, скользящим средним.
Позднее я построил еще ряд графиков, для других операторов связи. Сервер тестирования в этом случае уже был один, и результаты тоже очень интересные.
Как видно тема очень обширная для исследований и обработки этих данных, и явно не тянет на пару недель работы. Но…
Работа была резко завершена по независящим от меня обстоятельствам. Одной из слабых сторон данного проекта, на мой субъективный взгляд, был модем, который не очень хотел работать одновременно с другими модемами, и при каждой загрузке выделывал такие фортеля. Для данных целей существует громадное количество других моделей модемов, обычно они уже имеют формат Mini PCI-e и ставятся внутрь устройства и их сильно проще конфигурировать. Но это уже совсем другая история. Проект был интересный и был очень рад, что удалось в нем поучаствовать.
Заказчик хотел оценить скорость всевозможных операторов связи в различных географических точках, для того чтобы можно было понять, какой оператор сотовой связи ему наиболее оптимален при установке оборудования, использующее LTE-соединение, например, для видеотрансляций. При этом задачу нужно было решить максимально просто и дешево, без дорогостоящего оборудования.
Сразу скажу, что задача не самая простая и наукоемкая, расскажу, какие проблемы мне встретились и как я их решал. Итак, поехали.
Примечание
Измерение скорости LTE-соединение дело весьма сложное: необходимо правильно выбрать оборудования и методику измерения, также хорошо представлять топологию и работу сотовой сети. Плюс на скорость может влиять несколько факторов: количество абонентов на соте, погодные условия, даже от соты к соте скорость может разительно отличаться из-за топологии сети. В общем, эта задача с огромным количеством неизвестных, и ее правильно решить может только оператор связи.
Изначально заказчик хотел просто гонять курьера с телефонами операторов, проводить измерения прямо на телефоне и далее в тетрадку записывать результаты измерения скорости. Мое решение измерения скорости сетей lte, хоть не идеально, но решает поставленную задачу.
Из-за нехватки времени, я принимал решения не в пользу удобства или практичности, а в пользу скорости разработки. Например, для удаленного доступа поднимался обратный ssh, вместо более практичного vpn, ради экономии времени на настройку сервера и каждого отдельного клиента.
Техническое задание
Как сказано в статье Без ТЗ: почему клиент не хочет его: Не работайте без ТЗ! Никогда, нигде!
Техническое задание было достаточно простое, я немного его расширю для понимания конечного пользователя. Выбор технических решений и оборудования был продиктован заказчиком. Итак, само ТЗ, после всех согласований:
На базе одноплатного компьютера vim2 сделать тестер скорости lte-соединения через модемы Huawei e3372h — 153 нескольких операторов связи (от одного до n). Так же необходимо получать координаты с GPS-приемника, подключенного по UART. Замеры скорости производить с помощью сервиса www.speedtest.net и сводить их в таблицу вида:
Таблица в формате csv. После чего отсылать на е-майл каждые 6 часов данную табличку. В случае возникновения ошибок мигать светодиодом, который подключен к GPIO.
ТЗ я описал в вольной форме, после множества согласований. Но смысл задачи уже виден. Срок на все про все был дан неделя. Но в реальности он растянулся на три недели. Это с учетом того, что я делал это только после основной работы и по выходным.
Здесь я хочу еще раз обратить внимание, что заказчиком было заранее оговорено использование сервиса измерения скорости и аппаратное обеспечение, что сильно ограничило мои возможности. Был так же ограничен бюджет, поэтому особо ничего не докупалось. Так что пришлось играть по данным правилам.
Архитектура и разработка
Схема проста и очевидна. Поэтому оставлю ее без особых комментариев.
Весь проект решил реализовать на python, несмотря на то, что опыта разработки на этом языке у меня не было совсем. Выбрал его, так как была куча готовых примеров и решений, которые могли ускорить разработку. Поэтому, прошу всех профессиональных программистов не ругать мой первый опыт разработки на python, и всегда с удовольствием готов услышать конструктивную критику, для повышения своего скилла.
Также в процессе открыл, что python имеет две ходовые версии 2 и 3, в результате остановился на третьей.
Аппаратные узлы
Одноплатник vim2
В качестве основной машины мне был дан одноплатник vim2
Отличный, мощный медиакомбайн для умного дома и SMART-TV, но на редкость неподходящий для данной задачи, или скажем так, слабо подходящий. Например, его главная ОС — это Android, а Linux — это попутная ОС, и соответственно никто не гарантирует качественной работы всех узлов и драйверов под Linux. И я предполагаю, что часть проблем была связана с драйверами USB данной платформы, поэтому модемы работали на данной плате не так как ожидал. Так же у него очень плохая и разрозненная документация, поэтому каждая операция занимала много времени копания в доках. Даже рядовая работа с GPIO попила много крови. Например, чтобы настроить работу со светодиодом, мне понадобилось несколько часов. Но, если быть объективным, то принципиально не было важно, что за одноплатник, главное, чтобы работал и были USB-порты.
Для начала мне нужно установить Linux на данную плату. Чтобы не рыскать всем по дебрям документации, а также для тех, кто будет разбираться с этим одноплатником, пишу данную главу.
Есть два варианта установить линукс: на внешнюю SD-карту, либо на внутреннюю MMC. С картой я побился вечерок, так и не вкурил как же заставить работать, поэтому решил устанавливать на MMC, хотя без сомнения с внешней картой много проще было бы работать.
О прошивке криво рассказано тут. Перевожу со странного на русский. Для того, чтобы прошить плату, мне необходимо подключить аппаратный UART. Подключал его следующим образом.
- Tool Pin GND: <—> Pin17 of VIMs’s GPIO
- Tool Pin TXD: <—> Pin18 of VIMs’s GPIO (Linux_Rx)
- Tool Pin RXD: <—> Pin19 of VIMs’s GPIO (Linux_Tx)
- Tool Pin VCC: <—> Pin20 of VIMs’s GPIO
После чего, я скачал прошивку отсюда. Конкретная версия прошивки VIM1_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.
Для того, чтобы залить данную прошивку, мне необходимы нужны утилиты. Более подробно об этом рассказано тут. Под Windows не пробовал прошивать, а вот о прошивке под Linux надо пару слов рассказать. Для начала установлю утилиты, согласно инструкции.
git clone https://github.com/khadas/utils
cd /path/to/utils
sudo ./INSTALL
Иии… Ничего не работает. Потратил пару часов занимаясь правками установочных скриптов, чтобы все корректно установилось у меня. Что там делал не помню, но тоже еще тот цирк с конями. Так что будьте осторожны. Но без этих утилит дальше мучить vim2 смысла нет. Лучше с ним вообще не связываться!
После семи кругов ада, конфигурации скриптов и установки получил пакет работающих утилит. Подключил плата по USB к моему компьютеру линукс, и так же подключен UART по схеме выше.
Настраиваю мой любимый терминал minicom на скорость 115200, без аппаратного и программного контроля ошибок. И приступаем.
При загрузке VIM2 в терминале UART нажимаю какую-либо клавишу, например пробел, чтобы остановить загрузку. После того, как появится строка
kvim2#
Ввожу команду:
kvim2# run update
На хосте, откуда загружаем, выполняю:
burn-tool -v aml -b VIM2 -i VIM2_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.img
Все, фух. Прошил, на плате есть Linux. Логин/пароль khadas:khadas.
После этого небольшие первичные настройки. Для дальнейшей работы отключаю пароль у sudo (да, не безопасно, но удобно).
sudo visudo
Редактирую строку до вида и сохраняем
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
После чего меняю текущую локаль, чтобы время было по Москве, иначе будет по Гринвичу.
sudo timedatectl set-timezone Europe/Moscow
либо
ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime
Если вам показалось сложно, то не пользуйтесь данной платой, лучше Raspberry Pi. Честно.
Модем Huawei e3372h — 153
Данный модем у меня попил крови знатно, и, по сути, он и стал самым узким местом всего проекта. Вообще, название “модем” для данных устройств совершенно не отражает суть работы: это мощнейший комбайн, эта железяка имеет составное устройство, которое прикидывается CD-ROM для того, чтобы установить драйвера, а потом переходит в режим сетевой карты.
Архитектурно, с точки зрения пользователя Linux после всех настроек, выглядит так: после подключения модема, у меня появляется сетевой интерфейс eth*, который по dhcp получает ip адрес 192.168.8.100, и шлюз по умолчанию 192.168.8.1.
И самый главный момент! Данная модель модема, не умеет работать в режиме именно модема, который управляется АТ-командами. Все было бы сильно проще, создать ppp-соединения на каждый модем и дальше уже оперировать с ними. Но в моем случае «сам» (точнее дайвера Linux согласно правилам udev), создает eth-интерфейс и по dhcp назначают ему ip-адрес.
Чтобы дальше не путаться, предлагаю забыть слово «модем» и говорить сетевая карта и шлюз, ибо по сути, это как подключение новой сетевой карты со шлюзом.
Когда один модем, это не вызывает особых проблем, но когда их больше одного, а именно n-штук, то возникает следующая картина сети.
То есть n сетевых карт, с одним IP-адресом, у каждого один и тот же шлюз по умолчанию. Но по факту, каждый из них подключен к своему оператору.
Изначально у меня было простое решение: с помощью команды ifconfig или ip гасить все интерфейсы и просто включать по очереди один и тестировать его. Решение было всем хорошо, кроме того, что в моменты коммутации я не имел возможности подключиться к устройству. А поскольку коммутации частые и быстрые, то фактически у меня не было возможности подключиться вообще.
Поэтому я выбрал путь менять «вручную» ip-адреса модемов и дальше гонять трафик с помощью настроек маршрутизации.
На этом у меня проблемы с модемами не закончились: в случае проблем с питанием, они отваливались, требовалось хорошее стабильное питание USB-хаба. Эту проблему решил жестко припаяв питание прямо к хабу. Другая проблема, с которой я столкнулся и которая погубила весь проект: после перезагрузки или холодного старта устройства определялись не все модемы и не всегда, и почему это происходило и по какому алгоритму мне установить не удалось. Но обо всем по порядку.
Для корректной работы модема, я установил пакет usb-modeswitch.
sudo apt update
sudo apt install -y usb-modeswitch
После чего, модем после подключения будет корректно определяться и конфигурироваться подсистемой udev. Проверяю, просто подключив модем и убедившись, что сеть появилась.
Еще одна проблема, которую я не смог решить: это как из этого модема получить имя оператора, с которым мы работаем? Имя оператора содержится в веб-интерфейсе модема по адресу 192.168.8.1. Это динамическая веб-страница, которая получает данные посредством ajax-запросов, поэтому просто wget-тнуть страницу и спарсить имя не получится. Поэтому начал смотреть, как отработать web-страницу и т.п., и понял, что занимаюсь какой-то ерундой. В результате плюнул, и оператора начал получать с помощью API самого Speedtest.
Многое было бы проще, если бы у модема был бы доступ через AT-команды. Можно было бы его переконфигурировать, создавать ppp-соединение, назначать IP, получать оператора связи и т.д. Но увы, работаю с тем что дали.
GPS
GPS-приемник, который мне выдали, имел интерфейс UART и питание. Это было не самое лучшее решение, но тем не менее рабочее и простое. Приемник был примерно такого вида.
Честно говоря, впервые работал с GPS-приемником, но как предполагал, все давно придумано за нас. Так что просто пользуемся готовыми решениями.
Для начала включаю uart_AO_B (UART_RX_AO_B, UART_TX_AO_B) для подключения GPS.
khadas@Khadas:~$ sudo fdtput -t s /dtb.img /serial@c81004e0 status okay
После проверяю успешность операции.
khadas@Khadas:~$ fdtget /dtb.img /serial@c81004e0 status
okay
Данная команда, судя по всему, на лету редактирует devtree, что весьма удобно.
После успеха этой операции перезагружаемся и устанавливаем gps-демон.
khadas@Khadas:~$ sudo reboot
Установка gps-демона. Устанавливаю все и отрубаю его сразу для дальнейшей конфигурации.
sudo apt install gpsd gpsd-clients -y
sudo killall gpsd
/* GPS daemon stop/disable */
sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socket
Редактирую файл настроек.
sudo vim /etc/default/gpsd
Устанавливаю UART, на котором будет висеть GPS.
DEVICES="/dev/ttyS4"
И после все включаем и стартуем.
/* GPS daemon enable/start */
sudo systemctl enable gpsd.socket
sudo systemctl start gpsd.socket
После чего, подключаю GPS.
В руках провод GPS, под пальцами видны провода UART отладчика.
Перезагружаюсь, и проверяю работу GPS с помощью программы gpsmon.
На этом скриншоте спутников не видать, но видно общение с GPS-приемником, и это говорит, что все хорошо.
На python опробовал много вариантов работы с данным демоном, но я остановился на том, который корректно работал с python 3.
Устанавливаю необходимую библиотеку.
sudo -H pip3 install gps3
И ваяю код работы.
from gps3.agps3threaded import AGPS3mechanism
...
def getPositionData(agps_thread):
counter = 0;
while True:
longitude = agps_thread.data_stream.lon
latitude = agps_thread.data_stream.lat
if latitude != 'n/a' and longitude != 'n/a':
return '{}' .format(longitude), '{}' .format(latitude)
counter = counter + 1
print ("Wait gps counter = %d" % counter)
if counter == 10:
ErrorMessage("Ошибка GPS приемника!!!")
return "NA", "NA"
time.sleep(1.0)
...
f __name__ == '__main__':
...
#gps
agps_thread = AGPS3mechanism() # Instantiate AGPS3 Mechanisms
agps_thread.stream_data() # From localhost (), or other hosts, by example, (host='gps.ddns.net')
agps_thread.run_thread() # Throttle time to sleep after an empty lookup, default '()' 0.2 two tenths of a second
Если мне нужно получить координаты, то делается это следующим вызовом:
longitude, latitude = getPositionData(agps_thread)
И в течении 1-10 секунд я либо получу координату, либо нет. Да, у меня попыток получить координаты было десять. Не оптимально, криво и косо, но работает. Я решил так сделать, потому что GPS может ловить плохо и не всегда получать данные. Если ждать получения данных, то в случае работы в глухом помещении, программа зависнет в это месте. Поэтому реализовал такой не элегантный вариант.
В принципе, было бы больше времени, можно было бы напрямую по UART получать данные с GSP, парсить их в отдельном потоке и работать с ними. Но времени не было совсем, отсюда лютый некрасивый код. И да, мне не стыдно.
Светодиод
С подключением светодиода было все просто и сложно одновременно. Главная сложность в том, что номер пина в системе не соответствует номеру пина на плате и потому что документация написана левой пяткой. Чтобы сопоставить номер аппаратного пина и номер пина в ОС, надо выполнить команду:
gpio readall
Будет выведена таблица соответствия пина в системе, и на плате. После чего я уже могу оперировать пином в самой ОС. В моем случае светодиод подключен к GPIOH_5.
Перевожу пин GPIO в режим вывода.
gpio -g mode 421 out
Записываю нуль.
gpio -g write 421 0
Записываю единицу.
gpio -g write 421 1
Все горит, после записи «1»
#gpio subsistem
def gpio_init():
os.system("gpio -g mode 421 out")
os.system("gpio -g write 421 1")
def gpio_set(val):
os.system("gpio -g write 421 %d" % val)
def error_blink():
gpio_set(0)
time.sleep(0.1)
gpio_set(1)
time.sleep(0.1)
gpio_set(0)
time.sleep(0.1)
gpio_set(1)
time.sleep(0.1)
gpio_set(0)
time.sleep(1.0)
gpio_set(1)
def good_blink():
gpio_set(1)
Теперь, в случае ошибок я вызываю error_blink() и светодиод нам красиво помигает.
Программные узлы
Speedtest API
Большая радость, что у сервиса speedtest.net есть свой собственный python-API, посмотреть можно на Github.
Чем хорошо, что есть исходные коды, которые тоже можно посмотреть. Как работать с данным API (простейшие примеры) можно посмотреть в соответствующем разделе.
Устанавливаю python-библиотеку следующей командой.
sudo -H pip3 install speedtest-cli
Для примера вы можете вообще поставить спидтестер в Ubuntu прямо из реп. Это тоже самое python-приложение, которое потом можно запустить прямо из консоли.
sudo apt install speedtest-cli -y
И произвести замеры скорости вашего интернета.
speedtest-cli
Retrieving speedtest.net configuration...
Testing from B***** (*.*.*.*)...
Retrieving speedtest.net server list...
Selecting best server based on ping...
Hosted by MTS (Moscow) [0.12 km]: 11.8 ms
Testing download speed................................................................................
Download: 7.10 Mbit/s
Testing upload speed......................................................................................................
Upload: 3.86 Mbit/s
В результате, как сделал это я. Мне пришлось влезть в исходные коды этого спидтеста, чтобы более полно внедрить их в мой проект. Одна из важнейших задач — это получать еще имя оператора связи, для подстановки его в табличку.
import speedtest
from datetime import datetime
...
#Указываем конкретный сервер для теста
#6053) MaximaTelecom (Moscow, Russian Federation)
servers = ["6053"]
# If you want to use a single threaded test
threads = None
s = speedtest.Speedtest()
#получаем имя оператора сотовой связи
opos = '%(isp)s' % s.config['client']
s.get_servers(servers)
#получаем текстовую строку с параметрами сервера
testserver = '%(sponsor)s (%(name)s) [%(d)0.2f km]: %(latency)s ms' % s.results.server
#тест загрузки
s.download(threads=threads)
#тест выгрузки
s.upload(threads=threads)
#получаем результаты
s.results.share()
#После чего формируется строка для записи в csv-файл.
#получаем позицию GPS
longitude, latitude = getPositionData(agps_thread)
#время и дата
curdata = datetime.now().strftime('%d.%m.%Y')
curtime = datetime.now().strftime('%H:%M:%S')
delimiter = ';'
result_string = opos + delimiter + str(curpos) + delimiter + \
curdata + delimiter + curtime + delimiter + longitude + ', ' + latitude + delimiter + \
str(s.results.download/1000.0/1000.0) + delimiter + str(s.results.upload / 1000.0 / 1000.0) + \
delimiter + str(s.results.ping) + delimiter + testserver + "\n"
#тут идет запись в файл логов
Здесь тоже оказалось не так все просто, хотя, казалось бы, куда проще. Изначально параметр servers у меня был равен [], мол выбери лучший сервер. В результате у меня были случайные сервера, и как нетрудно догадаться, плавающая скорость. Это достаточно сложная тема, использовать фиксированный сервер, если да, то какой или динамический, требует исследования. Но вот пример графиков замеров скорости оператора Билайна при динамическом выборе тестового сервера и статически зафиксированного.
Результат измерения скорости при выборе динамического сервера.
Результат тестирования скорости, при одном строго выбранном одном сервере.
«Шерсть» при тестировании есть и там и там, и ее нужно убирать математическими методами. Но при фиксированном сервере ее немного меньше и амплитуда стабильнее.
Вообще это место больших исследований. И я бы проводил замеры скорости к своему серверу, по средством утилиты iperf. Но мы не отходим от ТЗ.
Отправка почты и ошибок
Для отправки почты попробовал несколько десятков различных вариантов, но в результате остановился на следующем. Зарегистрировал почтовый ящик на yandex и далее взял данный пример отправки почты. Проверил его и внедрил в программу. В этом примере разбираются различные варианты, в том числе отправка с gmail и т.п. Возится с поднятием своего почтового сервера мне не хотелось и не было времени на это, но как потом оказалось тоже напрасно.
Отправка логов производил по планировщику, при наличии связи, каждые 6 часов: в 00 часов, 06 утра, 12 дня и 18 вечера. Отправлял следующим образом.
from send_email import *
...
message_log = "Логи тестирования платы №1"
EmailForSend = ["dlinyj@trololo.ru", "pupkin@trololo.ru"]
files = ["/home/khadas/modems_speedtest/csv"]
...
def sendLogs():
global EmailForSend
curdata = datetime.now().strftime('%d.%m.%Y')
сurtime = datetime.now().strftime('%H:%M:%S')
try:
for addr_to in EmailForSend:
send_email(addr_to, message_log, "Логи за " + curdata + " " + сurtime, files)
except:
print("Network problem for send mail")
return False
return True
Ошибки тоже изначально отправлялись. Для начала они накапливались в списке, и потом отправлял также с помощью планировщика, при наличии связи. Однако потом возникли проблемы с тем, что yandex имеет ограничение на количество отправляемых сообщений в сутки (это боль, печаль и унижение). Поскольку ошибок даже в минуту могло быть огромное количество, соответственно от отправки ошибок по почте пришлось отказаться. Так что имейте в виду, при автоматической отправки через сервисы яндекса о такой проблеме.
Сервер обратной связи
Для того, чтобы иметь доступ к удаленной железке и иметь возможность ее донастроить и переконфигурировать мне понадобился внешний сервер. Вообще, справедливости говоря, правильно было бы все данные отправлять на сервер и в веб-интерфейсе строить все красивые графики. Но не все сразу.
В качестве VPS я выбрал ruvds.com. Можно было бы взять самый простой сервер. И в целом для моих бы целей этого хватило бы за глаза. Но поскольку платил за сервер не из своего кармана, решил взять с небольшим запасом, чтобы хватило, если будем разворачивать web-интерфейс, свой SMTP-сервер, vpn и т.д. Плюс иметь возможность настроить Telegram-бота и не иметь проблем с его блокировками. Поэтому выбрал Amsterdam и следующие параметры.
В качестве способа связи с железкой vim2 выбрал обратное ssh соединение и как показала практика — не самое лучшее. При разрыве соединения, сервер удерживает порт и по нему невозможно подключиться некоторое время. Поэтому, все же лучше использовать другие способы связи, например vpn. В будущем хотел перейти на vpn, но не успел.
Не буду вдаваться в подробности настройки файрвола, ограничения прав, отключения ssh соединения root и прочие прописные истины настройки VPS. Хочется верить, что вы и так все знаете. Для удаленного соединения, создаю нового пользователя на сервере.
adduser vimssh
На нашей железке генерирую ключи ssh соединения.
ssh-keygen
И копирую их на наш сервер.
ssh-copy-id vimssh@host.com
На нашей железке создаю автоматическое подключение обратного ssh при каждой загрузке.
[Unit]
Description=Auto Reverse SSH
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=/usr/bin/ssh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -CD 8080 -R 8083:localhost:22 vimssh@host.com
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
Обратите внимание на порт 8083: он и определяет по какому порту у меня будет осуществляется подключение через обратный ssh. Добавляем в автозагрузку и стартуем.
sudo systemctl enable autossh.service
sudo systemctl start autossh.service
Можно даже посмотреть статус:
sudo systemctl status autossh.service
Теперь, на нашем VPS-сервере, если выполнить:
ssh -p 8083 khadas@localhost
То я попадаю на мою тестовую железку. И с железки могу так же отправлять логи и любые данные по ssh на мой сервер, что весьма удобно.
Собираем все воедино
Включение, приступаем к разработке и отладке
Фух, ну вроде все, описал все узлы. Теперь пришло время собрать все это в единую кучу. Код можно посмотреть вот тут.
Важный момент с кодом: Данный проект вот так вот «влоб» может не запуститься, так как затачивался на определенную задачу, определенной архитектуры. Хоть я и даю исходники, но все же самое ценное разберу вот тут, прямо в тексте, иначе совершенно непонятно.
В начале у меня идет инициализация gps, gpio и запуск отдельного потока планировщика.
#запуск потока планировщика
pShedulerThread = threading.Thread(target=ShedulerThread, args=(1,))
pShedulerThread.start()
Планировщик достаточно прост: он смотрит не пришло ли время отправки сообщений и какой сейчас стоит статус ошибок. Если есть флаг ошибки, то мигаем светодиодом.
#sheduler
def ShedulerThread(name):
global ready_to_send
while True:
d = datetime.today()
time_x = d.strftime('%H:%M')
if time_x in time_send_csv:
ready_to_send = True
if error_status:
error_blink()
else:
good_blink()
time.sleep(1)
Самый сложный момент в данном проекте — это сохранять обратное ssh-соединение при каждом тесте. В каждом тесте идет заново настройка шлюза по умолчанию и dns-сервера. Поскольку все равно никто не читает, то знайте, что поезд не катается по деревянным рельсам. Кто найдет пасхалку, тому конфетка.
Для этого я создаю отдельную таблица маршрутизации --set-mark 0x2 и правило для перенаправления трафика.
def InitRouteForSSH():
cmd_run("sudo iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 22 -j MARK --set-mark 0x2")
cmd_run("sudo ip rule add fwmark 0x2/0x2 lookup 102")
Подробнее о том, как это работает можно прочитать в этой статье.
После чего перехожу в бесконечный цикл, где каждый раз получаем список подключенных модемов (чтобы узнать, вдруг конфигурация сети изменилась).
network_list = getNetworklist()
Получение списка сетевых интерфейсов достаточно простое.
def getNetworklist():
full_networklist = os.listdir('/sys/class/net/')
network_list = [x for x in full_networklist if "eth" in x and x != "eth0"]
return network_list
После получения списка, задаю IP-адреса всем интерфейсам, как я приводил на картинке в главе про модем.
SetIpAllNetwork(network_list)
def SetIpAllNetwork(network_list):
for iface in network_list:
lastip = "%d" % (3 + network_list.index(iface))
cmd_run ("sudo ifconfig " + iface + " 192.168.8." + lastip +" up")
Далее просто в цикле иду по каждому интерфейсу. И конфигурирую каждый интерфейс.
for iface in network_list:
ConfigNetwork(iface)
def ConfigNetwork(iface):
#сбрасываем все настройки
cmd_run("sudo ip route flush all")
#Назначаем шлюз по умолчанию
cmd_run("sudo route add default gw 192.168.8.1 " + iface)
#задаем dns-сервер (это нужно для работы speedtest)
cmd_run ("sudo bash -c 'echo nameserver 8.8.8.8 > /etc/resolv.conf'")
Проверяю интерфейс на работоспособность, если сети нет, то формирую ошибки. Если сеть есть, то время действовать!
Здесь я настраиваю ssh маршрутизацию на данный интерфейс (если не было сделано), отправляю ошибки на сервер, если время пришло, отправляю логи и в конце концов провожу speedtest и сохраняем логи в csv-файл.
if not NetworkAvalible():
....
#Здесь мы формируем ошибки
....
else: #Есть сеть, ура, работаем!
#Если у нас проблемный интерфейс, на котором ssh, то меняем его
if (sshint == lastbanint or sshint =="free"):
print("********** Setup SSH ********************")
if sshint !="free":
сmd_run("sudo ip route del default via 192.168.8.1 dev " + sshint +" table 102")
SetupReverseSSH(iface)
sshint = iface
#раз сетка работает, то давай срочно все отправим!!!
if ready_to_send:
print ("**** Ready to send!!!")
if sendLogs():
ready_to_send = False
if error_status:
SendErrors()
if (error_status):
SendErrors()
#и далее тестируем скорость и сохраняем логи.
Разве что стоит сказать о функции настройки обратного ssh.
def SetupReverseSSH(iface):
cmd_run("sudo systemctl stop autossh.service")
cmd_run("sudo ip route add default via 192.168.8.1 dev " + iface +" table 102")
cmd_run("sudo systemctl start autossh.service")
Ну и конечно же, необходимо всю эту красоту добавить в автозагрузку. Для этого создаю файл:
sudo vim /etc/systemd/system/modems_speedtest.service
И записываю в него:
[Unit]
Description=Modem Speed Test
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=/usr/bin/python3.6 /home/khadas/modems_speedtest/networks.py
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
Включаю автозагрузку и стартую!
sudo systemctl enable modems_speedtest.service
sudo systemctl start modems_speedtest.service
Теперь я могу смотреть логи того, что происходит с помощью команды:
journalctl -u modems_speedtest.service --no-pager -f
Результаты
Ну теперь самое главное, что же получилось в результате? Приведу несколько графиков, которые мне удалось заснять в процессе разработки и отладки. Графики строились с помощью gnuplot следующим скриптом.
#! /usr/bin/gnuplot -persist
set terminal postscript eps enhanced color solid
set output "Rostelecom.ps"
#set terminal png size 1024, 768
#set output "Rostelecom.png"
set datafile separator ';'
set grid xtics ytics
set xdata time
set ylabel "Speed Mb/s"
set xlabel 'Time'
set timefmt '%d.%m.%Y;%H:%M:%S'
set title "Rostelecom Speed"
plot "Rostelecom.csv" using 3:6 with lines title "Download", '' using 3:7 with lines title "Upload"
set title "Rostelecom 2 Ping"
set ylabel "Ping ms"
plot "Rostelecom.csv" using 3:8 with lines title "Ping"
Первый опыт был оператора Tele2, который я проводил в течении нескольких дней.
Здесь я использовал динамический измеряющий сервер. Замеры скорости работают, но очень сильно плавают, однако все же видна некоторая средняя величина, и ее можно получить, произведя фильтрацию данных, например, скользящим средним.
Позднее я построил еще ряд графиков, для других операторов связи. Сервер тестирования в этом случае уже был один, и результаты тоже очень интересные.
Как видно тема очень обширная для исследований и обработки этих данных, и явно не тянет на пару недель работы. Но…
Итог работы
Работа была резко завершена по независящим от меня обстоятельствам. Одной из слабых сторон данного проекта, на мой субъективный взгляд, был модем, который не очень хотел работать одновременно с другими модемами, и при каждой загрузке выделывал такие фортеля. Для данных целей существует громадное количество других моделей модемов, обычно они уже имеют формат Mini PCI-e и ставятся внутрь устройства и их сильно проще конфигурировать. Но это уже совсем другая история. Проект был интересный и был очень рад, что удалось в нем поучаствовать.