Сбор данных GSM сети с MikroTik RB951G-2HND

By | 2017-01-04

Маршрутизаторы MikroTik RB951G-2HND обладают портом USB, к которому можно подключить 3G/4G-модем для подключения к Интернет.
С USB-модемом, как и с любым другим, можно работать при помощи AT-команд. GSM-модемы, как правило, поддерживают команды получения данных о сети.
Эта заметка о нюансах работы с AT-командами на маршрутизаторах MikroTik.

На MikroTik, доступ к модему осуществляется командой /system serial-terminal channel=chnum, работа с которой в чём-то похожа на программу cu. Но фатальным недостатком данного метода, является то, что работа возможна только в интерактивном режиме, что не способствует автоматизации.

Собираем инфу для работы
Если в настройках PPP-соеденения нажать «Advanced Mode», то можно увидеть поля Data Channel и Info Channel. Это каналы модема отвечающие за, соответственно, передаваемые данные и информацию о статусе. Если подключиться к каналу информации при помощи команды /system serial-terminal usb1 channel=3, то на экран будет бесконечно сыпаться техническая информация о текущем статусе модема вот какого, малоинформативного вида:

^DSFLOWRPT:00001D5C,00000362,000002A0,00000000000A83FC,0000000000050247,00000000,00000000
^DSFLOWRPT:00001D5E,00000308,00000278,00000000000A8A0C,0000000000050737,00000000,00000000
^DSFLOWRPT:00001D60,00000256,00000180,00000000000A8EB9,0000000000050A37,00000000,00000000
^DSFLOWRPT:00001D62,00000000,0000004E,00000000000A8EB9,0000000000050AD4,00000000,00000000

Выполним команду /interface ppp-client info ppp-out1
Вывод будет следующим:

       modem-status: ready
         pin-status: no password required
      functionality: full
       manufacturer:
              model: E3131
           revision: 21.158.13.03.143
      serial-number: XXXXXXXXXXXXXXX
   current-operator: Beeline (cellid XXXX)
  access-technology: 3G
     signal-strengh: -83 dBm
   frame-error-rate: n/a

Команда опять же интерактивна и передать данные в скрипт не позволяет.
Не сдаёмся и выполним /interface ppp-client info ppp-out1 do={ :global modemsn $»serial-number»;}
Команда запустилась в бесконечном цикле и создала глобальную переменную $modemsn, в которую положила значение, идущее после serial-number:. После завершения команды по Ctrl-C переменная остаётся.
Также можно заметить, что команда позволяет передать пользовательские данные через параметр user-command, правда разметка вывода несколько поедет, но это не беда.
Открываем справочник AT-команд и ищем те, которые нас интересуют, а именно:

      AT+CIMI — информация об IMSI номере SIM карты
      AT+CREG? — тип регистрации сети
      AT^ICCID? — информация о серийном номере SIM карты

Чтобы команда AT+CREG выдавала инфу о базовых станциях, предварительно нужно выполнить AT+CREG=2. Выполняем /interface ppp-client info ppp-out1 user-command=»AT+CIMI\r\nAT+CREG\?\r\nAT^ICCID\?» do={ :global imsi $»manufacturer»; :global cell $»model»; :global iccid $»revision»; :global signal $»signal-strengh» }, что даст следующий вывод:

         pin-status: no password required
      functionality: full
       manufacturer: 25099XXXXXXXXXX
              model: +CREG: 2,1,"XXXX","XXXX"
           revision: ^ICCID: XXXXXXXXXXXXXXXXXXXX
      serial-number:
   current-operator: Beeline (cellid XXXX)
  access-technology: 3G
     signal-strengh: -85 dBm
   frame-error-rate: n/a
       user-command: AT+CIMI

а в соответствующие переменные упадут нужные данные, различной степени сырости.
Теперь, вооружившись полученными знаниями, напишем скрипты, которые будут получать данные за нас.

Скрипты
Т.к. у разных модемов, номер канала данных может различаться, то нам нужен скрипт, который будет выполняться при старте маршрутизатора и производить инициализацию ppp-соединения.

/system scheduler
add interval=20s name=start-up on-event=start-up policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive start-date=jan/01/1970 start-time=00:00:00
/system script
add name=start-up owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive source=":global modemsn\
    \n:global d\
    \n:if ([/system scheduler get start-up interval] != \"00:00:00\") do={/system scheduler set start-up start-time=startup interval=\"00:00:00\"} \
    \n:delay 20\
    \n:foreach i in=[/interface ppp-client find] do={\
    \n if ([/interface ppp-client get \$i value-name=disabled] = true) do={\
    \n  /interface ppp-client set \$i modem-init=\"AT+CREG=2\"\
    \n  :set \$chcnt ([/port get [/port find where name~[/interface ppp-client get \$i value-name=port]] value-name=channels]-1)\
    \n  while (\$chcnt!=0) do={\
    \n\t/interface ppp-client set \$i info-channel=\$chcnt\
    \n\t:set \$d \$i\
    \n\t:local j [execute {/interface ppp-client info \$d do={ :global modemsn \$\"serial-number\";}}]\
    \n\t:delay 5\
    \n\t:do { /system script job remove \$j } on-error={}\
    \n\tif ([:len \$modemsn]!=0) do={\
    \n\t  /interface ppp-client set \$i info-channel=\$chcnt\
    \n\t  :set \$chcnt 0\
    \n\t }\
    \n   }\
    \n  /interface ppp-client set \$i disabled=no\
    \n }\
    \n}\
    \n:set \$d\
    \n:set \$modemsn"

