Timeout handling fixed.
[openhab-process.git] / mqtt-bt / scan-beacons
1 #!/usr/bin/lua
2
3 function getConfig(configname)
4
5   local uci=require("uci")
6   local cur=uci.cursor()
7   local config
8   if configname then
9     config=configname
10   else
11     config="beacon"
12   end
13
14   logging = cur.get(config,"logging","enabled") 
15
16   mqtt_host = cur.get(config,"mqtt","host")
17   mqtt_port = cur.get(config,"mqtt","port")
18   mqtt_id = cur.get(config,"mqtt","id")
19   mqtt_topic = cur.get(config,"mqtt","topic")
20
21   mqtt_user = cur.get(config,"mqtt","user")
22   mqtt_passwd = cur.get(config,"mqtt","password")
23
24   if mqtt_host and not mqtt_id then
25     mqtt_id="beaconmon"
26   end
27
28   if mqtt_host and not mqtt_port then
29     mqtt_port = 1883
30   end
31
32   if mqtt_host and not mqtt_topic then
33     mqtt_topic = 'beaconmon/{type}/{details}'
34   end
35
36 end
37
38 function capture(cmd, raw)
39   local f = assert(io.popen(cmd, 'r'))
40   local s = assert(f:read('*a'))
41   f:close()
42   if raw then return s end
43   s = string.gsub(s, '^%s+', '')
44   s = string.gsub(s, '%s+$', '')
45   s = string.gsub(s, '[\n\r]+', ' ')
46   return s
47 end
48
49 function mqtt_encode(str)
50   if (str) then
51     str = string.gsub (str, "\n", "")
52     str = string.gsub (str, "/", "-")
53   end
54   return str    
55 end
56
57 function printLog(str)
58   if logging=="on" then
59     capture("logger -t beaconmon "..str)
60   else 
61     print(str)  
62   end 
63 end
64
65 function run_command(cmd)
66
67   local file = assert(io.popen(cmd, 'r'))
68   local output = file:read('*all')
69   file:close()
70
71 end
72
73 function open_dump()
74
75   f = assert(io.popen ("hcidump --raw"))
76   run_command("kill `pgrep hcitool`")
77   run_command("hciconfig hci0 down")
78   run_command("hciconfig hci0 up")
79   f_null = assert(io.popen ("hcitool lescan --duplicates"))
80
81   return f
82
83 end
84
85 function dump(o)
86    if type(o) == 'table' then
87       local s = '{ '
88       for k,v in pairs(o) do
89          if type(k) ~= 'number' then k = '"'..k..'"' end
90          s = s .. '['..k..'] = ' .. dump(v) .. ','
91 --         s = s .. dump(v) .. ','
92       end
93       return s .. '} '
94    else
95       return tostring(o)
96    end
97 end
98
99 function trim(s)
100   return (s:gsub("^%s*(.-)%s*$", "%1"))
101 end
102
103 function mqtt_pub(path,value)
104   printLog("Pub "..path.." "..value)
105   return mqtt_client:publish(path,value)
106 end
107
108 function process_packet(packet)
109
110   local bytes={}
111   local idx=1
112   local len
113   local mac
114   local flags
115   local power
116   local tx
117   local type
118   local paysublen
119   local uuid
120   local major
121   local minor
122   local details
123
124   if packet:len()>1 then
125
126     while packet:len()>2 do
127       bytes[idx]=trim(packet:sub(1,3))
128       idx=idx+1
129       packet=packet:sub(4)
130     end
131     len = idx-1
132
133     if bytes[1]=='04' and bytes[2]=='3E' then
134       -- BLE Beacon?
135       type=""
136       mac=bytes[13]..':'..bytes[12]..':'..bytes[11]..':'..bytes[10]..':'..bytes[9]..':'..bytes[8]
137       flags=bytes[14]
138       power=tonumber("0x"..bytes[len-1])-256
139       tx=tonumber("0x"..bytes[len])-256
140       local j = 15
141       while j<len-2 do
142         paysublen=tonumber('0x'..bytes[j])
143         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
144           -- Standard UUID iBeacon
145           type="ibeacon"
146           local uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
147           local uuid2=bytes[j+10]..bytes[j+11]
148           local uuid3=bytes[j+12]..bytes[j+13]
149           local uuid4=bytes[j+14]..bytes[j+15]
150           local uuid5=bytes[j+16]..bytes[j+17]..bytes[j+18]..bytes[j+19]..bytes[j+20]..bytes[j+21]
151           uuid=string.lower(uuid1..'-'..uuid2..'-'..uuid3..'-'..uuid4..'-'..uuid5)
152           major=bytes[j+23]..bytes[j+22]
153           minor=bytes[j+25]..bytes[j+24]
154         end
155         j=j+1+paysublen
156       end
157       if type=="ibeacon" then
158         printLog(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
159         details=uuid..'/'..major..'/'..minor
160       else
161         type='unknown'
162         details=dump(bytes)
163 --        printLog(details)
164       end
165       if not (type=="unknown") then
166         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
167           function (name) 
168             if name=="type" then
169               return mqtt_encode(type)
170             elseif name=="details" then
171               return mqtt_encode(details)
172             else
173               return '{'..name..'}'
174             end      
175           end)
176         
177         if not pcall(mqtt_pub,mqtt_path,tx) then
178           printLog('Reconnecting MQTT...')
179           mqtt_client:connect(mqtt_id)
180         end
181
182       end        
183     end
184   end
185
186 end
187
188 function read_loop()
189
190   packet = ""
191
192   for line in inp:lines() do
193
194     lchr=line:sub(1,1)
195     line=trim(line)
196
197     if lchr=="<" or lchr==">" then
198       line = trim(line:sub(2))
199     end
200
201     if not (lchr == " ") then
202       process_packet(packet)
203       packet = ""
204     end
205
206     llen=line:len()
207
208     if llen<59 then
209       packet = packet .. " " .. line
210       process_packet(packet)
211       packet=""
212     else
213       packet = packet .. " " .. line
214     end    
215     
216   end
217
218 end
219
220 getConfig(arg[1])
221
222 if mqtt_host then
223   MQTT = require "paho.mqtt"
224   mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
225   if mqtt_user then
226     mqtt_client:auth(mqtt_user, mqtt_passwd)
227   end
228   mqtt_client:connect(mqtt_id)
229 end
230
231 inp = open_dump()
232
233 read_loop()