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)