34de7dc8f93830f9bafd074226a3c6fb8d2fc1e3
[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   run_command("/bin/kill `/usr/bin/pgrep hcidump`")
76   run_command("/bin/kill `/usr/bin/pgrep hcitool`")
77   f = assert(io.popen ("/usr/bin/hcidump --raw"))
78   run_command("/usr/bin/hciconfig hci0 down")
79   run_command("/usr/bin/hciconfig hci0 up")
80   f_null = assert(io.popen ("/usr/bin/hcitool lescan --duplicates"))
81
82   return f
83
84 end
85
86 function dump(o)
87    if type(o) == 'table' then
88       local s = '{ '
89       for k,v in pairs(o) do
90          if type(k) ~= 'number' then k = '"'..k..'"' end
91          s = s .. '['..k..'] = ' .. dump(v) .. ','
92 --         s = s .. dump(v) .. ','
93       end
94       return s .. '} '
95    else
96       return tostring(o)
97    end
98 end
99
100 function trim(s)
101   return (s:gsub("^%s*(.-)%s*$", "%1"))
102 end
103
104 function mqtt_pub(path,value)
105   printLog("Pub "..path.." "..value)
106   return mqtt_client:publish(path,value)
107 end
108
109 function process_packet(packet)
110
111   local bytes={}
112   local idx=1
113   local len
114   local mac
115   local flags
116   local power
117   local tx
118   local type
119   local paysublen
120   local uuid
121   local major
122   local minor
123   local details
124
125   if packet:len()>1 then
126
127     while packet:len()>2 do
128       bytes[idx]=trim(packet:sub(1,3))
129       idx=idx+1
130       packet=packet:sub(4)
131     end
132     len = idx-1
133
134     if bytes[1]=='04' and bytes[2]=='3E' then
135       -- BLE Beacon?
136       type=""
137       mac=bytes[13]..':'..bytes[12]..':'..bytes[11]..':'..bytes[10]..':'..bytes[9]..':'..bytes[8]
138       flags=bytes[14]
139       power=tonumber("0x"..bytes[len-1])-256
140       tx=tonumber("0x"..bytes[len])-256
141       local j = 15
142       while j<len-2 do
143         paysublen=tonumber('0x'..bytes[j])
144         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
145           -- Standard UUID iBeacon
146           type="ibeacon"
147           local uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
148           local uuid2=bytes[j+10]..bytes[j+11]
149           local uuid3=bytes[j+12]..bytes[j+13]
150           local uuid4=bytes[j+14]..bytes[j+15]
151           local uuid5=bytes[j+16]..bytes[j+17]..bytes[j+18]..bytes[j+19]..bytes[j+20]..bytes[j+21]
152           uuid=string.lower(uuid1..'-'..uuid2..'-'..uuid3..'-'..uuid4..'-'..uuid5)
153           major=bytes[j+23]..bytes[j+22]
154           minor=bytes[j+25]..bytes[j+24]
155         end
156         j=j+1+paysublen
157       end
158       if type=="ibeacon" then
159         printLog(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
160         details=uuid..'/'..major..'/'..minor
161       else
162         type='unknown'
163         details=dump(bytes)
164 --        printLog(details)
165       end
166       if not (type=="unknown") then
167         mqtt_path=string.gsub(mqtt_topic,"{(.-)}", 
168           function (name) 
169             if name=="type" then
170               return mqtt_encode(type)
171             elseif name=="details" then
172               return mqtt_encode(details)
173             else
174               return '{'..name..'}'
175             end      
176           end)
177         
178         if not pcall(mqtt_pub,mqtt_path,tx) then
179           printLog('Reconnecting MQTT...')
180           mqtt_client:connect(mqtt_id)
181         end
182
183       end        
184     end
185   end
186
187 end
188
189 function read_loop()
190
191   packet = ""
192
193   for line in inp:lines() do
194
195     lchr=line:sub(1,1)
196     line=trim(line)
197
198     if lchr=="<" or lchr==">" then
199       line = trim(line:sub(2))
200     end
201
202     if not (lchr == " ") then
203       process_packet(packet)
204       packet = ""
205     end
206
207     llen=line:len()
208
209     if llen<59 then
210       packet = packet .. " " .. line
211       process_packet(packet)
212       packet=""
213     else
214       packet = packet .. " " .. line
215     end    
216     
217   end
218
219 end
220
221 getConfig(arg[1])
222
223 if mqtt_host then
224   MQTT = require "paho.mqtt"
225   mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
226   if mqtt_user then
227     mqtt_client:auth(mqtt_user, mqtt_passwd)
228   end
229   mqtt_client:connect(mqtt_id)
230 end
231
232 inp = open_dump()
233
234 read_loop()