MikroVoice — музыкально-звуковой Микротик: мой опыт озвучивания сетевых событий на роутерах

MikroVoice — музыкально-звуковой Микротик: мой опыт озвучивания сетевых событий на роутерах

1. Введение и о том как всё начиналось (рождение идеи)

Основная задача роутера – маршрутизация сетевого трафика. Однако как мы знаем, современный роутер, кроме этого выполняет массу других «около задач», одно перечисление которых может занять эту страницу. Рост производительности и увеличение объемов памяти с одной стороны и приближение сетевых технологий к пользователю, в том числе «одомашнивание» роутеров, как отдельных устройств, с другой, нагрузило их дополнительными, и даже напрямую не связанными с основной задачей, функциями. Спорам о целесообразности этого нет конца – IT-профессионалы твердят, что роутер должен быть только роутером и больше ничем. Желания пользователей и конкуренция на рынке производителей диктуют свои правила. Разумеется, как и во всех сферах, существует «разделение» этих устройств на профессиональные, «полупрофессиональные» и так называемые «SOHO», а крупные производители с целью не только не потерять, но и наращивать рынок, как правило, поддерживают производство всех указанных типов этих девайсов.

Чтобы в меня сразу не кидали грязные тряпки, в том числе за длинную преамбулу, скажу, что я не специалист в области IT, занимаюсь только роутерами Микротик и только лишь как «продвинутый» пользователь в рамках своего хобби. Больше всего мне нравится писать скрипты для решения различных задач и расширения возможностей этих роутеров (с некоторыми моими, в том числе нетривиальными, работами можно познакомиться здесь).
Проводя много времени за «скриптописанием», меня часто не покидала мысль универсально «озвучить» мои роутеры, которых за 10 лет увлечения накопился приличный «зоопарк». Большинство моделей Микротик (но далеко не все) имеют, конечно, встроенный бипер, доступный программно и способный воспроизводить простые звуки и монофонические мелодии за счет управления тональностью и длительностью воспроизведения нот из скриптов. Вполне достаточно для внешней сигнализации об основных событиях, происходящих на роутере, скажете Вы, и будете совершенно правы. Но, даже имея достаточный «запас» готовых или заново сочинённых (для этого имеются некоторые инструменты и сторонний софт) скриптов-мелодий в репозитории роутера озвучить таким образом много событий вряд ли получится. Человеку просто невозможно помнить какое событие какой определенной мелодии ты сам назначил – мозг перестает дифференцировать события по мелодиям, и «озвучка» теряет всякий смысл.
Нет, только не подумайте, что я хотел сделать из роутера звуковой медиаплеер – я хотел всего лишь озвучить события, происходящие именно на роутере, придав им не столько звуковое, сколько, в большей степени, голосовое сопровождение, смысл которого был бы явно понятен человеку (пользователю или админу).
Голосовой роутер – а почему бы нет? Да, профи админы работают сейчас давно уже преимущественно удалённо, обслуживая сотни роутеров и сетей. Но если главный роутер, обеспечивающий доступ ко всему этому, даже удалённому оборудованию, стоит «под боком», а твои глаза постоянно перегружены и безмерно устают каждый день от нескольких дисплеев и информирующих чатботов Телеграмм, почему бы их немного не разгрузить, переведя часть получаемой информации или дублируя её со зрения на наш слух? Да и дома можно использовать (без перегибов, конечно), а если надоест, то голосовое оповещение всегда можно отключить «в один клик».

2. Mодуль serial MP3/WAV Player Catalex YX5300/YX6300

За обдумыванием и поиском решения вышеизложенной идеи прошло не мало времени. Мне пришлось пересмотреть целую кучу разного оборудования, имеющегося на современном рынке подобных устройств. Главными критериями отбора были: простота «конструкции», дешевизна, компактность, серийная воспроизводимость, простота управления, ну и, конечно, самое важное – это возможность управления воспроизведением звука непосредственно с роутеров Микротик. В основном, я рассматривал различные одноплатные модули MP3/WAV проигрывателей и быстро понял, что наиболее всего мне подойдут устройства, так или иначе созданные для Arduino. В конце концов мой выбор пал на маленький и изящный модуль MP3/WAV Player YX5300, созданный первоначально под брендом Catalex в 2014 г. и позднее быстро многократно клонированный нашими друзьями из Китая.
Serial MP3/WAV Player Catalex YX5300/YX6300 (Рис. 1) представляет собой маленькую тёмно-синюю плату размерами всего 43 Х 25 Х 13 мм с интегрированным «звуковым» чипом, его «обвязкой», готовым разъемом line out, слотом для TF-карты (поддерживаются карты объемом до 32 ГБ) и UART TTL пинами с обычными пин-контактами RX, TX, GND и VCC (+3,3… +5В).

Рис. 1 Модуль Serial MP3/WAV Player Catalex YX5300/YX6300

Модуль поддерживает простую систему команд, управляющих воспроизведением звуковых файлов, заранее в особой структуре каталогов и имён записанных на TF-карту, представленных определённой последовательностью байт, получаемых модулем от управляющего устройства через порт UART. Catalex YX5300 широко используется в различных Arduino-проектах и бытовой звуковой электронике. Подробно с описанием Serial MP3/WAV Player Catalex YX5300/YX6300, его системой команд можно ознакомиться по «фирменному» руководству здесь.

