Избегаем "залипания" последнего сообщения в MQTT.
[openhab-process.git] / mqtt-bt / scan-beacons
index 98c39937d8efedb096c89c9439ef68a29c297ba1..5512aa9a009aa3c03531ba19c217c41687e0e5a3 100644 (file)
@@ -1,28 +1,67 @@
-#!/usr/bin/env lua
+#!/usr/bin/lua
+
+json = require("json")
+socket = require("socket")
 
 function getConfig(configname)
 
 
 function getConfig(configname)
 
-  local uci=require("uci")
-  local cur=uci.cursor()
+  local status,uci = pcall(require,"uci")
+
   local config
   local config
-  if configname then
-    config=configname
-  else
-    config="beacon"
-  end
 
 
-  logging = cur.get(config,"logging","enabled") 
+  if status then
 
 
-  mqtt_host = cur.get(config,"mqtt","host")
-  mqtt_port = cur.get(config,"mqtt","port")
-  mqtt_id = cur.get(config,"mqtt","id")
-  mqtt_topic = cur.get(config,"mqtt","topic")
+    if configname then
+      config=configname
+    else
+      config="beacon"
+    end
+
+    local cur=uci.cursor()
+
+    logging = cur.get(config,"logging","enabled") 
+
+    mqtt_host = cur.get(config,"mqtt","host")
+    mqtt_port = cur.get(config,"mqtt","port")
+    mqtt_id = cur.get(config,"mqtt","id")
+    mqtt_topic = cur.get(config,"mqtt","topic")
+  
+    mqtt_user = cur.get(config,"mqtt","user")
+    mqtt_passwd = cur.get(config,"mqtt","password")
+
+  else
+  
+    local status,ini = pcall(require,"ini")
+    if not status then
+      os.exit(1)
+    end 
+
+    if configname then
+      config=configname
+    else
+      config="/etc/beacon.ini"
+    end
 
 
-  mqtt_user = cur.get(config,"mqtt","user")
-  mqtt_passwd = cur.get(config,"mqtt","password")
+    local cur=ini.parse_file(config)
+    
+    logging = cur["logging"]["enabled"] 
+
+    mqtt_host = cur["mqtt"]["host"]
+    mqtt_port = cur["mqtt"]["port"]
+    mqtt_id = cur["mqtt"]["id"]
+    mqtt_topic = cur["mqtt"]["topic"]
+  
+    mqtt_user = cur["mqtt"]["user"]
+    mqtt_passwd = cur["mqtt"]["password"]
+  
+  end
 
 
+  hostname = socket.dns.gethostname()
   if mqtt_host and not mqtt_id then
   if mqtt_host and not mqtt_id then
-    mqtt_id="beaconmon"
+    socket = require("socket")
+    posix = require("posix")
+    pid = posix.getpid()
+    mqtt_id="beaconmon-"..hostname.."-"..pid
   end
 
   if mqtt_host and not mqtt_port then
   end
 
   if mqtt_host and not mqtt_port then
@@ -49,14 +88,16 @@ end
 function mqtt_encode(str)
   if (str) then
     str = string.gsub (str, "\n", "")
 function mqtt_encode(str)
   if (str) then
     str = string.gsub (str, "\n", "")
+    str = string.gsub (str, ":", "-")
     str = string.gsub (str, "/", "-")
     str = string.gsub (str, "/", "-")
+    str = string.gsub (str, " ", "_")
   end
   return str   
 end
 
 function printLog(str)
   end
   return str   
 end
 
 function printLog(str)
-  if logging=="on" then
-    capture("logger -t beaconmon "..str)
+  if logging=="yes" then
+    capture("logger -t beaconmon \""..str.."\"")
   else 
     print(str)  
   end 
   else 
     print(str)  
   end 
@@ -72,8 +113,7 @@ end
 
 function open_dump()
 
 
 function open_dump()
 
-  f = assert(io.popen ("hcidump --raw"))
-  run_command("kill `pgrep hcitool`")
+  f = assert(io.popen ("/usr/bin/btmon"))
   run_command("hciconfig hci0 down")
   run_command("hciconfig hci0 up")
   f_null = assert(io.popen ("hcitool lescan --duplicates"))
   run_command("hciconfig hci0 down")
   run_command("hciconfig hci0 up")
   f_null = assert(io.popen ("hcitool lescan --duplicates"))
@@ -83,140 +123,148 @@ function open_dump()
 end
 
 function dump(o)
 end
 
 function dump(o)
-   if type(o) == 'table' then
-      local s = '{ '
-      for k,v in pairs(o) do
-         if type(k) ~= 'number' then k = '"'..k..'"' end
---         s = s .. '['..k..'] = ' .. dump(v) .. ','
-         s = s .. dump(v) .. ','
-      end
-      return s .. '} '
-   else
-      return tostring(o)
-   end
+  return json.encode(o)
 end
 
 function trim(s)
   return (s:gsub("^%s*(.-)%s*$", "%1"))
 end
 
 end
 
 function trim(s)
   return (s:gsub("^%s*(.-)%s*$", "%1"))
 end
 
