3c6a230b2e4151ee46887173fe9560672c359f5f
[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 process_packet(packet)
104
105   local bytes={}
106   local idx=1
107   local len
108   local mac
109   local flags
110   local power
111   local tx
112   local type
113   local paysublen
114   local uuid
115   local major
116   local minor
117   local details
118
119   if packet:len()>1 then
120
121     while packet:len()>2 do
122       bytes[idx]=trim(packet:sub(1,3))
123       idx=idx+1
124       packet=packet:sub(4)
125     end
126     len = idx-1
127
128     if bytes[1]=='04' and bytes[2]=='3E' then
129       -- BLE Beacon?
130       type=""
131       mac=bytes[13]..':'..bytes[12]..':'..bytes[11]..':'..bytes[10]..':'..bytes[9]..':'..bytes[8]
132       flags=bytes[14]
133       power=tonumber("0x"..bytes[len-1])-256
134       tx=tonumber("0x"..bytes[len])-256
135       local j = 15
136       while j<len-2 do
137         paysublen=tonumber('0x'..bytes[j])
138         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
139           -- Standard UUID iBeacon
140           type="ibeacon"
141           local uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
142           local uuid2=bytes[j+10]..bytes[j+11]
143           local uuid3=bytes[j+12]..bytes[j+13]
144           local uuid4=bytes[j+14]..bytes[j+15]
145           local uuid5=bytes[j+16]..bytes[j+17]..bytes[j+18]..bytes[j+19]..bytes[j+20]..bytes[j+21]
146           uuid=string.lower(uuid1..'-'..uuid2..'-'..uuid3..'-'..uuid4..'-'..uuid5)
147           major=bytes[j+23]..bytes[j+22]
148           minor=bytes[j+25]..bytes[j+24]
149         end
150         j=j+1+paysublen
151       end
152       if type=="ibeacon" then
153         printLog(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
154         details=uuid..'/'..major..'/'..minor
155       else
156         type='unknown'
157         details=dump(bytes)
158         printLog(details)
159       end
160       if mqtt_client then
161         if not mqtt_client.connected then
162           mqtt_client:connect(mqtt_id)
163         end
164         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
165           function (name) 
166             if name=="type" then
167               return mqtt_encode(type)
168             elseif name=="details" then
169               return mqtt_encode(details)
170             else
171               return '{'..name..'}'
172             end      
173           end)
174         mqtt_client:publish(mqtt_path,tx)
175       end        
176     end
177   end
178
179 end
180
181 function read_loop()
182
183   packet = ""
184
185   for line in inp:lines() do
186
187     lchr=line:sub(1,1)
188     line=trim(line)
189
190     if lchr=="<" or lchr==">" then
191       line = trim(line:sub(2))
192     end
193
194     if not (lchr == " ") then
195       process_packet(packet)
196       packet = ""
197     end
198
199     llen=line:len()
200
201     if llen<59 then
202       packet = packet .. " " .. line
203       process_packet(packet)
204       packet=""
205     else
206       packet = packet .. " " .. line
207     end    
208     
209   end
210
211 end
212
213 getConfig(arg[1])
214
215 if mqtt_host then
216   MQTT = require "paho.mqtt"
217   mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
218   if mqtt_user then
219     mqtt_client:auth(mqtt_user, mqtt_passwd)
220   end
221 end
222
223 inp = open_dump()
224 read_loop()