Mikrotik, Telegram и не только…
Здравствуйте, друзья!
Сегодня я хочу рассказать вам, как открыл для себя новый язык программирования, среду исполнения, а ещё готовый фронт-энд. И всё это без кучи фреймворков и тысяч библиотек, чистое, непаханое поле…
Однако, давайте по порядку.
Список языков программирования обширен и уже устоялся, по ним много информации и всевозможных курсов. Среды разработки выросли и обзавелись множеством полезных функций, это радует.
Но мне всегда хочется чего-то нового. Долго искать не пришлось, всё уже давно было под рукой. Это Микротик. Вместе с его RouterOS. Как оказалось тут есть практически всё, что нужно и для разработки, и для исполнения кода.
А фронтом стал Телеграм с его bot API. Тут можно создать приятный интерфейс и сделать взаимодействие с ботом, очень похожим на работу с обычной программой. Телеграм мультиплатформенный и с отличным бэк-эндом. Ему бы побольше элементов управления в API и цены б ему не было. Хотя, я уверен он будет развиваться.
Начать стоит с Микротика. Точнее, давайте рассмотрим его скриптовый язык. Он совсем не сложный и на первый взгляд не годится для серьёзной разработки. Но если копнуть глубже, то он раскрывается, хоть и содержит ряд ограничений.
Из самых досадных то, что из числовых имеем только целочисленный тип данных num. Пять разделим на два, получим два. В остальном вполне можно есть.
Я писал на Си, на Ява и там много различных структур данных: деревья, списки, мапы. Тут в явном виде ничего этого нет. Из структур есть только массив, правда реализована поддержка многомерных ассоциативных массивов, что позволяет создавать из него нужные структуры.
Рассмотрим наглядно. Обычный одномерный массив — это индексированный список. Элементы можно вставлять в любое место списка.
Одномерный ассоциативный, где элемент key=value, это тоже список, но сортированный по key.
Двумерный — это HashMap. Он же, но ассоциативный — сортированный map.
Из многомерного можно сделать сортированное дерево. Не сортированное тоже можно.
В одном массиве отлично живут и индексированные и именованные элементы. Первые доступны по индексу, вторые по ключу. Так каждый массив может содержать служебную информацию о себе. Например в индексированных элементах хранить имя и размер.
А массив в памяти, т.е. развернутый в глобальную переменную, это доступная для записи и чтения, оперативная память, содержимое которой можно сохранять на диск, если надо. Причем на глобальные переменные ограничений по размеру нет, в отличие от локальных. Ограничивается только оперативной памятью.
Элементом массива может быть даже код. И его можно выполнить, передав туда параметры, не разворачивая в скрипт или функцию, ещё и в отдельном потоке.
Кому нужны пруфы, прошу под кат.
В этом коде объявляются две глобальные переменные и инициализируется массив, элементом которого является код.
:global queryID []
:global answerText []
:global answer ({
"warning"=":global queryID; :global answerText; :log warning \"fQueryID=\$queryID fAnswerText=\$answerText\"; :delay 3; :log warning \"after delay\"";
})
А тут мы инициализируем переменные, получаем код из массива и запускаем его на выполнение.
:global queryID 345554
:global answerText "Hello"
:global answer
:local exeText ($answer->"warning")
[[:parse $exeText]]
:log info "$exeText"
Чтобы передать параметры, нужно чтобы переменные были глобальными.
В данном случае код запускается командой: parse. Это значит, что сначала выполнится код из массива, а затем то, что идет после него (: log info »$exeText»)
Команда : execute работает по-другому. Она запускает выполнение кода в отдельном потоке.
Да, многопоточность тоже доступна. Зависит от того, как вызывать код. Есть несколько способов. Из скрипта можем вызвать функцию, развернутую в глобальной переменной, выполнить код из : parse, или вызвать другой скрипт. В этом случае код, который идет ниже места вызова, будет ждать пока отработает его товарищ и только потом продолжит выполняться, если первый чего-нибудь не выкинет.
Всё меняется, если вызывать код командой : execute. Товарищ в этом случае работает сам по себе и ждать его не надо, правда значение не вернет тут же. Всё что ниже такого вызова продолжит выполняться, а результат работы, вызванный выше код, может сохранить в массив или файл.
Под катом подробнее.
Тот же пример, что выше под катом, только с использованием : execute
:global queryID []
:global answerText []
:global answer ({
"warning"=":log warning \"fQueryID=\$queryID fAnswerText=\$answerText\"; :delay 3; :log warning \"after delay\"";
})
:global queryID 345554
:global answerText "Hello"
:global answer
:local exeText ($answer->"warning")
:execute script=$exeText file=123.txt
:log info "$exeText"
Теперь код из массива выполнится в отдельном потоке, всё, что после вызова, продолжит выполнение.
Обратите внимание на отсутствие объявления глобальных переменных в коде из массива. В случае с : execute объявлять их там не надо.
Код можно запускать как службу. К примеру, в цикле мониторить появление элементов в глобальном массиве и выполнять действия в зависимости от этого. Или проверять наличие сообщений на сервере Телеграм.
Благодаря стараниям уважаемого автора Хабра, Александра @Chupakabra303, Микротик теперь понимает JSON. Автор создал набор библиотек, который парсит JSON объекты в многомерный ассоциативный массив и с ним уже можно работать всеми доступными методами.
Ещё функции… Развернутые в глобальном окружении, они постоянно доступны в памяти. В любой момент можем вызвать их из кода или консоли, передав туда параметры, как именованные, так и по индексу. Об этом подробнее можно почитать у местных уважаемых авторов, здесь и здесь.
Есть проверка синтаксиса. Можно вывести в консоль содержимое скрипта командой [/system script print from=script] и в месте, где есть ошибка шрифт станет монохромным. Если ошибок нет, то будет разноцветным, как новогодняя ёлка.
Отладка реализуется выводом в лог или консоль значения любой переменной. Что ещё нужно?
Ах, да. Нужна нормальная среда разработки. Таких много. Для себя выбрал редактор Atom от команды GitHub. У него есть много плагинов, в том числе RouterOS plugin. Плюс нативная поддержка контроля версий Git. Работать вполне комфортно.
Всё это в совокупности позволяет создать на базе RouterOS полноценный бэк-энд для мобильных приложений. Для Телеграм вообще подходит идеально.
Многие правильно подметят, а как же базы данных? Где данные хранить, запросы куда отправлять?
Это тоже решаемо. Есть СУБД как SQL, так и noSQL, которые поддерживают HTTP (S) API и JSON. Для управления временными рядами есть InfluxData, которая из коробки работает с https и JSON. Она подходит для мониторинга параметров самого устройства, а также для сбора всевозможных метрик с фронта. Дружит с Grafana, есть дашборды и всё красиво.
А можно использовать массивы. И даже создать СУБД на их основе, с запросами и прочим. Когда будет время займусь этим плотнее.
И все это не только для управления параметрами маршрутизатора. Ничего не мешает обрабатывать данные других сервисов с JSON сериализацией и возвращать результат. Если развернуть RouterOS в виртуальной среде, то можно оперировать ресурсами. Захостить её в облаке не проблема.
Пока это только теория. А как на практике? Покажу на примере Телеграм бота, который разработан с использованием почти всех, описанных выше, возможностей. Здесь кода не будет. Скорее демонстрация работы связки Микротик плюс Телеграм. Архитектура приложения, интерфейс, bot API и немного о безопасности.
Так выглядит главное окно бота.
Начнем с архитектуры. Мне понравилась статья одного из авторов Хабра «Создание архитектуры программы или как проектировать табуретку».
Приведу небольшую выдержку из нее.
Начнем с архитектуры. Мне понравилась статья одного из авторов Хабра «Создание архитектуры программы или как проектировать табуретку».
Приведу небольшую выдержку из нее.
Не смотря на разнообразие критериев, все же главной при разработке больших систем считается задача снижения сложности. А для снижения сложности ничего, кроме деления на части, пока не придумано. Иногда это называют принципом «разделяй и властвуй» (divide et impera), но по сути речь идет об иерархической декомпозиции. Сложная система должна строится из небольшого количества более простых подсистем, каждая из которых, в свою очередь, строится из частей меньшего размера, и т.д., до тех пор, пока самые небольшие части не будут достаточно просты для непосредственного понимания и создания.
С этим трудно не согласиться. Монолитный код замучаешься читать, об отладке и поддержке вообще молчу. Поэтому — модульность. В RouterOS реализуется с помощью функций. В боте их условно можно разделить на несколько типов.
Библиотечные. Часто используются другими модулями и реализуют работу с API Телеграм например. Отвечают за отправку сообщений или построение кнопок и клавиатур.
Функции создания пользовательских интерфейсов. Они «рисуют окна» исходя из назначения и переданных параметров.
Функции обработчики. Эти содержат логику и обрабатывают команды, полученные от клиента. Для каждого модуля свой обработчик.
И функции диспетчеры, которые определяют какому модулю предназначена команда и передают ему управление. Таких в программе три. Первый обрабатывает Callbackи — это нажатие на кнопку, второй текстовые команды и третий команды модуля Терминала.
Код, который отслеживает наличие сообщений, работает как служба. Никаких шедулеров, он постоянно в работе. В бесконечном цикле мониторит сервер Телеграм, получает сообщения и, в зависимости от типа, передаёт их функции диспетчеру, предварительно проверив права пользователя на работу с системой в целом.
Процессор такой подход совсем не нагружает, зато повышает скорость обработки сообщений.
Конечно все паттерны тут не реализуешь, но…
В этом случае программа из «спагетти-кода» превращается в конструктор, состоящий из набора модулей/подпрограмм, взаимодействующих друг с другом по хорошо определенным и простым правилам, что собственно и позволяет контролировать ее сложность, а также дает возможность получить все те преимущества, которые обычно соотносятся с понятием хорошая архитектура:
Масштабируемость (Scalability) — возможность расширять систему и увеличивать ее производительность, за счет добавления новых модулей.
Ремонтопригодность (Maintainability) — изменение одного модуля не требует изменения других модулей
Заменимость модулей (Swappability) — модуль легко заменить на другой
Возможность тестирования (Unit Testing) — модуль можно отсоединить от всех остальных и протестировать / починить
Переиспользование (Reusability) — модуль может быть переиспользован в других программах и другом окружении
Сопровождаемость (Maintenance) разбитую на модули программу легче понимать и сопровождать
Теперь про интерфейс. Я видел много Телеграм-ботов и большинство из них не заботятся о чистоте пользовательского пространства. Каждое новое действие там — это новое сообщение, что для работы совсем не удобно. Такое ощущение, что их разработчики просто ленятся сделать красиво. А ведь в Телеграм есть функции для редактирования messageй. Можно отдельно редактировать и сам текст и клавиатуру под ним. Конечно не все этим грешат но, повторюсь, большинство не заморачивается.
Чтобы не дублировать код в модулях, были написаны библиотечные функции для работы с bot API. Их я выложил на русскоязычном форуме Микротик, кому надо можете пользоваться. Там есть всё, чтобы создать и отправить сообщение с текстом, фото, клавиатурой и т.д. Для редактирования и удаления тоже есть.
Настройки самого бота хранятся в массиве и считываются оттуда по необходимости.
Безопасность обеспечивается управлением пользовательскими разрешениями. Они тоже хранятся в массиве. Проверяются в функции обработчике перед выполнением команды.
Базовая версия бота опубликована на моем Телеграм канале. Написана так, что позволяет использовать его, как основу для разработки и подключения своих модулей. Используя эти наработки, любой из вас может самостоятельно дописывать нужный функционал.
Резюмируя можно сказать, что RouterOS, на мой взгляд, является неплохой средой разработки и исполнения. Конечно со своими тараканами, но их не так много. Я уверен, что рано или поздно её «распробуют» и другие разработчики приложений.
Как оказалось Микротик подходит для разработки Телеграм ботов, вообще не связанных с управлением самим устройством. Чтобы подтвердить этот тезис, я начал работу над новым проектом. Это будет сервис, связанный с геопозиционированием. Тут будет задействован и inline режим. Сейчас о подробностях говорить рано, но прототип уже есть, он работает и пока непреодолимых препятствий я не вижу.
Конечно много чего ещё хочется написать, но тогда статья слишком распухнет. И так занял много вашего времени.
Спасибо, что дочитали до конца. Сильно не пинайте, публикую в первый раз. Пожелания и предложения приветствуются.