+local function starts_with(str, start)
+   return str:sub(1, #start) == start
+end
+
+function mqtt_pub(path,value)
+  res=mqtt_client:publish(path,value,0,false)
+  printLog("Pub "..path.." returned "..res);
+  return res
+end
+
 function process_packet(packet)
 
 function process_packet(packet)
 
-  local bytes={}
-  local idx=1
-  local len
   local mac
   local mac
-  local flags
-  local power
-  local tx
-  local type
-  local paysublen
   local uuid
   local uuid
-  local major
-  local minor
   local details
   local details
+  local type
+  local name
+
+  packet['origin'] = hostname
+
+  mac = packet['Address']
+  uuid = packet['UUID']
+  type = packet['Type']
+  name = packet['Name (complete)']
+
+  if type and starts_with(type,'iBeacon') then
+    details=uuid
+  elseif name then
+    if not(type) then
+      type="name"
+    end  
+    details=name
+  else
+    if not type then 
+      type='unknown'
+    end  
+    details=mac
+  end
 
 
-  if packet:len()>1 then
-
-    while packet:len()>2 do
-      bytes[idx]=trim(packet:sub(1,3))
-      idx=idx+1
-      packet=packet:sub(4)
-    end
-    len = idx-1
-
-    if bytes[1]=='04' and bytes[2]=='3E' then
-      -- BLE Beacon?
-      type=""
-      mac=bytes[13]..':'..bytes[12]..':'..bytes[11]..':'..bytes[10]..':'..bytes[9]..':'..bytes[8]
-      flags=bytes[14]
-      power=tonumber("0x"..bytes[len-1])-256
-      tx=tonumber("0x"..bytes[len])-256
-      local j = 15
-      while j<len-2 do
-        paysublen=tonumber('0x'..bytes[j])
-       if bytes[j+1]=="FF" and bytes[j+2]=="4C" and bytes[j+3]=="00" and bytes[j+4]=="02" and bytes[j+5]=="15" then
-         -- Standard UUID iBeacon
-          type="ibeacon"
-         local uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
-         local uuid2=bytes[j+10]..bytes[j+11]
-         local uuid3=bytes[j+12]..bytes[j+13]
-         local uuid4=bytes[j+14]..bytes[j+15]
-         local uuid5=bytes[j+16]..bytes[j+17]..bytes[j+18]..bytes[j+19]..bytes[j+20]..bytes[j+21]
-         uuid=string.lower(uuid1..'-'..uuid2..'-'..uuid3..'-'..uuid4..'-'..uuid5)
-          major=bytes[j+23]..bytes[j+22]
-          minor=bytes[j+25]..bytes[j+24]
-       end
-        j=j+1+paysublen
-      end
-      if type=="ibeacon" then
-        printLog(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
-        details=uuid..'/'..major..'/'..minor..'/'
-      else
-        type='unknown'
-        details=dump(bytes)
-        printLog(details)
-      end
-      if mqtt_client then
-        mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
-          function (name) 
-            if name=="type" then
-              return mqtt_encode(type)
-            elseif name=="details" then
-              return mqtt_encode(details)
-            else
-              return '{'..name..'}'
-            end      
-          end)
-        mqtt_client:publish(mqtt_path,tx)
-      end        
+  if not (type=="unknown") then
+    mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
+      function (name) 
+        if name=="type" then
+          return mqtt_encode(type)
+        elseif name=="details" then
+          return mqtt_encode(details)
+        else
+          return '{'..name..'}'
+        end      
+      end)
+        
+    if not pcall(mqtt_pub,mqtt_path,dump(packet)) then
+      printLog('Reconnecting MQTT...')
+      mqtt_client:connect(mqtt_host,mqtt_port)
     end
     end
+
   end
 
 end
 
   end
 
 end
 
-function read_loop()
-
-  packet = ""
-
-  for line in inp:lines() do
-
-    lchr=line:sub(1,1)
-    line=trim(line)
-
-    if lchr=="<" or lchr==">" then
-      line = trim(line:sub(2))
-    end
+function starts(String,Start)
+   return string.sub(String,1,string.len(Start))==Start
+end
 
 
-    if not (lchr == " ") then
-      process_packet(packet)
-      packet = ""
-    end
+function split(inputstr, sep)
+        if sep == nil then
+                sep = "%s"
+        end
+        local t={} ; i=1
+        for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
+                t[i] = str
+                i = i + 1
+        end
+        return t
+end
 
 
-    llen=line:len()
+function read_loop()
 
 
-    if llen<59 then
-      packet = packet .. " " .. line
-      process_packet(packet)
-      packet=""
-    else
-      packet = packet .. " " .. line
-    end    
+  packet={}
+  inbound=false
+
+  while true do
+
+    str=inp:read("*l")
+
+    if str then 
+
+      str=trim(str)
+
+      if inbound then
+        t = split(str,':')
+        if #t>=2 then
+          name=t[1]
+          value=trim(table.concat(t,':',2))
+          if name=="Address" then
+            value=split(value)[1]
+          end
+          packet[name]=value
+        elseif #t==1 and name then
+          if not(packet[name..'.list']) then
+            packet[name..'.list']={}
+          end  
+          table.insert(packet[name..'.list'],(trim(t[1])))
+        end  
+      end  
+
+      if starts(str,'> HCI Event: LE Meta Event (0x3e)') then
+        inbound=true
+        name=nil
+      elseif starts(str,'RSSI:') then
+        inbound=false
+        process_packet(packet)
+        packet={}
+      end
+      
+    end  
     
   end
 
 end
 
     
   end
 
 end
 
+io.stdout:setvbuf('no')
+io.stdin:setvbuf('no') 
+
 getConfig(arg[1])
 
 if mqtt_host then
 getConfig(arg[1])
 
 if mqtt_host then
-  MQTT = require "paho.mqtt"
-  mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
+  MQTT = require "mosquitto"
+  mqtt_client = MQTT.new(mqtt_id)
   if mqtt_user then
   if mqtt_user then
-    mqtt_client:auth(mqtt_user, mqtt_passwd)
+    mqtt_client:login_set(mqtt_user, mqtt_passwd)
   end
   end
-  mqtt_client:connect(mqtt_id)
+  mqtt_client:connect(mqtt_host,mqtt_port)
 end
 
 inp = open_dump()
 end
 
 inp = open_dump()
+
 read_loop()
 read_loop()