#!/usr/bin/lua json = require("json") function getConfig(configname) local uci=require("uci") local cur=uci.cursor() local config if configname then config=configname else config="beacon" end logging = cur.get(config,"logging","enabled") mqtt_host = cur.get(config,"mqtt","host") mqtt_port = cur.get(config,"mqtt","port") mqtt_id = cur.get(config,"mqtt","id") mqtt_topic = cur.get(config,"mqtt","topic") mqtt_user = cur.get(config,"mqtt","user") mqtt_passwd = cur.get(config,"mqtt","password") if mqtt_host and not mqtt_id then socket = require("socket") posix = require("posix") hostname = socket.dns.gethostname() pid = posix.getpid() mqtt_id="beaconmon-"..hostname.."-"..pid end if mqtt_host and not mqtt_port then mqtt_port = 1883 end if mqtt_host and not mqtt_topic then mqtt_topic = 'beaconmon/{type}/{details}' end end function capture(cmd, raw) local f = assert(io.popen(cmd, 'r')) local s = assert(f:read('*a')) f:close() if raw then return s end s = string.gsub(s, '^%s+', '') s = string.gsub(s, '%s+$', '') s = string.gsub(s, '[\n\r]+', ' ') return s end function mqtt_encode(str) if (str) then str = string.gsub (str, "\n", "") str = string.gsub (str, ":", "-") str = string.gsub (str, "/", "-") str = string.gsub (str, " ", "_") end return str end function printLog(str) if logging=="yes" then capture("logger -t beaconmon "..str) else print(str) end end function run_command(cmd) local file = assert(io.popen(cmd, 'r')) local output = file:read('*all') file:close() end function open_dump() run_command("/bin/kill `/usr/bin/pgrep btmon`") run_command("/bin/kill `/usr/bin/pgrep hcitool`") f = assert(io.popen ("/usr/bin/stdbuf -o0 /usr/bin/btmon")) run_command("/usr/bin/hciconfig hci0 down") run_command("/usr/bin/hciconfig hci0 up") f_null = assert(io.popen ("/usr/bin/hcitool lescan --duplicates --passive")) return f end function dump(o) return json.encode(o) end function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end function mqtt_pub(path,value) res=mqtt_client:publish(path,value) printLog("Pub "..path.." "..value.." returned "..res); return res end function process_packet(packet) local mac local uuid local details local type local name mac = packet['Address'] uuid = packet['uuid'] type = packet['Type'] name = packet['Name (complete)'] if type=='iBeacon' then details=uuid elseif name then if not(type) then type="name" end details=name else if not type then type='unknown' end details=mac end if not (type=="unknown") then mqtt_path=string.gsub(mqtt_topic,"{(.-)}", function (name) if name=="type" then return mqtt_encode(type) elseif name=="details" then return mqtt_encode(details) else return '{'..name..'}' end end) if not pcall(mqtt_pub,mqtt_path,dump(packet)) then printLog('Reconnecting MQTT...') mqtt_client:connect(mqtt_id) end end end function starts(String,Start) return string.sub(String,1,string.len(Start))==Start end function split(inputstr, sep) if sep == nil then sep = "%s" end local t={} ; i=1 for str in string.gmatch(inputstr, "([^"..sep.."]+)") do t[i] = str i = i + 1 end return t end function read_loop() packet={} inbound=false while true do str=inp:read("*l") if str then str=trim(str) if inbound then t = split(str,':') if #t>=2 then name=t[1] value=trim(table.concat(t,':',2)) if name=="Address" then value=split(value)[1] end packet[name]=value elseif #t==1 and name then if not(packet[name..'.list']) then packet[name..'.list']={} end table.insert(packet[name..'.list'],(trim(t[1]))) end end if starts(str,'> HCI Event: LE Meta Event (0x3e)') then inbound=true name=nil elseif starts(str,'RSSI:') then inbound=false process_packet(packet) packet={} end end end end io.stdout:setvbuf('no') io.stdin:setvbuf('no') getConfig(arg[1]) if mqtt_host then MQTT = require "mosquitto" mqtt_client = MQTT.new(mqtt_id) if mqtt_user then mqtt_client:login_set(mqtt_user, mqtt_passwd) end mqtt_client:connect(mqtt_host,mqtt_port) end inp = open_dump() read_loop()