-#!/usr/bin/env lua
+#!/usr/bin/lua
+
+json = require("json")
+socket = require("socket")
function getConfig(configname)
- local uci=require("uci")
- local cur=uci.cursor()
+ local status,uci = pcall(require,"uci")
+
local config
- if configname then
- config=configname
- else
- config="beacon"
- end
- logging = cur.get(config,"logging","enabled")
+ if status then
- 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")
+ 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
- mqtt_user = cur.get(config,"mqtt","user")
- mqtt_passwd = cur.get(config,"mqtt","password")
+ 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
- mqtt_id="beaconmon"
+ socket = require("socket")
+ posix = require("posix")
+ pid = posix.getpid()
+ mqtt_id="beaconmon-"..hostname.."-"..pid
end
if mqtt_host and not mqtt_port then
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=="on" then
- capture("logger -t beaconmon "..str)
- else
+ 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 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)
- local bytes={}
- local idx=1
- local len
local mac
- local flags
- local power
- local tx
- local type
- local paysublen
local uuid
- local major
- local minor
local details
+ local type
+ local name
+
+ packet['origin'] = hostname
+
+ 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 packet:len()>1 then
-
- 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
- local 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"
- local uuid1=bytes[j+6]..bytes[j+7]..bytes[j+8]..bytes[j+9]
- local uuid2=bytes[j+10]..bytes[j+11]
- local uuid3=bytes[j+12]..bytes[j+13]
- local uuid4=bytes[j+14]..bytes[j+15]
- local 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
- printLog(string.format("{type:'ibeacon',mac:'%s',uuid:'%s',major:'%s',minor:'%s',power:%d,tx:%d}",mac,uuid,major,minor,power,tx))
- details=uuid..'/'..major..'/'..minor..'/'
- else
- type='unknown'
- details=dump(bytes)
- printLog(details)
- end
- if mqtt_client 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)
- mqtt_client:publish(mqtt_path,tx)
- 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 read_loop()
-
- packet = ""
-
- for line in inp:lines() do
-
- lchr=line:sub(1,1)
- line=trim(line)
-
- if lchr=="<" or lchr==">" then
- line = trim(line:sub(2))
- end
+function starts(String,Start)
+ return string.sub(String,1,string.len(Start))==Start
+end
- if not (lchr == " ") then
- process_packet(packet)
- packet = ""
- 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
- llen=line:len()
+function read_loop()
- if llen<59 then
- packet = packet .. " " .. line
- process_packet(packet)
- packet=""
- else
- packet = packet .. " " .. line
- end
+ 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 "paho.mqtt"
- mqtt_client = MQTT.client.create(mqtt_host, mqtt_port)
+ MQTT = require "mosquitto"
+ mqtt_client = MQTT.new(mqtt_id)
if mqtt_user then
- mqtt_client:auth(mqtt_user, mqtt_passwd)
+ mqtt_client:login_set(mqtt_user, mqtt_passwd)
end
- mqtt_client:connect(mqtt_id)
+ mqtt_client:connect(mqtt_host,mqtt_port)
end
inp = open_dump()
+
read_loop()