Сначала я «опробовал» работу модуля, соединив его с ПК через обычный USB-serial переходник, подавая ему команды из терминальной программы и остался доволен своими экспериментами. Это сейчас я могу так легко написать об этом. На самом деле в начале пути мне ужасно не повезло, так как волею судьбы первый модуль MP3/WAV Player Catalex YX5300/YX6300, который я заказал на AliExpress, пришёл бракованным (и это был единственный бракованный модуль, который мне попался, так как в последующем, сколько и у каких только разных поставщиков я не заказывал – брака не встретил ни одного раза). На том самом первом модуле видимо где-то был плохо пропаян главный чип на платке, но при механическом нажатии на него модуль начинал работать! Представьте сколько времени я потратил на то, чтобы понять почему мой первый Catalex «не хочет» воспроизводить музыку ?! Если бы не моё упорство и не чистая случайность, что мне пришла мысль в голову просто механически подавить на плату рукой, я мог бы отступить и бросить эту свою затею на всегда! Но давний опыт программиста и человека, возившегося в разные времена со многими железками, всё время подсказывал мне, что всё должно работать и «оно» в итоге заработало!
Пришлось вновь заказать модули в Китае и на этот раз я взял несколько различных версий прошивки и с различными маркировками чипов у разных продавцов на AliExpress. Надо сказать, что клонов этого модуля существует несколько, платы различаются маркировками чипов, «обвязкой» и разводкой дорожек, но при этом все они работают по сути одинаково. Цена модуля составляет в сущности копейки – от 100 до 200 рублей у разных китайских поставщиков. В России же он не особенно популярен у наших продавцов Ардуино, у которых он стоит конечно в два дороже (а собственно что мы хотели от русских продаванов) и продаётся буквально в единичных Интернет-магазинах. Существует аналог Catalex YX5300/6300 – модуль под брендом OpenSmart в 2016 г., выполненной на «красной» платке с аналогичной, но своей системой команд. Предупрежу сразу энтузиастов – этот модуль с Микротик работать не будет в принципе (почему, объяснять здесь не стану, пусть останется некоторая интрига).

3. Serial MP3/WAV Player и Микротик

Оставалось реализовать связь модуля Catalex с роутером Микротик. Аппаратно всё было просто – соединяем плату MP3 плейера с USB-TTL переходником и получаем готовое устройство, которым можно управлять через USB-порт! Вспоминаем, что у большинства routerboard Микротик искомый порт есть. Пробуем подключить в него нашу сборку и… видим, что Роутер ОС прекрасно определяет порт и на нём определяет USB-TTL –переходник (опробованы переходники на всех известных чипах, все они прекрасно определяются ROS и работают в системе)! Питание модуль Catalex YX5300 будет получать от того же USB-порта роутера Микротика через наш USB-Serial переходник и никакое дополнительное питание нам не требуется. Остаётся программно настроить USB-порт в Миротик Роутер ОС, задав параметры порта, подходящие для «обмена данными» с Catalex YX5300 (они очень простые: скорость обмена 9600; данные – 8 бит; контроль чётности – без контроля, стоповые биты 1, Рис. 2).

Рис. 2 Настройка параметров порта в Микротик Роутер ОС для Catalex YX5300.

А как же мы будем передавать данные MP3/WAV плейеру через USB-порт, ведь встроенных инструментов для этого у Микротика нет…? Нет, да не совсем «нет». Известно, что через USB-порт данные передавать можно, например, это широко используется в интерфейсах модемов, подключенных к роутеру Микротик через USB-порт. Делается это через стандартный ppp-out интерфейс, предоставяемый Роутер ОС. Да, Catalex YX5300 не поддерживает AT-команды и с ним не будет работать инструмент /ppp client at-chat Микротик Роутер ОС, но можно передать данные через параметр modem-init самого интерфейса ppp-out, включая и быстро отклячая его (пример ниже:)

:local SMPport usb3
:local commandCatalex «F\FF
:local SMPport usb3
:local commandCatalex «\7F\FF\06\03\00\00\01\EF»
/interface ppp-client add name=«Сatalex» dial-on-demand=no port=$SMPport modem-init=$commandCatalex null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove name=«Сatalex»
:local SMPport usb3
:local commandCatalex «\7F\FF\06\03\00\00\01\EF»
/interface ppp-client add name=«Сatalex» dial-on-demand=no port=$SMPport modem-init=$commandCatalex null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove name=«Сatalex»
\EF» /interface ppp-client add name=«Сatalex» dial-on-demand=no port=$SMPport modem-init=$commandCatalex null-modem=yes disabled=no :delay 1s /interface ppp-client remove name=«Сatalex»

Не буду описывать здесь программные тонкости и некоторые хитрости, которые мне пришлось преодолеть, для реализации готового скрипта, способного управлять Catalex YX5300, так как это выходит за рамки данной статьи, да и является определённым «ноу хау» автора. Код основной функции $fSMP приведён ниже в разделе 4 данной статьи. Кому интересно изучить все скрипты созданной мной библиотеки, может посмотреть их готовый код в топике MikroVoice русскоязычного форума Микротик и моем разделе ресурса GitHub (код будет размещён там в ближайшее время).

Отмечу однако, что такой подход имеет один существенный недостаток: мы можем передавать команды звуковому модулю и он будет их все выполнять, управляя воспроизведением звуковых треков с установленной TF-карты, но мы не сможем получить ответ от модуля по той простой причине, что ppp-out интерфейс Микротик не поддерживает такой возможности, а Сatalex YX5300 в свою очередь не поддерживает AT-команды. Но даже в отсутствии какой-либо «обратной» связи со звуковым модулем мне удалось реализовать задуманное – Микротик может надежно управлять работой всех команд этого звукового модуля!
Когда основные программные эксперименты с модулем были закончены, осталась важная задача – итоговая реализация для пользователя. Для удобства работы мной была написана небольшая библиотека функций на скриптовом языке Микротик Роутер ОС по управлению модулем Сatalex YX5300.
Также была создана библиотека текстов будующих звуковых джинглов «оповещения» на руском языке – изначально около 100 (а в последствии до 250-ти) коротких текстовых сообщений, которые будет «произносить» звуковой модуль для озвучивания основных «сетевых» событий на роутере. На Рис. 3 — вырезка из списка этих текстов, полный список базовых озвученных текстов на русском, английском и китайском языках и их коротких названий для функции $fVoice размещены в папке Приложение к руководству MikroVoice.

Рис. 3 Тексты джинглов, русскоязычный вариант (вырезка) …

Далее, чтобы не записывать голосовые треки (джинглы) через микрофон собственным голосом и заранее избежать долгой рутинной работы по последующему софтовому устранению посторонних шумов, артефактов дыхания и прочего, по совету друга, я воспользовался нейросетевым синтезатором речи, который легко преобразует текстовую строку в звуковой джингл с выбранным тембром голоса, темпом речи и другими параметрами, делающими озвучиваемые тексты приятными на слух (https://apihost.ru/voice). Также вся данная работа была продублирована для английских аналогов тех же джинглов, а также китайского языка. Джинглы на китайском пришлось «изготавливать» в ручную – с помощью профессионального переводчика и озвучки на микрофон, так как перевод на китайский и озвучка китайских текстов для нейросетевых платформ пока сложное дело и чтобы избежать ошибок и смеха наших китайских друзей, был использован «человеческий» ресурс.
Аппаратная часть на монтажной плате из стеклотекстолита была «заключена» в подходящий по размерам и параметрам для удобного монтажа стильный, чёрный алюминиевый корпус, также найденный и заказанный на нашем любимом AliExpress.
В итоге проект получил название «MikroVoice», аппаратная часть в корпусе мной названа MikroJuxBox (Рис. 4), а софтверная скриптовая библиотека функций для Роутер ОС – MikroVoiceSys (поставляется в едином самораспаковываемом одноимённом файле MikroVoiceSys.rsc).

Рис. 4 MikroJuxBox – готовое устройство в алюминиевом корпусе для подключения к USB-порту Микротик (слева), в сравнении размеров в подключённом состоянии к USB-порту RBM 33G сверху на его корпусе (справа).

Скриптовая программная часть пакета содержит главную функцию $fSMP – непосредственного управления воспроизведением модуля Catalex YX5300 и нескольких функций «надстройки», важнейшей из которых является функция $fVoice, воспроизводящая джинглы озвучивания событий на роутере по имени или по номеру базы каталога на выбранном языке (русском, английском, китайском), что обеспечивается размещением джинглов событий в зеркальных папках каталога TF-карты. Подробное руководство программной частью системы MikroVoice представлено в руководстве здесь. Там же можно посмотреть подробную презентацию системы MikroVoice.

Рис. 5 «Фирменная» визитка аппаратно-программного комплекса MikroVoice на коробке с поставляемой системой.

4. Управление воспроизведением из Роутер ОС Микротик

Ниже привожу код функции $fSMP, осуществляющей передачу команд управления воспроизведением модулю serial MP3/WAV Player Catalex YX5300/6300 (под спойлером).

Функция $fSMP для serial MP3/WAV Player Catalex YX5300/6300

:global fSMP do={
  
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "E\FF"
:local YX5300cmdPostfix "\EF"

:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}

:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт

    :foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
    :foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
  
    :set NewPort $portUSB
               :if ([:len $NewPort]=0) do={:set NewPort $portSerial}

    :if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}


   :if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
   :if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
  
   :local gpio
   :do {:set gpio [/system routerboard settings get gpio]} on-error={}

  :local consoleFlagOff false
        if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
                :set consoleFlagOff true
                /system console set [/system console find port=$SMPport] disable=yes
        }

   :if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
      :put "";
      :put "ERROR: not set GPIO-pins to $SMPport port."
      :put "execute commands: /system routerboard settings set gpio=$SMPport"
      :put "                  /system reboot"
      :return "Error not set gpio serial. Function $0 d`not work"
   }

    do {
             /port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
          } on-error={:return "Error set port $SMPport. Function $0 d`not work"}


# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser

   :local fBy do={
         :set $1 [:tonum $1]
    :return [[:parse "(\"\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
  }

:local ArrayComSMP {
   "play"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
D" "pause"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
E" "stop"="" "next"="" "previous"="" "volumeUP"="" "volumeDW"="" "sleep"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
A" "wakeup"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
B" "reset"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
C" "shuffle"="" "playfile"="
:global fSMP do={
# ----------------------------------------
:local ModuleType "Catalex"
:local portTypeSerial "serial"
:local portTypeUSB "usb"
#-----------------------------------------
:global serialModuleType $ModuleType
:local portUSB;
:local portSerial
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
:local YX5300cmdPrefix "\7E\FF\06"
:local YX5300cmdPostfix "\11\11\11\EF"
:do {/interface ppp-client remove [/interface ppp-client find name=$PppclientName]} on-error={}
:global SMPport
:local NewPort
:local NowPort $SMPport; # сохранить текущий порт
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
:foreach portId in=[/port find name~$portTypeSerial used-by="" !inactive] do={:set portSerial ([/port get $portId]->"name")}
:set NewPort $portUSB
:if ([:len $NewPort]=0) do={:set NewPort $portSerial}
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for Catalex module, port inactive or busy. Please, check /port"}
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set SMPport $NewPort} else={:set SMPport $NowPort}
:if ([:len $SMPport]=0) do={:set $SMPport $NewPort}
:local gpio
:do {:set gpio [/system routerboard settings get gpio]} on-error={}
:local consoleFlagOff false
if ([:len [/system console find port=$SMPport and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$SMPport] disable=yes
}
:if (($SMPport~$portTypeSerial) and ([:len $gpio]=0)) do={
:put "";
:put "ERROR: not set GPIO-pins to $SMPport port."
:put "execute commands: /system routerboard settings set gpio=$SMPport"
:put "                  /system reboot"
:return "Error not set gpio serial. Function $0 d`not work"
}
do {
/port set [/port find name=$SMPport] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $SMPport. Function $0 d`not work"}
# function $fByteToEscapeChar
# Chupakabra`s JSON parser for Mikrotik
# https://github.com/Winand/mikrotik-json-parser
:local fBy do={
:set $1 [:tonum $1]
:return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]]
}
:local ArrayComSMP {
"play"="\0D"
"pause"="\0E"
"stop"="\16"
"next"="\01"
"previous"="\02"
"volumeUP"="\04"
"volumeDW"="\05"
"sleep"="\0A"
"wakeup"="\0B"
"reset"="\0C"
"shuffle"="\18"
"playfile"="\0F\11"
"volume"="\06\11"
"playcycle"="\08\11"
"playfolder"="\17\11"
"TF"="09\11"}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command"}
:if ($1="list") do={
:local ArrayComSMPlist
:log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->"
:foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)}
:log info ""; :put "";
:return $ArrayComSMPlist}
:if ($1="mp3type") do={:return $ModuleType}
:local cmd ($ArrayComSMP->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command Serial MP3 Player: $1 $2 $3"
:log warning "Execute command Serial MP3 Player: $1 $2 $3"
:if ($1="TF") do={:set $2 02}
:local a1 $2; :local b1 $3
:if ([:len $3]=0) do={:set b1 $2; :set a1 "\11"}
:if ($a1!="\11") do={:set a1 [$fBy $a1]}
:set b1 [$fBy $b1]
:if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")}
:foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={
:foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i}
}
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no
:delay 1s
/interface ppp-client remove [/interface ppp-client find name=$PppclientName]
:if ($consoleFlagOff) do={
:do {/system console set [/system console find port=$SMPport] disable=no} on-error={}
}
:return OK
}
F" "volume"="" "playcycle"="" "playfolder"="" "TF"="09"} #---------------------------------------------------------------------------- # main function`s code # --------------------------------------------------------------------------- :if ([:len $1]=0) do={:return "Error: no set name command"} :if ($1="list") do={ :local ArrayComSMPlist :log info ""; :put ""; :log warning "<---- List commands Serial MP3/Wav Catalex YX5300/6300 Player: ---->" :foreach k,v in=$ArrayComSMP do={:log info $k; :put $k; :set ArrayComSMPlist ($ArrayComSMPlist, $k)} :log info ""; :put ""; :return $ArrayComSMPlist} :if ($1="mp3type") do={:return $ModuleType} :local cmd ($ArrayComSMP->$1) :if ([:len $cmd]=0) do={:return "Error: bad command"} :put "Execute command Serial MP3 Player: $1 $2 $3" :log warning "Execute command Serial MP3 Player: $1 $2 $3" :if ($1="TF") do={:set $2 02} :local a1 $2; :local b1 $3 :if ([:len $3]=0) do={:set b1 $2; :set a1 ""} :if ($a1!="") do={:set a1 [$fBy $a1]} :set b1 [$fBy $b1] :if ([:len $cmd]>1) do={:set YX5300cmdPostfix ("$a1"."$b1"."\EF")} :foreach portId in=[/port find name=$SMPport and used-by="PPP <$PppclientName>"] do={ :foreach i in=[/interface ppp-client find name=$PppclientName] do={/interface ppp-client remove $i} } /interface ppp-client add name=$PppclientName dial-on-demand=no port=$SMPport modem-init=("$YX5300cmdPrefix"."$cmd"."$YX5300cmdPostfix") null-modem=yes disabled=no :delay 1s /interface ppp-client remove [/interface ppp-client find name=$PppclientName] :if ($consoleFlagOff) do={ :do {/system console set [/system console find port=$SMPport] disable=no} on-error={} } :return OK }

Истинные любители скриптов Микротик, надеюсь, оценят некоторые отдельные изящные приемы автора, использованные при создании данной функции.
В комментариях к коду описан алгоритм определения рабочего порта, к которому подключен MP3 плейер, а также выбора команды, формирования командной байтовой строки с параметрами и передачи её модулю для исполнения через ppp-out-интерфейс, формируемый и удаляемый после каждого вызова функции. Функция $fSMP поддерживает работу как с MikroJuxBox, подключенном к USB-порту, так и с модулем serial MP3/WAV Player Catalex YX5300/6300, подключенному напрямую к serial1 порту платы Микротик RBM33G, что позволяет для этой RBM установить модуль MP3 плейера непосредственно внутрь корпуса роутера. К 3,5 mm line out разъему модуля можно подключить наушники, колонки либо усилитель по выбору пользователя.
Формат вызова функции и список параметров (команд, поддерживаемых $fSMP, для управления воспроизведением) приведены ниже:

fSMP <сmd> функция непосредственного обращения к модулю звукового MP3 плейера. Параметр обязателен и может принимать следующие строковые значения, для некоторых <сmd> также существуют параметры:
Значения принимаемые параметром :

list – выдать список возможных функции в Терминал Router OS, кроме самой . Список выдаётся без параметров и пояснений
TF – выбрать источником воспроизведения TF карту
reset – перезагрузка (сброс) MP3 модуля и интерфейса
sleep – перейти в режим сна
wakeup – перейти в режим готовности к приему команд
play – начать воспроизведение
stop – остановить воспроизведение
pause – пауза
next – проиграть следующий трек в выбранной папке
previous – проиграть предыдущий трек
volumeUP – увеличить значение громкости на одну условную единицу
volumeDW – понизить громкость на условную единицу
volume [0-30] – установить значение громкости
shuffle – режим случайного порядка воспроизведения файлов из папки
playcycle – режим циклического вопроизведения
playfile [XX (0-100) YY (0-254)] – воспроизвести файл YY из папки с номером XX
playfolder [0-100] – воспроизвести все файлы по очереди из папки с указанным номером

* — использованные здесь квадратные и круглые скобки в команде не указываются

Примеры использования:

$fSMP reset – выполнить программный сброс модуля MP3/WAV плейера
$fSMP TF – выбрать источник воспроизведения (не обязательная команда)
$fSMP volume 30 – установить максимальную громкость линейного выхода модуля
$fSMP playfile 7 5 – проиграть файл 005YYY.mp3 из папки 07, YYY после номера в имени файла может принимать значения произвольного текста допустимой длины в имени файла FAT16/FAT32
$fSMP pause – временно остановить воспроизведение
$fSMP play – начать/продолжить воспроизведение после остановки или паузы
$fSMP playfolder 3 – воспроизвести последовательно все MP3/WAV файлы папки 03 TF-карты

Модуль Catalex YX5300 исполняет команды воспроизведения чётко и без задержки, мгновенно реагируя на их смену.

5. Применение программно-аппаратного комплекса MikroVoice для озвучивания событий на роутере.

Используя возможности функции $fVoice (код приведён под спойлером) из собственных скриптов можно легко и удобно озвучить многие события собственного роутера.

