-#!/usr/bin/env lua
+#!/usr/bin/lua
+
+json = require("json")
+socket = require("socket")
+
+function getConfig(configname)
+
+ local status,uci = pcall(require,"uci")
+
+ local config
+
+ if status then
+
+ if configname then
+ config=configname
+ else
+ config="beacon"
+ end
+
+ local cur=uci.cursor()
+
+ 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")
+
+ else
+
+ local status,ini = pcall(require,"ini")
+ if not status then
+ os.exit(1)
+ end
+
+ if configname then
+ config=configname
+ else
+ config="/etc/beacon.ini"
+ end
+
+ local cur=ini.parse_file(config)
+
+ logging = cur["logging"]["enabled"]
+
+ mqtt_host = cur["mqtt"]["host"]
+ mqtt_port = cur["mqtt"]["port"]
+ mqtt_id = cur["mqtt"]["id"]
+ mqtt_topic = cur["mqtt"]["topic"]
+
+ mqtt_user = cur["mqtt"]["user"]
+ mqtt_passwd = cur["mqtt"]["password"]
+
+ end
+
+ hostname = socket.dns.gethostname()
+ if mqtt_host and not mqtt_id then
+ socket = require("socket")
+ posix = require("posix")
+ 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.."\"")
+ print(str)
+ elseif logging=="syslog" then
+ capture("logger -t beaconmon \""..str.."\"")
+ elseif logging=="stdout" then
+ print(str)
+ end
+end
function run_command(cmd)
function open_dump()
- f = assert(io.popen ("hcidump --raw"))
- run_command("kill `pgrep hcitool`")
+ run_command("/usr/bin/pkill btmon")
+ run_command("/usr/bin/pkill hcitool")
+ f = assert(io.popen ("/usr/bin/stdbuf -o0 /usr/bin/btmon"))
run_command("hciconfig hci0 down")
run_command("hciconfig hci0 up")
- f_null = assert(io.popen ("hcitool lescan --duplicates"))
+ f_null = assert(io.popen ("hcitool lescan --duplicates --passive"))
return f
end
function dump(o)
- if type(o) == 'table' then
- local s = '{ '
- for k,v in pairs(o) do
- if type(k) ~= 'number' then k = '"'..k..'"' end
--- s = s .. '['..k..'] = ' .. dump(v) .. ','
- s = s .. dump(v) .. ','
- end
- return s .. '} '
- else
- return tostring(o)
- end
+ return json.encode(o)
end
function trim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
+local function starts_with(str, start)
+ return str:sub(1, #start) == start
+end
+
+function mqtt_pub(path,value)
+ res=mqtt_client:publish(path,value)
+ printLog("Pub "..path.." returned "..res);
+ return res
+end
+
function process_packet(packet)
- bytes={}
- idx=1
+ local mac
+ local uuid
+ local details
+ local type
+ local name
- if packet:len()>1 then
+ packet['origin'] = hostname
- while packet:len()>2 do
- bytes[idx]=trim(packet:sub(1,3))
- idx=idx+1
- packet=packet:sub(4)
- end
- len = idx-1
-
- if bytes[1]=='04' and bytes[2]=='3E' then
- -- BLE Beacon?
- type=""
- mac=bytes[13]..':'..bytes[12]..':'..bytes[11]..':'..bytes[10]..':'..bytes[9]..':'..bytes[8]
- flags=bytes[14]
- power=tonumber("0x"..bytes[len-1])-256
- tx=tonumber("0x"..bytes[len])-256
- j = 15
- while j<len-2 do
- paysublen=tonumber('0x'..bytes[j])
- 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
- -- Standard UUID iBeacon
- type="ibeacon"
- uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
- uuid2=bytes[j+10]..bytes[j+11]
- uuid3=bytes[j+12]..bytes[j+13]
- uuid4=bytes[j+14]..bytes[j+15]
- uuid5=bytes[j+16]..bytes[j+17]..bytes[j+18]..bytes[j+19]..bytes[j+20]..bytes[j+21]
- uuid=string.lower(uuid1..'-'..uuid2..'-'..uuid3..'-'..uuid4..'-'..uuid5)
- major=bytes[j+23]..bytes[j+22]
- minor=bytes[j+25]..bytes[j+24]
- end
- j=j+1+paysublen
- end
- if type=="ibeacon" then
- print(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
- else
- print(dump(bytes))
- end
+ mac = packet['Address']
+ uuid = packet['UUID']
+ type = packet['Type']
+ name = packet['Name (complete)']
+
+ if type and starts_with(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 = ""
+ packet={}
+ inbound=false
- for line in inp:lines() do
+ while true do
- lchr=line:sub(1,1)
- line=trim(line)
+ str=inp:read("*l")
- if lchr=="<" or lchr==">" then
- line = trim(line:sub(2))
- end
+ if str then
- if not (lchr == " ") then
- process_packet(packet)
- packet = ""
- end
+ str=trim(str)
- llen=line:len()
+ 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 llen<59 then
- packet = packet .. " " .. line
- process_packet(packet)
- packet=""
- else
- packet = packet .. " " .. line
- 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()