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