Функция озвучивания сетевых событий $fVoice

:global fVoice do={
:global SMPport
:global VoiceFlag
:global VoiceFolder 2; # default RUS
:local fFL7Lang "MikroVoiceLang"
:local MVLang
:local LangRUS 2; #RUS=folder2
:local LangENG 3; #ENG=folder3
:local LangCNR 4; #CNR=folder4
:global fSMP
:local SMPflag true
:do {
:if (!any $fSMP) do={:global fSMP; /system script run mv_fSMP}; 
} on-error={:set SMPflag false}
:if (!$SMPflag) do={:return "Error: no SMP function"}
:if ([:len [/ip firewall layer7-protocol find name=$fFL7Lang]]!=0) do={
:set MVLang [/ip firewall layer7-protocol get [find name=$fFL7Lang] regexp]
:if (($MVLang="RUS") or ($MVLang="ENG") or ($MVLang="CNR")) do={
:if ($MVLang="RUS") do={set VoiceFolder $LangRUS}
:if ($MVLang="ENG") do={:set VoiceFolder $LangENG}
:if ($MVLang="CNR") do={:set VoiceFolder $LangCNR} 
}
} 
:if (([:typeof $2]="str") and (($2="ENG") or ($2="RUS") or ($2="CNR"))) do={
:if ($2="RUS") do={:set VoiceFolder $LangRUS}
:if ($2="ENG") do={:set VoiceFolder $LangENG}
:if ($2="CNR") do={:set VoiceFolder $LangCNR} 
}
:local AlertVoice do={
:global VoiceFolder
:global VoiceFlag
:global fSMP
:local alertJingle 114
:local importantJingle 113
:local currentJingle 112
:if ([:len $1]=1) do={:set $1 [:tonum $1]}
:if (($VoiceFlag>0) or ([:len $1]=1)) do={
:if (($VoiceFlag=1) or ($1=1)) do={
:do {
[$fSMP playfile $VoiceFolder $currentJingle]
} on-error={:return "Error $0 function call MikroVoice module function fSMP"}
:delay 5s
}
:if (($VoiceFlag=2) or ($1=2)) do={
:do {
[$fSMP playfile $VoiceFolder $importantJingle]
} on-error={:return "Error $0 function call MikroVoice module function fSMP"}
:delay 5s
}
:if (($VoiceFlag=3) or ($1=3)) do={
:do {
[$fSMP playfile $VoiceFolder $alertJingle]
} on-error={:return "Error $0 function call MikroVoice module function fSMP"}
:delay 5s
}
} else={
# добавление опции проигрывания джингла с предалармом 5сек (в local AlertVoice)
:do {  :global fAlarm
:if ([$fAlarm find $1]=true) do={[$fAlarm $1]; :delay 5s; [$fSMP stop]
}
}
}
:return []}
local ArrayComVoice {
hellolong=1
inform=2
start=3
reboot=4
hello=5
startset=6
inetcheck=7
wanmain=8
wanreserve=9
inetok=10
inetno=11
inetblock=12
inetrestore=13
waitreboot=14
rebooting=15
shutdown=16
firewalloff=17
firewallon=18
rosavaliable=19
vpnenable=20
vpndisable=21
vpnconnect=22
vpndisconnect=23
"wifi+"=24
wifion=25
"wifi-"=26
wifioff=27
questwifion=28
guestwifioff=29
"lte+"=30
lteon=31
"lte-"=32
lteoff=33
dudeon=34
dudeoff=35
backup=36
"backup_saved"=37
"backup_saved_disk"=38
"backup_saved_cloud"=39
waitrestore=40
restoreok=41
restore=42
firmware=43
telegram=44
healthcare=45
smssend=46
smsin=47
mailsend=48
inetnot=49
switchmain=50
switchreserve=51
"dhcp_client+"=52
"dhcp_client-"=53
dhcpoff=54
dhcpon=55
console=56
badlogin=57
admin=58
"change_password"=61
"change_password_ok"=62
gpson=63
gpsoff=64
usbreset=65
attack=66
attackfix=67
addressblock=68
displeyoff=69
displeyon=70
ftpon=71
ftpoff=72
telneton=73
telnetoff=74
sambaon=75
sambaoff=76
sshon=77
sshoff=78
modbuson=79
modbusoff=80
snmpon=81
snmpoff=82
pingno=83
pingok=84
romonon=85
romonoff=86
radiuson=87
radiusoff=88
portblocked=89
logclear=90
servicelog=91
dhcpclient=92
gatewayok=93
gatewayno=94
powerdown=95
"power_reserve"=96
powerup=97
greeting=98
copiright=99
100=100
checkwork=101
ntpok=102
ntpno=103
timedatenoset=104
timedatewrong=105
ntpcloud=106
timedateset=107
timedateupdate=108
read=109
write=110
admininform=111
accident=112
important=113
current=114
"voice_active"=115
rus=116
eng=117
cnr=118
"newDHCP"=119
"newUserDHCP"=120
"userDHCPblocked"=121
"userWIFIblocked"=122
addroute=123
addmarkroute=124
delroute=125
delmarkroute=126
delinactroute=127
firewallreset=128
firewallinactreset=129
addscript=130
delscript=131
addsched=132
actsched=133
delsched=134
addadrlist=135
addaddress=136
deladdress=137
addintlist=138
"VPNinactreconnect"=139
"LTEinactreconnect"=140
"PPPinactreconnect"=141
"VPNreset"=142
sslclient=143
sslserver=144
password=145
modeminit=146
waitmodem=147
celupdate=148
ati=149
cregok=150
cregno=151
modemreset=152
rssiok=153
rssilow=154
speedhigh=155
speedlow=156
"modem_inetok"=157
"modem_inetno"=158
"firmware_lte"=159
"firmware_lte_ok"=160
"firmware_lte_error"=161
usbconnect=162
usbnas=163
usbdisk=164
usblan=165
usbmodem=166
usbserial=167
usbno=168
usbbusy=169
usbinactive=170
mainofficeno=171
mainofficeok=172
filialno=173
filialok=174
vpnmain=175
vpnreserve=176
vpnmainno=177
vpnreserveok=178
vpnclientno=179
vpnclientok=180
vpnunstable=181
speedvpnhigh=182
speedvpnmiddle=183
speedvpnlow=184
fetchint=185
fetchintans=186
fetchext=187
fetchextans=188
cpu90=189
cpu100=190
cpuload=191
import=192
inkey=193
"enter_login"=194
"enter_password"=195
"enter_key"=196
"enter_yn"=197
wifitimerange=198
inettimerange=199
block=200
unblock=201
tesendactive=202
tesendlogin=203
tesend=204
wifistop=205
wifistart=206
wifirun=207
wifiactive=208
wificonnect=209
wifiout=210
wificome=211
wifishutdown=212
wifilimit=213
ltelimit=214
delfiles=215
mikrotik=216
forum=217
forumrus=218
sertik=219
voicetimerun=220
h00=221
h01=222
h02=223
h03=224
h04=225
h05=226
h06=227
h07=228
h08=229
h09=230
h10=231
h11=232
h12=233
h13=234
h14=235
h15=236
h16=237
h17=238
h18=239
h19=240
h20=241
h21=242
h22=243
h23=244
voicetimestop=245
}
#----------------------------------------------------------------------------
# main function`s code
# ---------------------------------------------------------------------------
:if ([:len $1]=0) do={:return "Error: no set name command/name jingle"}
:if ($1="list") do={
:put ""; :put "<---- List $0 jingles name in function ---->"
:foreach k,v in=$ArrayComVoice do={
:if ([:len $2]>0) do={\
:if ([:len [:find $k $2]]>0) do={:put $k}\
} else={:put $k}
}
:put "";
:return []}
:if ($1="\6C\69\73\74\65\78\74") do={
:put ""; :put "<---- List $0 jingles num in folder $VoiceFolder TF-card and name in function ---->"
:local Lfind [:toarray ""]
:foreach k,v in=$ArrayComVoice do={
:if ([:len $2]>0) do={\
:if ([:len [:find $k $2]]>0) do={:put ("$v"." - "."$k"); :set Lfind ($Lfind,"$k=$v")}\
} else={:put ("$v"." - "."$k")}
}
:put "";
:if ([:len $2]>0) do={:return $Lfind} else={:return $ArrayComVoice}
}
:if ($1="find") do={
:if ([:len ($ArrayComVoice->$2)]>0) do={:return true} else={:return false}
}
:local SMPanswer
:if ($1="stop") do={
:do {
:set SMPanswer [$fSMP mp3type]
:if ($SMPanswer="DFPlayer") do={:return "mp3 module is not supported"}
:set SMPanswer [$fSMP stop]
} on-error={:return "Error $0 function call MikroVoice module function SMP"}
}
:if ($1="pause") do={
:do {
:set SMPanswer [$fSMP mp3type]
:if ($SMPanswer="DFPlayer") do={:return "mp3 module is not supported"}
:set SMPanswer [$fSMP pause]
} on-error={:return "Error $0 function call MikroVoice module function SMP"}
}
:if ($1="playback") do={
:do {
:set SMPanswer [$fSMP mp3type]
:if ($SMPanswer="DFPlayer") do={:return "mp3 module is not supported"}
:set SMPanswer [$fSMP play]
} on-error={:return "Error $0 function call MikroVoice module function SMP"}
}
:if ($1="lang") do={
:if (($2="RUS") or ($2="ENG") or ($2=CNR)) do={ 
:if ([:len [/ip firewall layer7-protocol find name=$fFL7Lang]]=0) do={
/ip firewall layer7-protocol add name=$fFL7Lang regexp=$2 \
} else={\
:if ($2!=[/ip firewall layer7 get [find name=$fFL7Lang] regexp]) do={
/ip firewall layer7-protocol set [find name=$fFL7Lang] regexp=$2}
:return "Done. Select MikroVoice language $2"}
} else={:return "ERROR select language $2"}
}
:local VoiceNum; :do {:set VoiceNum [:tonum $1]} on-error={}
:local JingleNum
:local cmd
:if ([:typeof $VoiceNum]="num") do={
:foreach k,v in=$ArrayComVoice do={:if ($v=$VoiceNum) do={:set cmd $k; set JingleNum $v}}
:if ([:len $cmd]=0) do={:return "Error: jingle number mismatch $0"}
:do {$AlertVoice $3} on-error={}
:put ("Play jingle"." $cmd". " < $JingleNum >"." in bibliojinglesfolder $VoiceFolder")
:log warning ("Play jingle"." $cmd". " < $JingleNum >"." in bibliojinglesfolder $VoiceFolder")
:do {
:set SMPanswer [$fSMP playfile $VoiceFolder $VoiceNum]
} on-error={:return "Error $0 function call MikroVoice module function SMP"}
} else={
:set cmd ($ArrayComVoice->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command/name jingle"}
:do {$AlertVoice $3} on-error={}
:put "Play jingle $1 in bibliojinglesfolder $VoiceFolder"
:log warning "Play jingle $1 in bibliojinglesfolder $VoiceFolder"
:do {
:set SMPanswer [$fSMP playfile $VoiceFolder $cmd]
} on-error={:return "Error $0 function call MikroVoice module function SMP"}
}
:return $SMPanswer
}

Для озвучки индивидуальных ситуаций и решений придётся модернизировать базу $fVoice и создать собственные звуковые джинглы на TF-карте. Либо можно создать копию штатной $fVoice со своей базой джинглов, размещённых в отдельном каталоге.

Формат вызова функции $fVoice и примеры использования приведены ниже:

$fVoice jingleName/jingleNum, где jingleName – понятное (смысловое) имя озвучиваемого события/действия на роутере или его jingleNum — номер в базе текстов

Вместо jingleName/jingleNum могут использоваться зарезервированные слова, являющиеся опциями функции:

$fVoice list – позволяет просмотреть в /terminal или получить в массив имена
всех доступных джинглов библиотеки, управляемой этой функицией
$fVoice list jinglePattern – аналогичный вывод списка имен, включающих шаблон
$fVoice find jingleName – поиск джингла jingleName в базе джинглов функции. Возвращает true или false в зависимости от результата поиска
$fVoice lang — установка языка озвучивания по умолчанию (RUS, ENG, CNR)

$fVoice pause – пауза воспроизведения
$fVoice stop – остановка воспроизведения джингла
$dVpoce playback – возобновление после паузы

$fVoice hellolong – воспроизвести джингл приветствия (эквивалентно $Voice 1)

$fVoice rosavaliable ENG – воспроизвести джингл «для обновления доступна новая версия Роутер ОС» (эквивалентно вызову того же джингла по номеру — $Voice 19) на английском языке
$fVoice start CNR cucaracha – воспроизвести джингл «старт роутера» на китайском языке с предджинглом мелодии cucaracha из базы функции $fAlarm

Можно вызывать функции $fSMP и $fVoice из любых собственных скриптов и заданий планировщика Роутера, подобрав для них «озвучки» соответствующего смысла из базы $fVoice. Также можно вставлять вызовы этих функций в частные инструменты Роутер OС, такие как /ppp profile, /ip dhcp-client, /ip dhcp server lease script, /netwatch и другие…

Ниже (Рис. 6) приведён простой пример озвучивания при получении роутером Микротик адреса на интерфейс, настроенным как dhcp-client. В данном примере, как при получении адреса, так и при потери его интерфейсом ether1, будет воспроизводиться джингл короткого приветствия:

Рис. 6. «Звуковой» скрипт dhcp-клиента

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

Библиотека MikroVoiceSys.rsc содержит также и другие функции, не обязательные для озвучивания сетевых событий, позволяющие воспроизводить коллекции музыки, аварийных джинглов и будильники, голосовые мемы и шутки, а также информационные функции и функцию переустановки/удаления библиотеки (подробнее см. в MikroVoice manual).

6. MultyCards version

Пока писал эту статью адаптировал MikroVoice под более «перспективные» звуковые модули для подобного «озвучивания»: DF1201S (DF Player PRO) от DFRobot и BT201 Shenzhen Qingyue Electronics (Рис. 7), работающие аналогичным образом, но поддерживающие AT-команды для интерфейса управления, что позволяет не только передать им команду, но и получить ответ средствами /ppp client at-chat Микротик строкой типа:

:local Modulanswer [/interface ppp-client at-chat $PppclientName input=("AT+"."$cmd"."$postfix") as-value]

Рис. 7 DF1201S (DFP PRO) от DFRobot (справа) и BT201 Shenzhen Qingyue Electronics (слева).

Так появилась MultyCards версия MikroVoice, при инсталляции запрашивающая тип Вашего mp3 проигрывателя и устанавливающая версию $fSMP, поддерживающую именно его. Остальные функции системы, как мы знаем, являются hardware-независимыми. Возможности всех трех mp3 плейеров несколько различаются, но я старался сделать так, чтобы их команды работали максимально схоже. DFPlayer и BT201 кроме AT-команд управления воспроизведением имеют также AT-команды опроса системы, позволяющие вернуть данные о том сколько файлов записано на диск, какова продолжительность каждого и т д… Подробно их возможности описаны в соответствующих руководствах (datashit) и руководстве MikroVoice, которые прилагаются мной к MikroVoice. Различия в наборе команд $fSMP для всех трех модулей видны из таблицы ниже (Рис. 8):


Рис.8. Параметр $1 функции $fSMP различных звуковых модулей системы MikroVoice

$fVoice, осуществляющая озвучивание событий на роутере, использует практически только возможности [$fSMP playfile] и работает одинаково для всех трех модулей. Оба последних проигрывателя не поддерживают режим проигрывания файлов в папке (playfolder), поэтому эта команда и функция $fMeloman для них не реализованы. В DFPlayer почемуто не работает, заявленная в datashit команда AT+PP (play/pause), в скрипте я оставил эту возможность, не работающую, но заявленную в руководстве. BT201 может воспроизводить звуковые файлы как с TF-карты, так и с SD-карты, имея оба разъема. Переключение источника воспроизведения осуществляется командами [$fSMP TF] и [$fSMP SD], соответственно (по умолчанию используется TF). Вообще говоря, возможности BT201 гораздо шире, включая работу с bluethooth, которые я даже не рассматривал, так как для наших задач они не нужны. Для полной совместимости $fVoice и $fSMP структура папок на носителе должна быть одинаковой – имена папок только в виде двухзначных номеров (соответственно поддерживается 99 папок), имена файлов озвучиваемых событий только в виде трехзначных номеров без каких-либо добавлений и только в формате mp3 (для $fSMP использующей DFPlayer). Команда $fSMP reset позволяет сбросить модули и/или устранить коллизии в работе, связанные с особенностью определения порта, его занятостью и работой pptp-out интерфейса. DFPlayer не работает с картами памяти, имеет только встроенную flash-память на 128МБ, которой с избытком хватает для задач озвучивания событий на роутере, но явно не достаточно для хранения музыкальных коллекций. Записать/удалить записи на DFPlayer можно непосредственно с ПК, подключив его через имеющияся разъем USB type-C, что позволяет более оперативно обновить базу звуковых файлов, не открывая корпус MikroJuxBox (при корпусном исполнении). В отличии от двух других mp3 проигрывателей DFЗPlayer имеет два audio-канала и может служить внешней звуковой картой (для ПК, жаль, что не для Микротика).

7. Бонус

Подарю читателю несколько скриптов и скриптовых заданий для Планировщика вкачестве примеров использования системы MikroVoice.
Первый из них – скрипт проверки доступности Интернет на lte1 канале.

# check internet MikroVoice
/system script run mv_fVoice ; /system script run mv_fAlarm
:global $fVoice; :global $fAlarm; $fVoice lang ENG
:if ([/ping 8.8.8.8 interface=lte1 count=3]=0) do={\
:if ($inetCheckMV) do={
$fVoice inetno
:delay 2s
$fVoice inetno
:delay 2s
$fVoice inetno
:delay 2s
:set  inetCheckMV false}
} 
:if ([/ping 8.8.8.8 interface=lte1 count=3]>0) do={\
:if (!$inetCheckMV) do={
$fAlarm run; :delay 5s
$fVoice inetok
:delay 2s
f$Voice inetrestore
:delay 2s
$fVoice inetok
:delay 2s
:set  inetCheckMV true}
}

Второй – скрипт «кукушка» — проверка и озвучивание текущего времени. В каждый полный час скрипт сообщает голосом текущее время. Каждые полчаса отбивает кукушка. Разместить в Планировщике следует с интервалом повторения каждые 30 минут.

# voicing the current time
:do {
:global fVoice
:global fAlarm
:if ([:pick [/system clock get time] 4 6]="30")\
do={
$fAlarm clockcoco]
} else={
$fVoice ("[:pick [/system clock get time] 0 2]"."h")
}
} on-error={}