По непонятной мне причине, при первом старте маршрутизатора после сброса, правило планировщика, помеченного как start-up, не срабатывает, хотя при последующих запусках отрабатывает как положено. В связи с этим, добавляем в шедулер правило c интервалом 20 сек., которое вызовет скрипт, устанавливающий этому правилу интервал в start-up, в результате, при первом старте скрипт выполнится по таймеру, а во все последующие по событию start-up. Попутно, скрипт проходит все соединения и для каждого получает количество каналов модема, по очереди, от 1 до MAX_CHANNELS, ставит в качестве канала информации в ppp-соединении, пытается получить данные и если данные получены, то оставляет текущий канал и разрешает это соединение. В качестве строки инициализации устанавливает AT+CREG=2 (см. выше зачем).

Теперь можно собирать данные.
Добавим задание в планировщик, которое будет выполнять скрипт раз в час.

/system scheduler
add interval=1h name=netinfo on-event=netinfo policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-date=dec/22/2016 start-time=07:09:46

/system script
add name=netinfo owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=":global imsi\
    \n:global cell\
    \n:global iccid\
    \n:global signal\
    \n:global lac\
    \n\
    \n:local serial [/system routerboard get value-name=\"serial-number\"]\
    \n:local osver [:pick [/system resource get value-name=version] 0 [:find [/system resource get value-name=version] \" \"]]\
    \n:local url \"myserver_ip:port/cells.php\"\
    \n:local user \"username\"\
    \n:local pass \"password\"\
    \n\
    \n:local j [execute {/interface ppp-client info ppp-out1 user-command=\"AT+CIMI\\r\\nAT+CREG\\\?\\r\\nAT^ICCID\\\?\" do={ :global imsi \$\"manufacturer\"; :global\
    \_cell \$\"model\"; :global iccid \$\"revision\"; :global signal \$\"signal-strengh\" }}]\
    \n:delay 10\
    \n\
    \n:do { /system script job remove \$j } on-error={}\
    \n\
    \nset \$signal [pick \$signal 0 [:find \$signal \" \" -1]]\
    \nset \$iccid [pick \$iccid ([:find \$iccid \" \" -1]+1) [:len \$iccid]]\
    \nset \$cell [:pick \$cell ([:find \$cell \"\\\"\"]+1) [:len \$cell]]\
    \n\
    \n:set \$lac [:pick \$cell 0 [find \$cell \"\\\"\"]]\
    \n:set \$cell [pick \$cell ([:find \$cell \",\"]+2) ([:len \$cell]-1)]\
    \n\
    \n/tool fetch mode=https keep-result=no user=\$user password=\$pass [:put (\"https://\$url\\\?serial=\$serial&imsi=\$imsi&signal=\$signal&iccid=\$iccid&lac=\$lac&\
    cell=\$cell\")]"

Скрипт создает фоновое задание с командой /interface ppp-client info …, которая, как мы помним, является интерактивной, ждёт 10 секунд, завершает задание, парсит полученные данные и выполняет запрос к серверу, в котором передаёт параметры.

Сервер
Ставим web-сервер, поддержку cgi и СУБД. В моём случае это nginx, PHP и SQLite. Также рекомендую поднять на сервере SLL, хотя бы самоподписанный — 2017 год как-никак, безопасность и всё такое…
На web-сервере кладём файл cellstat.php следующего содержания:

<?php
$imsi=$_GET['imsi'];
$signal=$_GET['signal'];
$iccid=$_GET['iccid'];
$lac=hexdec($_GET['lac']);
$cell=hexdec($_GET['cell']);
$dbname="cells.sqlite";
try { $db=new SQLite3($dbname,SQLITE3_OPEN_READWRITE); }
catch (Exception $exception)
 {
  header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  exit();
 }

if(!empty($imsi) && !empty($signal) && !empty($iccid) && !empty($lac) && !empty($cell)) //Additing signal strength and BS info to DB
 {
  $time=time();
  $results=$db->query("SELECT * FROM cellstat WHERE id=(SELECT MAX(id) FROM cellstat WHERE serial='$serial')"); //Select last data from DB
  $cellstat=$results->fetchArray();
  if($cellstat['imsi']==$imsi && $cellstat['iccid']==$iccid && $cellstat['lac']==$lac && $cellstat['cell']==$cell && $cellstat['sig']==$signal) //If new data equals old update record uptade date
   {
    $results=$db->query("UPDATE cellstat SET date=$time WHERE id=".$cellstat['id']);
   }
  else $results=$db->query("INSERT INTO cellstat VALUES(NULL,$time,'$serial','$imsi',$signal,'$iccid',$lac,$cell)");
  $results->finalize();
  $db->close();
  header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  exit();
 }
?>

Находясь в папке со скриптом, выполним sqlite cellstat.sqlite и создадим таблицу, в которую будут складываться данные и не забудем назначить права на запись пользователю www:

CREATE TABLE cellstat(
        id INTEGER PRIMARY KEY,
        date INTEGER,
        serial VARCHAR,
        imsi VARCHAR,
        sig INTEGER,
        iccid VARCHAR,
        lac INTEGER,
        cell INTEGER
);

Скрипт выбирает последнюю запись с переданным серийным номером маршрутизатора и если новые данные совпадают со старыми, то обновляет время записи, дабы не раздувать базу, иначе добавляет новую запись.

Имея данные о базовых станций и уровне сигнала, можно получить координаты устройства через API геолокации Яндекса или Google. К стати, в моём случае Яндекс отрабатывал лучше, да и отечественный производитель опять же, что улучшает карму. И это вполне себе работает в местах более-менее близких от населённых пунктов. Где-нибудь по среди тайги, в гефтегазоносных местах нашей необъятной Родины, конечно же — нет, но как правило координаты этих мест обычно сообщаются при заезде и ничего не мешает не получать, а отсылать данные о БСках и уровне сигнала, дабы внести свой вклад в гоолокацию, за что + в карму, опять-таки.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *