USB GPIO EXTENDER – another opportunity to manage loads from the Mikrotik router
Previously, I wrote several articles about the possibility of controlling the relay directly from the Router OS Mikrotik devices such as
Laurent
and
Rhodes
and also at
using the capabilities of the built-in GPIO RB Mikrotik 33G
. Many people consider it unnecessary or underestimate it, but in vain, because it is a very useful function, for example, for rebooting network equipment, turning on additional equipment or hardware protection of a PC, NAS, etc. directly from the router.
Recently, I found on the Internet the domestic company “Open Developments” and its product USB GPIO EXTENDER – a small and cute device for a USB port that has “on board” 10 digital lines: 5 input lines and 5 output lines.
Fig. 1. USB GPIO EXTENDER
As a non-specialist, I will not judge the hardware part and the reliability of the module (professionals can comment on this), the board looks neat, all the elements are “tweed”, no “bank” capacitors.
The device supports the following commands:
And when they are performed, it gives the same format of responses from the device:
As we can see from the list of commands, for some reason there are commands for reading states of IN-lines, but there are no similar commands for reading states of OUT lines… How to find out what state the output lines are in is not clear (significant minus).
We also see that there is no response to the reboot command, which is incorrect. It would be possible to respond to the command first, and then execute it (that is, from the programmer’s point of view, you just need to introduce a reboot delay so that the module has time to send a response to ~B).
Unfortunately, the exchange of commands/responses is not in AT format, which does not allow you to use the functionality of the device with the Mikrotik Router OS in full. From the Router OS, you can send a device command through the standard ppp-out interface in the “dial-on-demand” field:
/interface ppp-client add name=«USB-GPIO-EXTENDER» dial-on-demand=no port=$UGEport modem-init=«~S1» null-modem=yes disabled=no
I’ve used this trick before, for example, when creating “
Voice router
“, but it is impossible to get a response from the module. Thus, firstly, we really do not know whether the command reached the device and we cannot use at least the available commands for reading the status of the IN-lines, and it is a pity – it would be possible to connect various digital sensors. I really hope that the developers of “Open Developments” will hear me and in future versions will come to support AT-commands in USB GPIO EXTENDER once they have already managed simple text commands (thanks, at least not with HEX byte sequences – that’s progress).
To the credit of the developers, it should be noted that the module is perfectly defined by Linux and, accordingly, Router OS, which in /system resourse usb adequately shows the name of the module, by which it is easily identified in the system:
Fig. 2 Output to the Terminal /system resource usb print when the module is connected
Due to the limitations mentioned above, I implemented only support for output (OUT) line control commands and a reset command (an example of connecting a 4-channel 220V relay module to a USB GPIO EXTENDER is shown in Fig. 3 below).
Fig. 3. Connection diagram of USB GPIO EXTENDER input/output lines to a 4-channel relay block (for example, lines 3 and 4 are connected)
The corresponding script function for controlling the output lines for the Mikrotik router is presented under the spoiler
#----------------------------------------------------------------------------
# Functon support OPEN Dev USB GPIO EXTENDER by Sertik 24/09/2024 version 1.0
#----------------------------------------------------------------------------
# check in ROS 6.49.10
# usage:
# $OpenDevExt list
# $OpenDevExt logic [true/false]
# $OpenDevExt reset
# $OpenDevExt SetOut XXYXY [0-1]
# $OpenDevExt OutOn X [1-5]
# $OpenDevExt OutOff Y [1-5]
# все команды модуля:
# -------------------
# ~Sx Установка выхода x в 1.
# ~Rx Установка выхода x в 0.
# ~Gx Чтение текущего значения входа x.
# ~A Чтение значений всех входов в виде «xxxxx». Пример ответа «~A11001».
# ~Pxxxxх Запись значений всех выходов в виде «xxxxx». Например «~P11001».
# ~B Перезагрузка модуля.
# ~I Запросить информация о версии прошивки.
# Поддерживаемые функцией в данной версии команды:
# ------------------------------------------------
# ~Sx
# ~Rx
# ~Pxxxxх
# ~B Перезагрузка модуля. При перезагрузке все out-выходы устанавливаются в 0 !
# logic - чтение/установка логики out-выходов: true - прямая; false - обратная (для реле с прямой и обратной логикой включения/выключения выходов)
# если logic не был задан функция устанавливает прямую логику работы, если был задан - запоминается в global $OpenDevReleLogic
# и команды SetOut OutOn и OutOff работают по ней.
:global OpenDevExt do={
:local version "1.0 by Sertik 24/09/2024"
# установка переменных
:local ModuleType "OpenDevUsbGPIOExtender"
:local portTypeUSB "usb"
:global OpenDevModuleType $ModuleType
:local USBresLinuxName "USB GPIO Extender"
:global OpenDevReleLogic
:local UsbGpioExtFlag false
:local portUSB;
:local BaudRate 9600
:local DataBits 8
:local Parity none
:local StopBits 1
:local FlowControl none
:local PppclientName $ModuleType
# массив имен команд и соответствующих им команд модуля
:local ArrayCom {
"logic"="X"
"OutOn"="~S"
"OutOff"="~R"
"SetOut"="~P"
"reset"="~B"
}
# если имя команды не задано вернуть ошибку
:if ([:len $1]=0) do={:return "Еrror: no set name command"}
# команда help в Терминал
:if ($1="help") do={
:put ""; :put "---- Function support for $OpenDevModuleType ----"
:put " version $version"
:put " usage:"
:terminal style "syntax-meta"
:put "$0 help"
:put "$0 list"
:put "$0 logic [true/false]"
:put "$0 reset"
:put "$0 SetOut XXYXY [0-1]"
:put "$0 OutOn X [1-5]"
:put "$0 OutOff Y [1-5]"
:terminal style none
:return []}
# команда list в Терминал
:if ($1="list") do={
:put ""; :put ""
:foreach k,v in $ArrayCom do={:put (" "."$k")}
:return []}
# команда logic
:if ($1="logic") do={
:if (($2="true") or ($2="false")) do={
:if ($2="true") do={:set OpenDevReleLogic true}
:if ($2="false") do={:set OpenDevReleLogic false}
:return OK
} else={:if ([:len $2]=0) do={
:if (($OpenDevReleLogic=true) or ($OpenDevReleLogic=false)) do={
:return $OpenDevReleLogic} else={:return "logic is not specified or incorrect"}
}
:return ("Error"." $0"." $1")}
}
# проверка определения модуля в USB прерываниях Linux ядра Router OS
:local UsbGpioExtName
:do {
:set UsbGpioExtName [/system resource usb get [/system resource usb find name~$USBresLinuxName] name]
} on-error={}
:if ($UsbGpioExtName=$USBresLinuxName) do={:set UsbGpioExtFlag true}
:if ($UsbGpioExtFlag=false) do={:return "Error: Not find $OpenDevModuleType module in ROS system. Please, check device in USB port"}
# определение порта
:global ODUsbGPIOExtPort
:local NewPort
:local NowPort $ODUsbGPIOExtPort; # сохранить текущий порт
:do {
:foreach portId in=[/port find name~$portTypeUSB !inactive] do={:set portUSB ([/port get $portId]->"name")}
} on-error={}
:set NewPort $portUSB
# если возможный порт так и не найден или изначально в $ODUsbGPIOExtPort установлен ошибочный порт, то выдать ошибку
:if (([:len $NewPort]=0) or ([:len [/port find name=$NewPort]]=0)) do={:return "Error: Not find port for $OpenDevModuleType module, port inactive or busy. Please, check /port"}
# если функцией был найден (определён) новый порт, а $ODUsbGPIOExtPort, который использовался функцией ранее, инактивировался, то выбрать в качестве рабочего новый найденный порт
:if (($NowPort!=$NewPort) and ([/port find name=$NowPort and inactive=yes])) do={:set ODUsbGPIOExtPort $NewPort} else={:set ODUsbGPIOExtPort $NowPort}
:if ([:len $ODUsbGPIOExtPort]=0) do={:set ODUsbGPIOExtPort $NewPort}
# одна консоль может быть настроена только на один порт или консоли вообще может не быть в /system console !
:local consoleFlagOff false
if ([:len [/system console find port=$ODUsbGPIOExtPort and !disabled]]>0) do={
:set consoleFlagOff true
/system console set [/system console find port=$ODUsbGPIOExtPort] disable=yes
}
# установка параметров порта, выбранного для работы
do {
/port set [/port find name=$ODUsbGPIOExtPort] baud-rate=$BaudRate data-bits=$DataBits parity=$Parity stop-bits=$StopBits flow-control=$FlowControl
} on-error={:return "Error set port $ODUsbGPIOExtPort. Function $0 d`not work"}
# main function`s code
# --------------------
# взять код команды из массива по имени команды
:local cmd ($ArrayCom->$1)
:if ([:len $cmd]=0) do={:return "Error: bad command"}
:put "Execute command $OpenDevModuleType: $1 $2"
:log warning "Execute command $OpenDevModuleType: $1 $2"
# проверить и подготовить параметры для команд в соответствии с логикой подключения к выходам
:if ((($1="OutOn") or ($1="OutOff")) and ([:len $2]!=1)) do={:return ("Error function: "."$0 "."$2 - there is incorrect data, set number in range [1-5]")}
:if (($1="SetOut") and ([:len $2]!=5)) do={:return ("Error function: "."$0 "."$2 - there is incorrect data, set the status of 5 outputs")}
:if ([:len $OpenDevReleLogic]=0) do={:set OpenDevReleLogic true}
:if (($1="OutOn") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOff")}
:if (($1="OutOff") && ($OpenDevReleLogic=false)) do={:set cmd ($ArrayCom->"OutOn")}
:if (($1="SetOut") && ($OpenDevReleLogic=false)) do={
:local r ""
:for i from=0 to=([:len $2] -1) do={
:if ([:pick $2 $i (1 + $i)] = "1") do={:set r ($r . "0")} else={:set r ($r . "1")}
}
:set $2 $r
}
# удалить интерфейс ppp-клиента на всякий случай, если был оставлен ошибочным вызовом
:if ([/interface ppp-client find name=$PppclientName]) do={/interface ppp-client remove [/interface ppp-client find name=$PppclientName]}
# :put ("Send module "."$USBresLinuxName "."command: "."$cmd"."$2")
# передача команды устройству serial-MP3-player через ppp-client
/interface ppp-client add name=$PppclientName dial-on-demand=no port=$ODUsbGPIOExtPort modem-init=("$cmd"."$2") 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=$ODUsbGPIOExtPort] disable=no} on-error={}
}
# конец работы вернуть "OK"
:return OK
}
.
Unfortunately, the manufacturer supplies the device only without a case. At the same time, the pins of the connection block are directed up from the board, not back, which makes it inconvenient to independently select the case for the device, increasing its expected height, rather than the length, which would be more compact and logical. If the manufacturer planned that his device will be installed in the case of any customer’s product, then the size of the module board should be slightly increased and mounting holes should be made in it, which are also not there, and also instead of the “female” USB connector, install a connector I have “male”, which the customer could display on the panel of his case.
On a power reset or a reset command, the module resets all output lines to “0”. If a load is connected to them, then depending on the type of relay logic, it will either be turned on or off. The module does not have an option to save the state of the lines during a cold/hot restart, although the latter probably has a flash memory (given the presence of a programmable version with TOIC, which I don’t have time to deal with).
P/S The version with TOIC (an environment for creating firmware, also developed in “Open Developments”), by the way, allows you to change the number of inputs / outputs by reprogramming them, which would allow, for example, to connect more relays (up to 10) to control the load.
Taking into account the convenient external connection to the Mikrotik through the USB port and taking into account patriotism (I honestly admit that there are no imported analogues, but I definitely have never seen them), we give the manufacturer a “four” for the device so far and hope for the introduction of the specified necessary finalization plan of the future version of the firmware of the module.
All materials are also presented in the author’s corresponding section on GitHub.