Третий пример – скрипты мониторинга подключения новых wifi-клиентов и новых PPP VPN-клиентов. Оба скрипта используют возможности print follow only where, работают «в фоне», отслеживая появление новых записей в соответствующих таблицах:

# wifi Voice monitoring
:global wifiVoiceEventHandler do={
:global fVoice
/log warning "On $1 is added a new item $2 $3"
:if ([:len $3]>0) do={[$fVoice wificonnect]}
:return []
}
:execute {
:global wifiVoiceEventHandler
/interface wireless registration-table print follow-only where [$wifiVoiceEventHandler "wifi registration" $comment $interface]
}
# vpn Voice monitoring
:global pppVoiceEventHandler do={
:global fVoice
/log warning "On $1 is added a new item $2 $3"
:if ([:len $2]>0) do={[$fVoice vpnconnect]} else={[$fVoice vpndisconnect]}
:return []
}
:execute {
:global pppVoiceEventHandler
/ppp active print follow-only where [$pppVoiceEventHandler "ppp active" $name $"caller-id"]
}

8. Вместо заключения

Предполагаю, что реакция читателей на данную разработку будет не однозначной. Найдутся как сторонники, так и явные противники. Чтобы исключить не нужные комментарии прошу критиков сразу учесть, что рассуждения о личных предпочтениях мной не будут рассматриваться. Не нужно, например, сравнивать проект, выполненный автором, и возможности нейросетевых «озвучек» типа, скажем, колонки Алиса. В данной работе ставилась задача озвучить события именно на роутерах Микротик (и ни на каких других), и только программными средствами самого роутера, не прибегая ни к каким сторонним API и сетевым ресурсам (так как в нужный момент как раз они могут оказаться для роутера не доступными). Также прошу учесть по сути грошовую стоимость оборудования и вложений при полученной надежности, универсальности и открытом коде для использования в собственных разработках и скриптах. Всё что я сделал, видит Бог, делал я не из коммерческих соображений, а в большей степени потому, что мне самому это было очень интересно.
При этом, как мне кажется, автору полностью удалось создать продукт, по сути готовый к мелкосерийному производству. Захотят ли пользователи Микротик (пользователями Микротик, я называю, конечно, не домохозяек, а «домашних», офисных и профессиональных админов), чтобы их роутеры «заговорили» оповещая своих владельцев о происходящих событиях в сети или проигрывали во время отдыха от работы настоящую музыку, вместо привычных монотональных мелодий встроенного бипера – решать Вам самим. Разумеется, все права автора на данную разработку должны быть соблюдены. Энтузиасты Микротик и лица, заинтересованные в серийном производстве, а также представители SOHO-сегмента, желающие иметь MikroVoice, могут обращаться к автору на е-mail проекта: mikrovoice@bk.com.
Компания Микротик, кстати, могла бы легко внедрить звуковой чип на свои RouterBoard и, добавив, соответствующий пакет к ядру системы, озвучить хотя бы часть своих роутеров, тем более что на OpenWRT, например, это вполне выполнимо, а на Router OS нет.
Но первыми это всё же сделали мы, Росияне!

