MikroTik integration #
MikroTik API library #
Creating api user #
On the MikroTik router go to System > Users.
Open the Groups tab and create a new group named api
with read
and api
access rights.
Open the Users tab and create a new user named api
and set the group to api
.
For improved security you specify LogicMachine IP in the Allowed Address list.
User library #
Go to Scripting > User libraries and create a new user library called mikrotik
.
Copy/paste the following code into the scripting editor.
local bnot, band, bor = bit.bnot, bit.band, bit.bor
local lshift, rshift = bit.lshift, bit.rshift
local char = string.char
local socket = require('socket')
local DEFAULT_PORT = 8728
local DEFAULT_TIMEOUT = 5
local function byte(int, idx)
local shifted = rshift(int, 8 * idx)
return band(shifted, 0xff)
end
local function parseword(word)
local _, pos = word:find('.=')
if not pos then
return '_type', word
end
local tag = word:sub(2, pos - 1)
local value = word:sub(pos + 1)
return tag, value
end
local function encodelength(l)
if l < 0x80 then
return char(l)
elseif l < 0x4000 then
local l = bor(l, 0x8000)
return
char(byte(l, 1)) ..
char(byte(l, 0))
elseif l < 0x200000 then
local l = bor(l, 0xC00000)
return
char(byte(l, 2)) ..
char(byte(l, 1)) ..
char(byte(l, 0))
elseif l < 0x10000000 then
local l = bor(l, 0xE0000000)
return
char(byte(l, 3)) ..
char(byte(l, 2)) ..
char(byte(l, 1)) ..
char(byte(l, 0))
else
return
'\xF0' ..
char(byte(l, 3)) ..
char(byte(l, 2)) ..
char(byte(l, 1)) ..
char(byte(l, 0))
end
end
local _M = {}
_M.__index = _M
function _M.new(host, port, timeout)
local mtk = {}
setmetatable(mtk, _M)
local sock = socket.tcp()
sock:settimeout(timeout or DEFAULT_TIMEOUT)
local res, err = sock:connect(host, port or DEFAULT_PORT)
if not res then
return nil, err
end
mtk.sock = sock
return mtk
end
function _M:close()
if self.sock then
self.sock:close()
self.sock = nil
end
end
function _M:readbyte()
local b = assert(self.sock:receive(1))
return b:byte(1)
end
function _M:readlength()
local l = self:readbyte()
if band(l, 0x80) == 0x00 then
return l
elseif band(l, 0xc0) == 0x80 then
l = band(l, bnot(0xc0))
return
lshift(l, 8) +
self:readbyte()
elseif band(l, 0xe0) == 0xc0 then
l = band(l, bnot(0xc0))
return
lshift(l, 16) +
lshift(self:readbyte(), 8) +
self:readbyte()
elseif band(l, 0xf0) == 0xe0 then
l = band(l, bnot(0xf0))
return
lshift(l, 24) +
lshift(self:readbyte(), 16) +
lshift(self:readbyte(), 8) +
self:readbyte()
elseif band(l, 0xf8) == 0xf0 then
return
lshift(self:readbyte(), 24) +
lshift(self:readbyte(), 16) +
lshift(self:readbyte(), 8) +
self:readbyte()
end
end
function _M:send(...)
local message = { ... }
for i, word in ipairs(message) do
message[ i ] = encodelength(#word) .. word
end
message[ #message + 1 ] = '\0'
return self.sock:send(table.concat(message))
end
function _M:readword()
local stat, len = pcall(self.readlength, self)
if not stat then
return nil, 'timeout'
end
if len == 0 then
return nil, 'done'
end
return self.sock:receive(len)
end
function _M:read()
local sentence = {}
while true do
local word, err = self:readword()
if not word then
if err == 'done' then
return sentence
else
return nil, err
end
end
local tag, value = parseword(word)
sentence[ tag ] = value
end
return sentence
end
function _M:readlist(...)
local res, err, list
res, err = self:send(...)
if not res then
return nil, err
end
list = {}
while true do
res, err = self:read()
if res then
if res._type == '!done' then
break
elseif res._type == '!trap' then
return nil, res.message or 'read failed'
else
list[ #list + 1 ] = res
end
else
return nil, err
end
end
return list
end
function _M:login(user, pass)
self:send('/login', '=name=' .. user, '=password=' .. pass)
local res, err = self:read()
if res then
if res._type == '!done' then
return true
else
return nil, res.message or 'login failed'
end
else
return nil, err
end
end
return _M
Example #
Presence detection of connected Wi-Fi clients.
Change ip
, user
, password
and macs
table as needed.
Create a resident or a scheduled script depending how often you want the status to update.
ip = '192.168.1.1'
user = 'api'
password = '123456'
macs = {
['00:11:22:33:44:55'] = '1/1/1',
['66:77:88:99:AA:BB'] = '1/1/2',
}
mt, err = require('user.mikrotik').new(ip)
if mt then
res, err = mt:login(user, password)
if res then
-- get wireless registration table
res, err = mt:readlist('/interface/wireless/registration-table/print')
regmacs = {}
for _, item in ipairs(res) do
mac = item['mac-address']
regmacs[ mac ] = true
end
for mac, addr in pairs(macs) do
found = regmacs[ mac ] == true
grp.checkupdate(addr, found)
end
else
log('login failed', tostring(err))
end
mt:close()
else
log('connect failed', tostring(err))
end
Further assistance can be found in this forum thread
Activate LogicMachine scene from MikroTik #
This example runs the I am home scene when the router detects that your phone is connected to the router WiFi.
MikroTik scripting and LogicMachine Remote services are used for this.
Check your WiFi device MAC #
Check your phone or your family member’s phone MAC addresses. In Android you can see it in Settings > About phone > Status . In iOS you can see it in Settings > General > About
Enable Remote service on LogicMachine #
Go to System config > Services > Remote services
WiFi client detection script on Mikrotik RouterOS #
Go to System > Scheduler and add the following script. It will check the existence of two MAC addresses and activate the group address 1/1/1 on LogicMachine with IP 192.168.1.13 (in this example login=remote, password=11111111). In case one of the WiFi devices is connected to the Mikrotik router, 1 will be sent to 1/1/1. If both are disconnected – 0 is sent to 1/1/1. You can also adjust the Scheduler interval, how often you want the script to check the WiFi registration table.
{
:local exists [/int wire reg find mac-address="78:02:F8:7E:96:01"];
:local exists2 [/int wire reg find mac-address="B0:E2:35:CF:46:48"];
:if ($exists!="" or $exists2!="") do={
/tool fetch url="http://remote:11111111@192.168.1.13/scada-remote" http-data="m=json&r=grp&fn=write&alias=1/1/1&value=1" http-method=post;
} else={
/tool fetch url="http://remote:11111111@192.168.1.13/scada-remote" http-data="m=json&r=grp&fn=write&alias=1/1/1&value=0" http-method=post;
}
}
Event-based script for scene group address #
Add event-based script for I am home group address 1/1/1. On each received telegram to this group address, the script will activate groups 1/1/2 and 1/1/3 if both of the following conditions are true:
-
if the previous value for 1/1/1 was false (the scene haven't already been run)
-
the scene has not been executed in the past 5 hours
value = event.getvalue()
prev_value = storage.get('prev_value')
if value then
if not prev_value then
off_time = storage.get('last_off_time', 0)
delta = os.time() - off_time
if delta > (5 * 60 * 60) then
grp.write('1/1/2', true)
grp.write('1/1/3', true)
end
end
else
storage.set('last_off_time', os.time())
end
storage.set('prev_value', value)