Адаптация прошивки USB GPIO EXTENDER T под нужды Микротик

в 7:38, , рубрики: mikrotik, USB GPIO EXTENDER T (TOIC)
Недавно я писал статью про модуль USB GPIO EXTENDER компании «Открытые разработки», где было отмечено, что прошивка этой версии модуля не поддерживает формат AT-команд для /ppp-client at-chat Роутер ОС Микротик, поэтому отправить команды модулю можно, а получить ответ нельзя. Это даёт возможность управления выходными линиями GPIO, но не позволяет использовать входные линии. Хочу немного дополнить предыдущую статью описанием другой, программируемой версии модуля USB GPIO EXTENDER T (TOIC), выполненной на темно-синей плате в отличии от непрограммируемой версии (а может такая попалась мне), которая содержит встроенный язык программирования TOIC, а компания поставляет среду разработки и прошивки скрипта в модуль для этого языка. Версия с TOIC поставляется с демонстрационными скриптами Demo1 и Demo2 доступными на сайте разработчика. Demo 1 практически аналогичен прошивке непрограммируемого USB GPIO EXTENDER (5 линий OUT, 4 линии IN). Demo 2 (под спойлером) поддерживает не только GPIO линии ввода/вывода, но и ADC, PWM и SPI.

Скрипт прошивки USB GPIO EXTENDER T Demo 2

#define FW_STR "2.1 02 Aug 2021"
  
/*
IO1 - PA4 - ADC
IO2 - PA2 - OUTPUT
IO3 - PA0 - SPI CS
IO4 - PA6 - SPI MISO
IO5 - PA7 - SPI MOSI
IO6 - PA3 - INPUT
IO7 - PA1 - PWM OUTPUT
IO8 - NC
IO9 - PB1 - PWM INPUT
IOx - PA5 - SPI SCK
LED - PF0
 */

var _msg() {
      var c = stoi(&MSG.RX, 'c');
      memcpy(SYS.RAM, &MSG.RX, MSG.SIZE);
      switch (c) {
          case 'S':
              PA2.VALUE = 1;
              break;
          case 'R':
              PA2.VALUE = 0;
              break;
          case 'A':
              sprintf(&UART0.TX, "A%dn", PA4.VALUE);
              break;
         case 'K':
              PA0.VALUE = 0;
              memcpy(&SPI.DR, SYS.RAM+1, MSG.SIZE-1);
              sprintf(&UART0.TX, "K%dn", SYS.RAM+1);
              PA0.VALUE = 1;
              break;
          case 'G':
              sprintf(&UART0.TX, "G%dn", PA3.VALUE);
              break;
          case 'F':
              sprintf(&UART0.TX, "F%dn", PB1.VALUE);
              break;
          case 'I':
              sprintf(&UART0.TX, "I%sn", FW_STR);
              break;       
          default:
              break;
      }
}


var io_setup() {
    // IO1 - PA4 - ADC
    PA4.MODE = GPIO_MODE_ADC; 
    // IO2 - PA2 - OUTPUT
    PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
    // IO6 - PA3 - INPUT
    PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN;
    // IO7 - PA1 - PWM OUTPUT
    PA1.MODE = GPIO_MODE_OPWM|GPIO_INIT_LOW|GPIO_OTYPE_PP;
    // IO9 - PB1 - PWM INPUT
    PB1.MODE = GPIO_MEASURE_FREQ|GPIO_MODE_IPWM|GPIO_INIT_LOW; 
    // LED - PF0
    PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_HIGH|GPIO_OTYPE_PP;
    
    // IO3 - PA0 - SPI CS
    // IO4 - PA6 - SPI MISO
    // IO5 - PA7 - SPI MOSI
    // IOx - PA5 - SPI SCK
    PA0.MODE = GPIO_INIT_LOW|GPIO_OTYPE_PP|GPIO_TYPE_SOFTWARE|GPIO_MODE_OUTPUT; 
    SPI.SETUP = SPI_SETUP_POLARITY_LOW|SPI_SETUP_EDGE_LEADING|1000/*kBod*/;
    SPI.EN = 1;
    
    PA1.PWM = 128; 
    TIM2.FREQ = TIM3.FREQ = 2;                                
    TIM2.EN = TIM3.EN = 1;
    UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
    __enable_irq();
}


var main() {

    io_setup();
    while (1) {};
    return 0;
}

Данный скрипт следующим образом настраивает пины устройства:

IO1 — PA4 — АЦП (0-3.3В)
IO2 — PA2 — Дискретный выход
IO3 — PA0 — SPI CS
IO4 — PA6 — SPI MISO
IO5 — PA7 — SPI MOSI
IO6 — PA3 — Дискретный вход (до 3.3В)
IO7 — PA1 — PWM выход с фиксированной частотой
IO8 — не используется
IO9 — PB1 — PWM вход (до 3.3В)
IOx — PA5 — SPI SCK

и поддерживает следующие команды:

S — Установить выход (2) в 1,
R — Установить выход (2) в 0,
G — Считать данные со входа (6),
A — Считать данные с АЦП (1),
K — Отправить в SPI данные и прочитать ответ,
F — Считать данные с определителя частоты PWM (9),
I — Считать информацию о версии скрипта.

На сайте TOIC, также разработанным Open Development, есть весьма урезанное описание среды разработки и языка программирования и данный пример скрипта, а также скрипты для других устройств компании. Но без описаний переменных скрипта и значений регистров модуля до конца понять всё не представляется возможным.
Я немного модифицировал скрипт для возможности полноценного использования устройства в Микротик Роутер ОС, добавив в возвраты команд rnOKrn, что позволяет интерфейсу /ppp-client при at-chat «увидеть» OK от устройства и вернуть буфер в Роутер ОС.

Скрипт прошивки USB GPIO EXTENDER T Demo 2 (modify Sertik 08/10/2024)

#define FW_STR "2.1 for Mikrotik modify Sertik"
  
/*
IO1 - PA4 - ADC
IO2 - PA2 - OUTPUT
IO3 - PA0 - SPI CS
IO4 - PA6 - SPI MISO
IO5 - PA7 - SPI MOSI
IO6 - PA3 - INPUT
IO7 - PA1 - PWM OUTPUT
IO8 - NC
IO9 - PB1 - PWM INPUT
IOx - PA5 - SPI SCK
LED - PF0
 */

var _msg() {
      var c = stoi(&MSG.RX, 'c');
      memcpy(SYS.RAM, &MSG.RX, MSG.SIZE);
      switch (c) {
          case 'S':
              PA2.VALUE = 1;
              sprintf(&UART0.TX, "%drnOKrn", PA2.VALUE);
              break;
          case 'R':
              PA2.VALUE = 0;
              sprintf(&UART0.TX, "%drnOKrn", PA2.VALUE);
              break;
          case 'A':
              sprintf(&UART0.TX, "%drnOKrn", PA4.VALUE);
              break;
         case 'K':
              PA0.VALUE = 0;
              memcpy(&SPI.DR, SYS.RAM+1, MSG.SIZE-1);
              sprintf(&UART0.TX, "%drnOKrn", SYS.RAM+1);
              PA0.VALUE = 1;
              break;
          case 'G':
              sprintf(&UART0.TX, "%drnOKrn", PA3.VALUE);
              break;
          case 'F':
              sprintf(&UART0.TX, "%drnOKrn", PB1.VALUE);
              break;
          case 'L':
              PF0.VALUE = 1;
              sprintf(&UART0.TX, "OKrn");
              delay (1000)
              PF0.VALUE = 0;
              break;
          case 'I':
              sprintf(&UART0.TX, "%srnOKrn", FW_STR);
              break;       
          default:
              break;
      }
}


var io_setup() {
    // IO1 - PA4 - ADC
    PA4.MODE = GPIO_MODE_ADC; 
    // IO2 - PA2 - OUTPUT
    PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
    // IO6 - PA3 - INPUT
    PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN;
    // IO7 - PA1 - PWM OUTPUT
    PA1.MODE = GPIO_MODE_OPWM|GPIO_INIT_LOW|GPIO_OTYPE_PP;
    // IO9 - PB1 - PWM INPUT
    PB1.MODE = GPIO_MEASURE_FREQ|GPIO_MODE_IPWM|GPIO_INIT_LOW; 
    // LED - PF0
    PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
    
    // IO3 - PA0 - SPI CS
    // IO4 - PA6 - SPI MISO
    // IO5 - PA7 - SPI MOSI
    // IOx - PA5 - SPI SCK
    PA0.MODE = GPIO_INIT_LOW|GPIO_OTYPE_PP|GPIO_TYPE_SOFTWARE|GPIO_MODE_OUTPUT; 
    SPI.SETUP = SPI_SETUP_POLARITY_LOW|SPI_SETUP_EDGE_LEADING|1000/*kBod*/;
    SPI.EN = 1;
    
    PA1.PWM = 128; 
    TIM2.FREQ = TIM3.FREQ = 2;                                
    TIM2.EN = TIM3.EN = 1;
    UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
    __enable_irq();
}


var main() {

    io_setup();
    while (1) {};
    return 0;

На базе модифицированной прошивки я написал также новую функцию OpnDevExtTOIC version 1, полностью поддерживающую в Микротик эту прошивку и позволяющую получать все ответы от устройства, в том числе состояние линии IN, значения PWM и ADC и ответы SPI-интерфейса, добавив ещё инструкцию (case 'L ') для управления LED-индикатором, которую также использую как тестовую команду «AT» для проверки связи с модулем.

fOpnDevExtTOIC version 1

#----------------------------------------------------------------
# Functon support OPEN Dev USB GPIO EXTENDER TOIC
# by Sertik version 1.0 08/10/2024
#----------------------------------------------------------------
# check in ROS 6.49.10

# usage: 

# $fOpenDevExtTOIC list - output a list of function commands for the module to the terminal and log
# :put [$fOpenDevExtTOIC logic] - return the set value of the operating logic
# $fOpenDevExtTOIC logic [true/false] - set forward/reverse operating logic
# $fOpenDevExtTOIC OutOn - turn on OUT (2)
# $fOpenDevExtTOIC OutOff - turn off OUT (2)
# $fOpenDevExtTOIC In - read IN (6)
# $fOpenDevExtTOIC SPI xxxxx - send xxxxx to SPI and return module`s answer
# $fOpenDevExtTOIC PWM - read PWM-OUT
# $fOpenDevExtTOIC AT - check connect and LED
# $fOpenDevExtTOIC ATI - request information about the firmware version

# module commands:
# -----------------------------------------------------------------------
# S sets the output (2) to 1.
# R sets the output (2) to 0.
# G read the current value of input (6)
# A - read ADC
# K - send data to SPI and take answer
# F - read data in PWM
# L - test connect
# I request information about the firmware version.


:global  fOpenDevExtTOIC do={
 
:local version "1.0 08/10/2024"

:local ModuleType "USB GPIO EXTENDER TOIC"
:local portTypeUSB "usb"
:global OpenDevModuleType $ModuleType
:local USBresLinuxName "TOIC-F0-GE"
: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"
   "In"="G"
   "ADC"="A"
   "PWM"="F"
   "SPI"="K"
   "ATI"="I"
   "AT"="L"
 }

: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 ReadIn"
:put "$0 In [6-9]"
:put "$0 OutOn X [1-5]"
:put "$0 OutOff Y [1-5]"
:put "$0 AT"
:put "$0 ATI"
:terminal style none
  :return []}

# list 
   :if ($1="list") do={
      :put ""; :put "<---- Supported $OpenDevModuleType commands  ---->"
          :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")}
}

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

    :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"}

   :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}
  
  :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 ([: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 ([/interface ppp-client find name=$PppclientName]) do={/interface ppp-client remove [/interface ppp-client find name=$PppclientName]}

# :put ("Send module "."$USBresLinuxName "."command: "."$cmd"."$2")

     /interface ppp-client add name=$PppclientName dial-on-demand=no port=$ODUsbGPIOExtPort null-modem=yes disabled=yes
     :delay 1s
     :local GPIOanswer [/interface ppp-client at-chat $PppclientName input=("$cmd"."$2") as-value]
     /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={}
     }
# end	 
   :return $GPIOanswer
}

Функция имеет более удобные названия команд ($1 параметр) для модуля, они приведены в коде функции.

Надо отметить, что в

/system resource usb print

TOIC-версия модуля отображается как «TOIC-F0-GE», а не как «USB GPIO EXTENDER», что актуально для непрограммируемой версии. Не знаю одинаково ли это для всех экземпляров TOIC-версии, или имя в firmware может меняться взависимости от партии или прошивки TOIC. Если у Вас отображается другое имя, то его нужно заменить в коде моей функции (переменная USBresLinuxName), иначе функция не найдет модуль.

Пригодится кому-либо или нет — вопрос, у меня работает. Дальше ковырять скрипт я не стал, так как не имею навыков программирования на TOIC, представляющим собой какую-то урезанную смесь Питона и Си++ (возможно тем, кто свободно программирует на этих языках и посмотрит исходники скриптов выше, это не составит никакого труда).

Что касается версии Demo 1 скрипта, то её я тоже модифицировал аналогичным образом:

Скрипт прошивки USB GPIO EXTENDER T Demo 1 (modify Sertik 08/10/2024)

#define FW_STR «1.1 for Mikrotik modify Sertik»

/*
IO1 — PA4 — OUTPUT
IO2 — PA2 — OUTPUT
IO3 — PA0 — OUTPUT
IO4 — PA7 — OUTPUT
IO5 — PA6 — OUTPUT

IO6 — PA3 — INPUT
IO7 — PA1 — INPUT
IO8 — PA13 — INPUT
IO9 — PB1 — INPUT
IO10 — BOOT

IOx — PA5 — INPUT PULLED DONW R10K
LED — PF0

*/

var GPIO_OUT = [PA4.VALUE, PA2.VALUE, PA0.VALUE, PA7.VALUE, PA6.VALUE];
var GPIO_IN = [PA3.VALUE, PA1.VALUE, PA13.VALUE, PB1.VALUE, PB1.VALUE];

var io_setup() {
// OUTPUTS
PA4.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA2.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA7.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
PA6.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// INPUTS
PA3.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA13.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PB1.MODE = GPIO_MODE_INPUT | GPIO_PULL_UP;
PA5.MODE = GPIO_MODE_INPUT | GPIO_PULL_DOWN; // unused
// LED — PF0
PF0.MODE = GPIO_MODE_OUTPUT|GPIO_INIT_LOW|GPIO_OTYPE_PP;
// UART
UART0.CFG = UART_MODE_PLAIN | UART_CONFIG_START;
__enable_irq();
}

var _msg() {
if(MSG.PORT == 4) {
memcpy(&RING.BUF, &MSG.RX, MSG.SIZE);
}
}

var main() {
var c;
var pstate;
var p_index;
var p;
io_setup();
if((RING.ALLOC = 16) != 16) {
return 1;
}
SYS.WDG = 1;
while(1){
if(RING.QUEUE > 0) {
c = RING.PULL;
if (c == '~') {
pstate = 1;
p_index = 0;
} else if (pstate == 1) {
pstate = 0;
switch © {
case 'I':
sprintf(&UART0.TX, "%srnOKrn", FW_STR);
pstate = 0;
break;
case 'L':
PF0.VALUE = 1;
sprintf(&UART0.TX, «OKrn»);
delay (1000);
PF0.VALUE = 0;
break;
case 'B':
sprintf(&UART0.TX, «module resetingrnOKrn»);
delay (1000);
SYS.RESET = 1;
break;
case 'A':
sprintf(&UART0.TX, "%d%d%d%d%drnOKrn", *GPIO_IN[0], *GPIO_IN[1], *GPIO_IN[2], *GPIO_IN[3], *GPIO_IN[4]);
pstate = 0;
break;
case 'S':
case 'R':
case 'G':
case 'P':
pstate = c;
break;
default:
break;
}
} else if (pstate) {
p = c — '0';
if (pstate == 'S' || pstate == 'R') { // ~S or ~R
if (p > 0 && p < 6) {
GPIO_OUT[p — 1] = (pstate == 'S');
sprintf(&UART0.TX, "%c%drnOKrn", pstate, p);
} else {
sprintf(&UART0.TX, «Errrn»);
}
pstate = 0;
} else if (pstate == 'G') { // ~G
if (p > 0 && p < 6) {
sprintf(&UART0.TX, "%drnOKrn", *GPIO_IN[p — 1]);
} else {
sprintf(&UART0.TX, «Errrn»);
}
pstate = 0;
} else if (pstate == 'P') { // ~P
GPIO_OUT[p_index++] = (c == '1');
if (p_index == 5) {
sprintf(&UART0.TX, "%d%d%d%d%drnOKrn", *GPIO_OUT[0], *GPIO_OUT[1], *GPIO_OUT[2], *GPIO_OUT[3], *GPIO_OUT[4]);
pstate = 0;
}
}
}
} else {
delay(10);
}
SYS.WDG = 42;
}

return 0;
}

Настроенные пины:

IO1 — PA4 — выход 1
IO2 — PA2 — выход 2
IO3 — PA0 — выход 3
IO4 — PA7 — выход 4
IO5 — PA6 — выход 5
IO6 — PA3 — Вход1
IO7 — PA1 — Вход2
IO8 — PA13 — Вход3
IO9 — PB1 — Вход4
IO10 — Контакт входа в загрузчик
IOx — PA5 — Доп. вход с подтяжкой резистром 10КОм к земле
PF0 — управляемый светодиод

Демонстрационный код 1 поддерживает следующие команды:

~Sx Установка выхода в x в 1
~Rx Установка выхода в x в 0
~Gx Чтение текущего значения входа x
~A Чтение значений всех входов в виде «xxxxx». Пример ответа «~A11001»
~Pxxxxх Запись значений всех выходов в виде «xxxxx». Например «~P11001»
~B Перезагрузка модуля.
~I Запросить информация о прошивке.
Примечание: т.к. IO10 используется для перехода в загрузчик, то он не используется в программе и вместо него при ~A отображается еще раз сигнал IO9.

И под него соответствующая функция для Микротик

fOpenDevExtTOIC version 2

#----------------------------------------------------------------
# Functon support OPEN Dev USB GPIO EXTENDER T (TOIC)
# by Sertik version 2.0 08/10/2024
#----------------------------------------------------------------
# check in ROS 6.49.10

# usage: 

# $fOpenDevExt list - output a list of function commands for the module to the terminal and log
# :put [f$OpenDevExt logic] - return the set value of the operating logic
# $fOpenDevExt logic [true/false] - set forward/reverse operating logic
# $fOpenDevExt reset - module reset
# $fOpenDevExt SetOut XXYXY [0-1] - setting all Out outputs, for example 00111
# $fOpenDevExt OutOn X [1-5] - “turn on” output X 
# $fOpenDevExt In X [1-5] - read IN X
# $fOpenDevExt ReadIn XXYXY - read all In input
# $fOpenDevExt OutOff Y - “turn off” output Y [1-5]
# $fOpenDevExt AT - test connect
# $fOpenDevExt ATI - firmware information

# module commands:
# -----------------------------------------------------------------------
# ~Sx sets the output in x to 1.
# ~Rx sets the output at x to 0.
# ~Gx read the current value of input x.
# ~A read the values of all inputs as "xxxxx". Example response "~A11001".
# ~Pxxxxx writes the values of all outputs as “xxxxx”. For example "~P11001".
# ~B reboot the module.
# ~I request information about the firmware version.
# ~L test AT connect


:global  fOpenDevExtTOIC do={
 
:local version "2.0 08/10/2024"

:local ModuleType "USB GPIO EXTENDER TOIC"
:local portTypeUSB "usb"
:global OpenDevModuleType $ModuleType
:local USBresLinuxName "TOIC-F0-GE"
: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"
   "ReadIn"="~A"
   "In"="~G"
   "reset"="~B"
   "AT"="~L"
  "ATI"="~I"
 }

: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]"
:put "$0 ReadIn"
:put "$0 In [6-9]"
:put "$0 AT"
:put "$0 ATI"
:terminal style none
  :return []}

# list 
   :if ($1="list") do={
      :put ""; :put "<---- Supported $OpenDevModuleType commands  ---->"
          :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")}
}

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

    :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"}

   :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}
  
  :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
}

: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")

     /interface ppp-client add name=$PppclientName dial-on-demand=no port=$ODUsbGPIOExtPort null-modem=yes disabled=yes
     :local GPIOanswer [/interface ppp-client at-chat $PppclientName input=("$cmd"."$2") as-value]
     :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={}
     }
# end	 
   :return $GPIOanswer
}

Тут следует отметить, что как и в непрограммируемой версии модуля, так и данная прошивка TOIC-версии позволяет считывать состояние IN-линий, но не позволяет считывать состояние линий OUT. Функциональность линий назначается скриптом один раз при запуске скрипта и я не знаю может ли меняться, но возможно считывать состояние линий можно только, когда они назначены, как входные. Тогда, чтобы считать состояние OUT-линии, её нужно временно переназначить, а это может изменить её состояние. Это уже вопрос к разработчику — можно ли вообще считывать состояние OUT-линий в его устройстве. Если аппаратно нет — это существенный минус.

Мои версии модифицированных прошивок USB GPIO EXTENDER T (TOIC) от 08.10.2024 г. под Микротик и коды моих функций OpnDevExtTOIC для Роутер ОС можно скачать по ссылке на GitHub.

С появлением модификации прошивок можно адекватно использовать устройство с Микротик, как отправляя команды на выполнение, так и получая ответы. В том числе можно считывать состояние IN-линий и др. в зависимости от версии прошивки.

Например, для версии прошивки 1 с использванием функции версии 2 на

:put ([$fOpenDevExtTOIC In 4]->"output")

получим в Терминале:

1
OK

Значит можно смело использовать команды и возвраты ответов от модуля в своих скриптах Микротик. Например, присоединив к USB GPIO EXTENDER T модуль радиоприемника 433 Мгц можно получить USB-радиопульт с управлением Микротик Роутер ОС для любых нужд, подобно как я это делал для Serial1-порта RBM33G. Теперь это стало возможным для любой RB Микротик с USB-портом.

Надо отметить, что без навыков программирования в Си++ или Питоне прошивки всё равно сложно модифицировать переопределяя пины под конкретные задачи. Разработчику следовало бы написать универсальную прошивку, в которой пользователь просто бы мог назначить пины устройства в пользовательских переменных без каких-либо правок исполняемого кода.

Так что пока за устройство поставим «5-», а за софт к нему «4».

Автор: Sertik13

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js