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