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