d6d0d26f0006f64c68ca5a2afe6561c39a08b607
[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   f = assert(io.popen ("/usr/bin/btmon"))
120   run_command("hciconfig hci0 down")
121   run_command("hciconfig hci0 up")
122   f_null = assert(io.popen ("hcitool lescan --duplicates"))
123
124   return f
125
126 end
127
128 function dump(o)
129   return json.encode(o)
130 end
131
132 function trim(s)
133   return (s:gsub("^%s*(.-)%s*$", "%1"))
134 end
135
136 local function starts_with(str, start)
137    return str:sub(1, #start) == start
138 end
139
140 function mqtt_pub(path,value)
141   res=mqtt_client:publish(path,value)
142   printLog("Pub "..path.." returned "..res);
143   return res
144 end
145
146 function process_packet(packet)
147
148   local mac
149   local uuid
150   local details
151   local type
152   local name
153
154   packet['origin'] = hostname
155
156   mac = packet['Address']
157   uuid = packet['UUID']
158   type = packet['Type']
159   name = packet['Name (complete)']
160
161   if type and starts_with(type,'iBeacon') then
162     details=uuid
163   elseif name then
164     if not(type) then
165       type="name"
166     end  
167     details=name
168   else
169     if not type then 
170       type='unknown'
171     end  
172     details=mac
173   end
174
175   if not (type=="unknown") then
176     mqtt_path=string.gsub(mqtt_topic,"{(.-)}",
177       function (name) 
178         if name=="type" then
179           return mqtt_encode(type)
180         elseif name=="details" then
181           return mqtt_encode(details)
182         else
183           return '{'..name..'}'
184         end      
185       end)
186         
187     if not pcall(mqtt_pub,mqtt_path,dump(packet)) then
188       printLog('Reconnecting MQTT...')
189       mqtt_client:connect(mqtt_id)
190     end
191
192   end
193
194 end
195
196 function starts(String,Start)
197    return string.sub(String,1,string.len(Start))==Start
198 end
199
200 function split(inputstr, sep)
201         if sep == nil then
202                 sep = "%s"
203         end
204         local t={} ; i=1
205         for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
206                 t[i] = str
207                 i = i + 1
208         end
209         return t
210 end
211
212 function read_loop()
213
214   packet={}
215   inbound=false
216
217   while true do
218
219     str=inp:read("*l")
220
221     if str then 
222
223       str=trim(str)
224
225       if inbound then
226         t = split(str,':')
227         if #t>=2 then
228           name=t[1]
229           value=trim(table.concat(t,':',2))
230           if name=="Address" then
231             value=split(value)[1]
232           end
233           packet[name]=value
234         elseif #t==1 and name then
235           if not(packet[name..'.list']) then
236             packet[name..'.list']={}
237           end  
238           table.insert(packet[name..'.list'],(trim(t[1])))
239         end  
240       end  
241
242       if starts(str,'> HCI Event: LE Meta Event (0x3e)') then
243         inbound=true
244         name=nil
245       elseif starts(str,'RSSI:') then
246         inbound=false
247         process_packet(packet)
248         packet={}
249       end
250       
251     end  
252     
253   end
254
255 end
256
257 io.stdout:setvbuf('no')
258 io.stdin:setvbuf('no') 
259
260 getConfig(arg[1])
261
262 if mqtt_host then
263   MQTT = require "mosquitto"
264   mqtt_client = MQTT.new(mqtt_id)
265   if mqtt_user then
266     mqtt_client:login_set(mqtt_user, mqtt_passwd)
267   end
268   mqtt_client:connect(mqtt_host,mqtt_port)
269 end
270
271 inp = open_dump()
272
273 read_loop()