Merge branch 'master' of rvb.name:openhab-process
[openhab-process.git] / mqtt-bt / scan-beacons
1 #!/usr/bin/lua
2
3 json = require("json")
4 socket = require("socket")
5
6 function getConfig(configname)
7
8   local status,uci = pcall(require,"uci")
9
10   local config
11
12   if status then
13
14     if configname then
15       config=configname
16     else
17       config="beacon"
18     end
19
20     local cur=uci.cursor()
21
22     logging = cur.get(config,"logging","enabled") 
23
24     mqtt_host = cur.get(config,"mqtt","host")
25     mqtt_port = cur.get(config,"mqtt","port")
26     mqtt_id = cur.get(config,"mqtt","id")
27     mqtt_topic = cur.get(config,"mqtt","topic")
28   
29     mqtt_user = cur.get(config,"mqtt","user")
30     mqtt_passwd = cur.get(config,"mqtt","password")
31
32   else
33   
34     local status,ini = pcall(require,"ini")
35     if not status then
36       os.exit(1)
37     end 
38
39     if configname then
40       config=configname
41     else
42       config="/etc/beacon.ini"
43     end
44
45     local cur=ini.parse_file(config)
46     
47     logging = cur["logging"]["enabled"] 
48
49     mqtt_host = cur["mqtt"]["host"]
50     mqtt_port = cur["mqtt"]["port"]
51     mqtt_id = cur["mqtt"]["id"]
52     mqtt_topic = cur["mqtt"]["topic"]
53   
54     mqtt_user = cur["mqtt"]["user"]
55     mqtt_passwd = cur["mqtt"]["password"]
56   
57   end
58
59   hostname = socket.dns.gethostname()
60   if mqtt_host and not mqtt_id then
61     socket = require("socket")
62     posix = require("posix")
63     pid = posix.getpid()
64     mqtt_id="beaconmon-"..hostname.."-"..pid
65   end
66
67   if mqtt_host and not mqtt_port then
68     mqtt_port = 1883
69   end
70
71   if mqtt_host and not mqtt_topic then
72     mqtt_topic = 'beaconmon/{type}/{details}'
73   end
74
75 end
76
77 function capture(cmd, raw)
78   local f = assert(io.popen(cmd, 'r'))
79   local s = assert(f:read('*a'))
80   f:close()
81   if raw then return s end
82   s = string.gsub(s, '^%s+', '')
83   s = string.gsub(s, '%s+$', '')
84   s = string.gsub(s, '[\n\r]+', ' ')
85   return s
86 end
87
88 function mqtt_encode(str)
89   if (str) then
90     str = string.gsub (str, "\n", "")
91     str = string.gsub (str, ":", "-")
92     str = string.gsub (str, "/", "-")
93     str = string.gsub (str, " ", "_")
94   end
95   return str    
96 end
97
98 function printLog(str)
99   if logging=="yes" then
100     capture("logger -t beaconmon \""..str.."\"")
101     print(str)  
102   elseif logging=="syslog" then
103     capture("logger -t beaconmon \""..str.."\"")
104   elseif logging=="stdout" then 
105     print(str)  
106   end 
107 end
108
109 function run_command(cmd)
110
111   local file = assert(io.popen(cmd, 'r'))
112   local output = file:read('*all')
113   file:close()
114
115 end
116
117 function open_dump()
118
119   run_command("/usr/bin/pkill btmon")
120   run_command("/usr/bin/pkill hcitool")
121   f = assert(io.popen ("/usr/bin/stdbuf -o0 /usr/bin/btmon"))
122   run_command("hciconfig hci0 down")
123   run_command("hciconfig hci0 up")
124   f_null = assert(io.popen ("hcitool lescan --duplicates --passive"))
125
126   return f
127
128 end
129
130 function dump(o)
131   return json.encode(o)
132 end
133
134 function trim(s)
135   return (s:gsub("^%s*(.-)%s*$", "%1"))
136 end
137
138 local function starts_with(str, start)
139    return str:sub(1, #start) == start
140 end
141
142 function mqtt_pub(path,value)
143   res=mqtt_client:publish(path,value)
144   printLog("Pub "..path.." returned "..res);
145   return res
146 end
147
148 function process_packet(packet)
149
150   local mac
151   local uuid
152   local details
153   local type
154   local name
155
156   packet['origin'] = hostname
157
158   mac = packet['Address']
159   uuid = packet['UUID']
160   type = packet['Type']
161   name = packet['Name (complete)']
162
163   if type and starts_with(type,'iBeacon') then
164     details=uuid
165   elseif name then
166     if not(type) then
167       type="name"
168     end  
169     details=name
170   else
171     if not type then 
172       type='unknown'
173     end  
174     details=mac
175   end
176
177   if not (type=="unknown") then
178     mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
179       function (name) 
180         if name=="type" then
181           return mqtt_encode(type)
182         elseif name=="details" then
183           return mqtt_encode(details)
184         else
185           return '{'..name..'}'
186         end      
187       end)
188         
189     if not pcall(mqtt_pub,mqtt_path,dump(packet)) then
190       printLog('Reconnecting MQTT...')
191       mqtt_client:connect(mqtt_id)
192     end
193
194   end
195
196 end
197
198 function starts(String,Start)
199    return string.sub(String,1,string.len(Start))==Start
200 end
201
202 function split(inputstr, sep)
203         if sep == nil then
204                 sep = "%s"
205         end
206         local t={} ; i=1
207         for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
208                 t[i] = str
209                 i = i + 1
210         end
211         return t
212 end
213
214 function read_loop()
215
216   packet={}
217   inbound=false
218
219   while true do
220
221     str=inp:read("*l")
222
223     if str then 
224
225       str=trim(str)
226
227       if inbound then
228         t = split(str,':')
229         if #t>=2 then
230           name=t[1]
231           value=trim(table.concat(t,':',2))
232           if name=="Address" then
233             value=split(value)[1]
234           end
235           packet[name]=value
236         elseif #t==1 and name then
237           if not(packet[name..'.list']) then
238             packet[name..'.list']={}
239           end  
240           table.insert(packet[name..'.list'],(trim(t[1])))
241         end  
242       end  
243
244       if starts(str,'> HCI Event: LE Meta Event (0x3e)') then
245         inbound=true
246         name=nil
247       elseif starts(str,'RSSI:') then
248         inbound=false
249         process_packet(packet)
250         packet={}
251       end
252       
253     end  
254     
255   end
256
257 end
258
259 io.stdout:setvbuf('no')
260 io.stdin:setvbuf('no') 
261
262 getConfig(arg[1])
263
264 if mqtt_host then
265   MQTT = require "mosquitto"
266   mqtt_client = MQTT.new(mqtt_id)
267   if mqtt_user then
268     mqtt_client:login_set(mqtt_user, mqtt_passwd)
269   end
270   mqtt_client:connect(mqtt_host,mqtt_port)
271 end
272
273 inp = open_dump()
274
275 read_loop()