Хочу отдельно поблагодарить:

Amm0 – гуру официального форума Микротик за подсказку по возможности использования ppp-out интерфейса Роутер ОС Микротик для связи с оборудованием, подключаемом к serial или USB портам роутера.
Chupakabra – за функцию $fBy ($CHR из набора функций JSON-парсера для Роутер ОС Микротик, использованную мной в функции SMP и необходимую для «перекодирования» HEX-данных, отправляемых звуковому модулю.
Rextended и Amm0 — за подсказки в реализации средств работы с USB-портом RouterBoard Микротик, Amm0 – за функцию $CHOICES ($Chs), использованную в меню установщика системы при выборе языка озвучивания по умолчанию.
Моих друзей и коллег Родионова Павла и Головач Константина, инженеров отделения рентгенодиагностики НИМЦ НХ им. Н. Н. Бурденко (по месту моей основной работы) — за ценные советы, моральную поддержку во время разработки и пайку разъемов опытных версий аппаратной части системы.
Махиянову Алину – за перевод текста джинглов функции $fVoice для носителя модулей на китайский язык и озвучивания их собственным голосом.

Результаты данной работы я планировал доложить на MUM Россия 2024, который должен был состояться 18 сентября и мой доклад на нём вроде как был одобрен. Но до настоящего времени организаторы так и не связались со мной, а недавно выяснилось, что мероприятие переносится на ноябрь.
Ну нет, подумал я, до ноября ждать не стоит, тем более что я могу более подробно изложить всё в статье, и прочтёт её, конечно, гораздо больше пользователей Микротик, чем количество очных посетителей MUMа. Те, кто хотел бы увидеть презентацию моего доклада для MUM могут скачать её по ссылке. Там же будут чуть позже расположены код MikroVoice и папки с джинглами для озвучивания событий на роутере.

Желающие получить более узкие адаптации MikroVoice непосредственно под задачи своей сети и объекта (дом/офис) могут обращаться к автору.

© Sertik 2024

 

Источник

Читайте также