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