diff --git a/Makefile b/Makefile index 61f4281b..ed97d692 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ bundle-server: mv fgcom-mumble-server-$(SERVER_VER)/statuspage/Readme.statuspage.md fgcom-mumble-server-$(SERVER_VER) cp server/Readme.server-de_DE.md fgcom-mumble-server-$(SERVER_VER)/ cp server/fgcom-botmanager.sh server/*.bot.lua fgcom-mumble-server-$(SERVER_VER) - sed '/^\s\+gitver/s/""/"$(GITVER) $(GITDATE)"/' server/sharedFunctions.inc.lua > fgcom-mumble-server-$(SERVER_VER)/sharedFunctions.inc.lua + sed '/^\s\+gitver/s/""/"$(GITVER) $(GITDATE)"/' server/fgcom-sharedFunctions.inc.lua > fgcom-mumble-server-$(SERVER_VER)/fgcom-sharedFunctions.inc.lua zip -r fgcom-mumble-server-$(SERVER_VER).zip fgcom-mumble-server-$(SERVER_VER) rm -rf fgcom-mumble-server-$(SERVER_VER) diff --git a/server/VERSION b/server/VERSION index fefc4eac..d4efe0e6 100644 --- a/server/VERSION +++ b/server/VERSION @@ -1 +1 @@ -VERSION: 1.1.4 +VERSION: 1.2.0 diff --git a/server/fgcom-radio-playback.bot.lua b/server/fgcom-radio-playback.bot.lua index e14a0047..8e26769d 100644 --- a/server/fgcom-radio-playback.bot.lua +++ b/server/fgcom-radio-playback.bot.lua @@ -33,7 +33,7 @@ The bot is depending on lua-mumble from bkacjios (https://github.com/bkacjios/l Installation of this plugin is described in the projects readme: https://github.com/bkacjios/lua-mumble/blob/master/README.md ]] -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions fgcom.botversion = "1.8.1" -- init random generator using /dev/random, if poosible (=linux) @@ -253,7 +253,8 @@ end -- Connect to server, so we get the API -fgcom.log(botname..": connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") +fgcom.log(botname..": "..fgcom.getVersion()) +fgcom.log("connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") local client = assert(mumble.connect(host, port, cert, key)) client:auth(fgcom.callsign) fgcom.dbg("connect and bind: OK") diff --git a/server/fgcom-radio-recorder.bot.lua b/server/fgcom-radio-recorder.bot.lua index 742bb3f6..a5e24240 100644 --- a/server/fgcom-radio-recorder.bot.lua +++ b/server/fgcom-radio-recorder.bot.lua @@ -37,8 +37,8 @@ Installation of this plugin is described in the projects readme: https://github. ]] -dofile("sharedFunctions.inc.lua") -- include shared functions -fgcom.botversion = "1.8.1" +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions +fgcom.botversion = "1.8.2" local botname = "FGCOM-Recorder" fgcom.callsign = "FGCOM-REC" local voiceBuffer = Queue:new() @@ -125,7 +125,8 @@ end -- Connect to server, so we get the API -fgcom.log(botname..": connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") +fgcom.log(botname..": "..fgcom.getVersion()) +fgcom.log("connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") local client = assert(mumble.connect(host, port, cert, key)) client:auth(botname) fgcom.log("connect and bind: OK") @@ -285,6 +286,9 @@ client:hook("OnPluginData", function(client, event) -- clean up stale entries fgcom.data.cleanupPluginData() + + -- check for missing data and ask for it if neccesary + local missing_data = fgcom.data.checkMissingPluginData(client); end) diff --git a/server/sharedFunctions.inc.lua b/server/fgcom-sharedFunctions.inc.lua similarity index 77% rename from server/sharedFunctions.inc.lua rename to server/fgcom-sharedFunctions.inc.lua index 3d14ddc1..e8d752d1 100644 --- a/server/sharedFunctions.inc.lua +++ b/server/fgcom-sharedFunctions.inc.lua @@ -80,7 +80,7 @@ end -- FGCom functions fgcom = { botversion = "unknown", - libversion = "1.7.0", + libversion = "1.8.0", gitver = "", -- will be set from makefile when bundling channel = "fgcom-mumble", callsign = "FGCOM-someUnknownBot", @@ -308,15 +308,19 @@ fgcom = { end -- check if we already know this clients identity with given iid; if not, add template - if not fgcom_clients[sid][iid] then + if fgcom_clients[sid][iid] then + if fgcom.hooks.parsePluginData_updateKnownClient ~= nil then fgcom.hooks.parsePluginData_updateKnownClient(sid, iid) end + else fgcom_clients[sid][iid] = { callsign="", lat="", lon="", alt="", radios={}, - lastUpdate=0 + lastUpdate=0, + missingDataSince=0 -- 0: need to check; >0: missing-timestamp; -1: verified ok } + if fgcom.hooks.parsePluginData_newClient ~= nil then fgcom.hooks.parsePluginData_newClient(sid, iid) end end -- record that we had an data update @@ -369,6 +373,8 @@ fgcom = { fgcom.dbg("parsing field failed! "..#field.." tokens seen") end end + + if fgcom.hooks.parsePluginData_afterParseIID ~= nil then fgcom.hooks.parsePluginData_afterParseIID(sid, iid) end elseif packtype == "PING" then -- update the contained identites lastUpdate timestamps @@ -378,11 +384,16 @@ fgcom = { if fgcom_clients[sid][iid] then fgcom_clients[sid][iid].lastUpdate = os.time() end + + if fgcom.hooks.parsePluginData_afterParseIID ~= nil then fgcom.hooks.parsePluginData_afterParseIID(sid, iid) end end elseif packtype == "ICANHAZDATAPLZ" then -- ignore for now end + + fgcom.dbg("Packet fully processed.") + if fgcom.hooks.parsePluginData_processedPacket ~= nil then fgcom.hooks.parsePluginData_processedPacket(sender, packtype, dataID_t) end end fgcom.dbg("Parsing done. New remote state:") @@ -398,6 +409,9 @@ fgcom = { fgcom.dbg("sid="..uid.."; idty="..iid.." radio #"..radio_id.." pwr='"..radio.power.."'") fgcom.dbg("sid="..uid.."; idty="..iid.." radio #"..radio_id.." opr='"..radio.operable.."'") end + elseif k == "lastUpdate" then + local last_updated_since = os.time() - v + fgcom.dbg("sid="..uid.."; idty="..iid.."\t"..k..":\t"..tostring(v).." ("..last_updated_since.."s ago)") else fgcom.dbg("sid="..uid.."; idty="..iid.."\t"..k..":\t"..tostring(v)) end @@ -416,10 +430,74 @@ fgcom = { local stale_since = os.time() - idty.lastUpdate if stale_since > fgcom.data.cleanupTimeout then fgcom.dbg("cleanup remote data: sid="..uid.."; idty="..iid.." stale_since="..stale_since) - fgcom_clients[uid][iid] = nil; + local process = true + if fgcom.hooks.cleanupPluginData_entry ~= nil then process=fgcom.hooks.cleanupPluginData_entry(uid, iid) end + + if process then fgcom_clients[uid][iid] = nil end + end + end + end + end, + + -- Check if data is missing for an extended period of time, and ask for more. + -- You can "just check" without asking if supplying nil as client param. + -- @param mumble.client instance. If nil, no sending for the ask package is performed. + -- @param int (optional) timeout in secs, after which to ask for data. + -- @return array with missing data: ({ {sid=n, iid=n, missing_data={list, of, missing, data, field, names}}, {...} }) + checkMissingPluginData = function(client, askForMissingDataAfter) + askForMissingDataAfter = askForMissingDataAfter or 30 + local missing_data_return = {} + + local allUsers = {} -- sid=>mumbleUser table + if client then + for i, mc in ipairs(client:getUsers()) do allUsers[mc:getSession()] = mc end + end + + for sid,remote_client in pairs(fgcom_clients) do + for iid,idty in pairs(remote_client) do + fgcom.dbg("checking for missing fgcom.clients data: sid="..sid.."; idty="..iid.."...") + if idty.missingDataSince == -1 then + -- no further checks needed (-1) + fgcom.dbg(" all data already complete") + else + -- see if data is missing + local missing_data = {} + if idty.callsign == "" then table.insert(missing_data, "callsign") end + if idty.lat == "" then table.insert(missing_data, "lat") end + if idty.lon == "" then table.insert(missing_data, "lon") end + if idty.alt == "" then table.insert(missing_data, "alt") end + + if #missing_data == 0 then + -- all needed data there, mark identity as finally checked + fgcom.dbg(" all data is now complete, no further checks needed") + idty.missingDataSince = -1 + else + -- we really still miss data. See if we already need to ask. + if idty.missingDataSince == 0 then + -- first check, store timestamp for later checks + fgcom.dbg(" missing data ("..table.concat(missing_data, ", ").."), marking for further checks") + idty.missingDataSince = os.time() + else + -- consecutive checks, see if timeout exceeded + local missing_since = os.time() - idty.missingDataSince + fgcom.dbg(" missing data ("..table.concat(missing_data, ", ").."), since "..missing_since.."s") + if client and askForMissingDataAfter > 0 and missing_since > askForMissingDataAfter then + fgcom.dbg(" data missing for too long; asking sid="..sid.." for data") + client:sendPluginData("FGCOM:ICANHAZDATAPLZ", "orly!", {allUsers[sid]}) + for iid,idty in pairs(remote_client) do + idty.missingDataSince = 0 -- mark for checking again + end + end + end + + -- add data to return structure + table.insert(missing_data_return, {sid=sid, iid=iid, missing=missing_data}) + end + end end end + return missing_data_return end }, @@ -476,6 +554,27 @@ fgcom = { end return fgcom.auth.isAuthenticated(user) end, + }, + + -- Various hooks, bots can implement to have event based adjustment options. + -- If they are not defined, they will not be called. + hooks = { + -- parsePluginData_afterParseIID(sid, iid) + -- called when parsePluginData() received data for a given iid + + -- fgcom.hooks.parsePluginData_newClient(sid, iid) + -- called when parsePluginData() detected that the client was not seen before. + -- is called before any datas is parsed/added. + + -- fgcom.hooks.parsePluginData_updateKnownClient(sid, iid) + -- called when parsePluginData() detected that the client was known. + -- is called before any datas is parsed/updated. + + -- fgcom.hooks.parsePluginData_processedPacket(mumble_user, packtype, dataID_t) + -- called after processing the packet, passing raw data + + -- fgcom.hooks.cleanupPluginData_entry(sid, iid) + -- called when cleaning up an entry. return false to prevent the entry to be cleaned out. } } diff --git a/server/statuspage/fgcom-status.bot.lua b/server/statuspage/fgcom-status.bot.lua index a6edcfde..0651f2f5 100644 --- a/server/statuspage/fgcom-status.bot.lua +++ b/server/statuspage/fgcom-status.bot.lua @@ -36,7 +36,7 @@ Installation of this plugin is described in the projects readme: https://github. ]] -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions fgcom.botversion = "1.9.0" json = require("dkjson") local botname = "FGCOM-Status" @@ -99,7 +99,8 @@ if arg[1] then end -- Connect to server, so we get the API -fgcom.log(botname..": connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") +fgcom.log(botname..": "..fgcom.getVersion()) +fgcom.log("connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") local client = assert(mumble.connect(host, port, cert, key)) client:auth(fgcom.callsign) fgcom.log("connect and bind: OK") @@ -132,59 +133,92 @@ local generateOutData = function() for i, mc in ipairs(client:getUsers()) do allUsers[mc:getSession()] = mc end local data = {clients={}, meta={}} -- final return array + -- Record already processed entries (Issue #177) + -- TODO: probably I''m just to dumb to properly use lua in the first place. So consider this a workaround. + -- If you ever see this comment and think otherwise, let me know so I can remove this embarrassing bit of comment and/or code. :) + local already_processed_clients = {} + -- generate list of current users local users_alive = 0 for sid, remote_client in pairs(fgcom_clients) do for iid,user in pairs(remote_client) do - fgcom.dbg("generateOutData(): processing user: "..sid.." with idty="..iid) - local userData = {} -- the return structure for generating the message - local mumbleUser = allUsers[sid] - if not mumbleUser then - fgcom.dbg("User sid="..sid.." not connected anymore!") - -- push out old data for a while + fgcom.dbg("generateOutData(): evaluating user: "..sid.." with idty="..iid) + + -- Skip, if already processed (Issue #177). + -- I really have no Idea, why table entries are sometimes iterated more than once. + local already_processed_clients_key = string.format("%s:%s", sid, iid) + if already_processed_clients[already_processed_clients_key] ~= nil then + fgcom.dbg("skipping entry '"..already_processed_clients_key.."'); already processed") + + else + fgcom.dbg("processing entry '"..already_processed_clients_key.."'") + already_processed_clients[already_processed_clients_key] = true + + + -- prepare the return structure for generating the message + local userData = { + callsign="", + type="invalid", + radios = {}, + lat="", + lon="", + alt="", + updated=0 + } + + -- populate users metadata + local mumbleUser = allUsers[sid] + local last_updated_since = os.time() - fgcom_clients[sid][iid].lastUpdate + if not mumbleUser then + fgcom.dbg("User sid="..sid.." not connected anymore!") + -- push out old data for a while; + -- fgcom.data.cleanupPluginData() (called from dbUpdateTimer) will clean up for us. + -- once that happened, we will not see the entry here anymore. + else + if userData.type == "client" then users_alive = users_alive + 1 end + end userData.updated = fgcom_clients[sid][iid].lastUpdate - userData.type = fgcom_clients[sid][iid].type - else - fgcom_clients[sid][iid].type = "client" - if mumbleUser:getName():find("FGCOM%-.*") then fgcom_clients[sid][iid].type = "playback-bot" end - if mumbleUser:getName():find("FGCOM%-BOTPILOT.*") then fgcom_clients[sid][iid].type = "client" end - userData.type = fgcom_clients[sid][iid].type + fgcom.dbg(" updated="..userData.updated.. " ("..last_updated_since.."s ago)") - if userData.type == "client" then users_alive = users_alive + 1 end - end - - userData.callsign = user.callsign - fgcom.dbg(" callsign="..userData.callsign.." (type="..userData.type..")") - - userData.radios = {} - for radio_id,radio in pairs(user.radios) do - fgcom.dbg(" radio #"..radio_id..", ptt='"..radio.ptt.."', frq='"..radio.frequency.."', dialedFRQ='"..radio.dialedFRQ.."', operable="..radio.operable) - if radio.frequency ~= "" then - table.insert(userData.radios, radio_id, radio) + -- populate users callsign + userData.type = fgcom_clients[sid][iid].type + userData.callsign = user.callsign + fgcom.dbg(" callsign="..userData.callsign.." (type="..userData.type..")") + + -- populate users radios + for radio_id,radio in pairs(user.radios) do + fgcom.dbg(" radio #"..radio_id..", ptt='"..radio.ptt.."', frq='"..radio.frequency.."', dialedFRQ='"..radio.dialedFRQ.."', operable="..radio.operable) + if radio.frequency ~= "" then + table.insert(userData.radios, radio_id, radio) + end end + + -- populate users location + userData.lat = user.lat + userData.lon = user.lon + userData.alt = user.alt + fgcom.dbg(" lat="..userData.lat.."; lon="..userData.lon.."; alt="..userData.alt) + + -- finally push the prepared data into the client result + table.insert(data.clients, userData) end - userData.lat = user.lat - userData.lon = user.lon - userData.alt = user.alt - userData.updated = fgcom_clients[sid][iid].lastUpdate - fgcom.dbg(" updated="..userData.updated) - fgcom.dbg(" lat="..userData.lat.."; lon="..userData.lon.."; alt="..userData.alt) - - table.insert(data.clients, userData) end end -- generate metadata + fgcom.dbg("generateOutData(): generating highscore data...") if highscore.num < users_alive then highscore.num = users_alive highscore.date = os.time() + fgcom.log("new highscore: "..highscore.num.." clients at "..os.date('%Y-%m-%d %H:%M:%S', highscore.date).." ("..highscore.date..")") end data.meta.highscore_clients = highscore.num data.meta.highscore_date = highscore.date -- generate JSON structure + fgcom.dbg("generateOutData(): generating db content...") dataJsonString = json.encode(data) fgcom.dbg("JSON RESULT: "..dataJsonString) return dataJsonString @@ -209,7 +243,6 @@ updateAllChannelUsersforSend = function(cl) playback_targets = ch:getUsers() end - -- Timed loop to update the database local dbUpdateTimer = mumble.timer() dbUpdateTimer_func = function(t) @@ -223,6 +256,7 @@ dbUpdateTimer_func = function(t) fgcom.dbg("opened db '"..tmpdb.."'") -- tmpdb is open, write out the data local data = generateOutData() + fgcom.dbg("db data generating completed") local writeRes = tmpdb_fh:write(data) if not writeRes then fgcom.log("unable to write into db: "..tmpdb) @@ -235,14 +269,20 @@ dbUpdateTimer_func = function(t) io.close(tmpdb_fh) os.remove(db) - ren_rc, ren_message = os.rename(tmpdb, db) - -- TODO: handle errors - fgcom.dbg("published db '"..db.."'") + local ren_rc, ren_message = os.rename(tmpdb, db) + if not ren_rc then + fgcom.log("error publishing db: "..db) + else + fgcom.dbg("published db '"..db.."'") + end end -- clean up stale entries fgcom.data.cleanupTimeout = 60 -- enhance timeout, so we can display them longer fgcom.data.cleanupPluginData() + + -- check for missing data and ask for it if neccesary + local missing_data = fgcom.data.checkMissingPluginData(client); else fgcom.log("ERROR: unable to open db: "..tmpdb) @@ -477,5 +517,45 @@ client:hook("OnMessage", function(client, event) end) +-- Implement fgcom hooks +fgcom.hooks.parsePluginData_updateKnownClient = function(sid, iid) + -- called when parsePluginData() detected that the client was known. + fgcom.dbg("fgcom.hooks.parsePluginData_updateKnownClient("..sid..","..iid..") called") +end + +fgcom.hooks.parsePluginData_newClient = function(sid, iid) + -- called when parsePluginData() detected that the client was not seen before. + fgcom.dbg("fgcom.hooks.parsePluginData_newClient("..sid..","..iid..") called") + fgcom_clients[sid][iid].type = "new/unknown" -- assure type field is set. Will get updated after fully parsing the packet +end + +--fgcom.hooks.parsePluginData_afterParseIID = function(sid, iid) +-- -- called when parsePluginData() received data for a given iid +-- fgcom.dbg("fgcom.hooks.parsePluginData_afterParseIID("..sid..","..iid..") called") +--end + +fgcom.hooks.parsePluginData_processedPacket = function(mumble_user, packtype, dataID_t) + -- called after processing the packet, passing raw data + fgcom.dbg("fgcom.hooks.parsePluginData_processedPacket("..packtype..") called") + if packtype == "UPD_USR" or packtype == "UPD_LOC" or packtype == "UPD_COM" then + local iid = dataID_t[3] -- identity selector + local sid = mumble_user:getSession() -- mumble session id + + -- Update type of client based on client name; + -- We know, that bot clients use FGCOM- as prefix for their mumble username. + local newType = "client" -- assume client as default + if mumble_user:getName():find("FGCOM%-.*") then newType = "playback-bot" end + if mumble_user:getName():find("FGCOM%-BOTPILOT.*") then newType = "client" end + + if fgcom_clients[sid][iid].type ~= newType then + fgcom.dbg(" update type to '"..newType.."'") + fgcom_clients[sid][iid].type = newType + end + end +end + + + + -- Finally start the bot mumble.loop() diff --git a/server/test/fgcom-echo.bot.lua b/server/test/fgcom-echo.bot.lua index 29151ba4..c76b40df 100644 --- a/server/test/fgcom-echo.bot.lua +++ b/server/test/fgcom-echo.bot.lua @@ -32,7 +32,7 @@ TODO: concurrency not implemented. Currently the bot can only respond to one tes ]] local botname = "FGOM-Echotest" -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions fgcom.callsign = "FGOM-ECHO" local voiceBuffer = Queue:new() diff --git a/server/test/fgcom-fakepilot.bot.lua b/server/test/fgcom-fakepilot.bot.lua index 83ad4610..9582518b 100644 --- a/server/test/fgcom-fakepilot.bot.lua +++ b/server/test/fgcom-fakepilot.bot.lua @@ -1,7 +1,7 @@ ---[[ +--[[ This file is part of the FGCom-mumble distribution (https://github.com/hbeni/fgcom-mumble). Copyright (c) 2020 Benedikt Hallinger - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3. @@ -32,7 +32,8 @@ The bot is depending on lua-mumble from bkacjios (https://github.com/bkacjios/l Installation of this plugin is described in the projects readme: https://github.com/bkacjios/lua-mumble/blob/master/README.md ]] -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions +fgcom.botversion = "1.0.1" -- init random generator using /dev/random, if poosible (=linux) fgcom.rng.initialize() @@ -46,6 +47,7 @@ local voiceBuffer = Queue:new() -- Queue voice buffer holding the cached samples local lastHeader = nil -- holds last header data local playback_targets = nil -- holds updated list of all channel users local ptt = false -- signals if the bot is currently transmitting +local freq = "none" -- active frequency local sleep = 30 -- time beween checks if to transmit local chance_transmit = 0.25 -- chance that he will transmit @@ -64,10 +66,13 @@ local port = 64738 local cert = "bot.pem" local key = "bot.key" local sample = "recordings/fgcom.rec.testsample.fgcs" +local s_lat = "random" +local s_lon = "random" +local s_alt = "random" if arg[1] then if arg[1]=="-h" or arg[1]=="--help" then - print(botname) + print(botname..", "..fgcom.getVersion()) print("usage: "..arg[0].." [opt=val ...]") print(" Options:") print(" --id= id to join with (default=random)") @@ -81,6 +86,11 @@ if arg[1] then print(" --sleep= Time interval between checks (default="..sleep..")") print(" --chancet= Chance for transmission (default="..chance_transmit..")") print(" --chancee= Chance that transmit is echotest (default="..chance_echotest..")") + print(" --lat= start latitude (decimal) (default="..s_lat..")") + print(" --lon= start longitude (decimal) (default="..s_lon..")") + print(" --alt= start altitude (decimal) (default="..s_alt..")") + print(" --debug print debug messages (default=no)") + print(" --version print version and exit") os.exit(0) end @@ -97,12 +107,20 @@ if arg[1] then if k=="sleep" then sleep=tonumber(v) end if k=="chancet" then chance_transmit=tonumber(v) end if k=="chancee" then chance_echotest=tonumber(v) end + if k=="lat" then s_lat=v end + if k=="lon" then s_lon=v end + if k=="alt" then s_alt=v end + if opt == "--debug" then fgcom.debugMode = true end + if opt == "--version" then print(botname..", "..fgcom.getVersion()) os.exit(0) end end end -- parameter checks --if sample == "" then print("parameter --sample is mandatory!") os.exit(1) end +if s_lat ~= "random" and tonumber(s_lat) == nil then print("parameter --lat must be a number or 'random'!") os.exit(1) end +if s_lon ~= "random" and tonumber(s_lon) == nil then print("parameter --lon must be a number or 'random'!") os.exit(1) end +if s_alt ~= "random" and tonumber(s_alt) == nil then print("parameter --alt must be a number or 'random'!") os.exit(1) end fgcom.callsign = "FGCOM-BOTPILOT-" if botid == "" then @@ -111,7 +129,6 @@ else fgcom.callsign = fgcom.callsign..botid end - ----------------------------- --[[ DEFINE SOME FUNCTIONS ]] ----------------------------- @@ -130,7 +147,7 @@ readFGCSSampleFile = function(file) local endOfSamples = false while not endOfSamples do local nextSample = fgcom.io.readFGCSSample(sampleFH) - --print("sample: len="..nextSample.len.."; eof='"..tostring(nextSample.eof).."'; data='"..nextSample.data.."'") + --fgcom.dbg("sample: len="..nextSample.len.."; eof='"..tostring(nextSample.eof).."'; data='"..nextSample.data.."'") if nextSample.len > 0 and not nextSample.eof then vb:pushright(nextSample) -- write the sample structure to the buffer end @@ -149,40 +166,62 @@ end ----------------------------- --[[ BOT RUNTIME ]] ----------------------------- +fgcom.log(botname..": "..fgcom.getVersion()) -- read the file into the queue. lastHeader, _ = readFGCSSampleFile(sample) -if not lastHeader then print("ERROR: '"..sample.."' not readable or no FGCS file") os.exit(1) end +if not lastHeader then fgcom.log("ERROR: '"..sample.."' not readable or no FGCS file") os.exit(1) end -- AFTER THIS CODE we can be confident it was a valid FGCS file! -- lastHeader is initialized with the header data -- voiceBuffer is initialized with the read samples -print(sample..": successfully loaded.") +fgcom.log(sample..": successfully loaded.") +-- Initialize starting position and move vector +local lat = math.random( -80, 80) + math.random(-100000, 100000)/100000 +local lon = math.random(-150, 150) + math.random(-100000, 100000)/100000 +local alt = math.random(15, 8000) +local latmv = math.random(-100, 100)/100000 +local lonmv = math.random(-100, 100)/100000 +if s_lat ~= "random" then lat = s_lat end +if s_lon ~= "random" then lon = s_lon end +if s_alt ~= "random" then alt = s_alt end +fgcom.log("initalized location to: lat="..lat..", lon="..lon..", alt="..alt) +fgcom.log("move vector: latmv="..latmv..", lonmv="..lonmv) -- Connect to server, so we get the API -print(botname..": connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key..")") +fgcom.log("connecting as '"..fgcom.callsign.."' to "..host.." on port "..port.." (cert: "..cert.."; key: "..key.."), joining: '"..fgcom.channel.."'") local client = assert(mumble.connect(host, port, cert, key)) client:auth(fgcom.callsign) -print("connect and bind: OK") +fgcom.dbg("connect and bind: OK") -- function to get all channel users -- this updates the global playback_target table updateAllChannelUsersforSend = function(cl) - --print("udpate channelusers") + --fgcom.dbg("udpate channelusers") local ch = cl:getChannel(fgcom.channel) local users = ch:getUsers() playback_targets = {} - --print("ok: "..ch:getName()) + --fgcom.dbg("ok: "..ch:getName()) for k,v in pairs(users) do - --print(" k",k,"v=",v) + --fgcom.dbg(" k",k,"v=",v) table.insert(playback_targets, v) end end +-- pick a random frequency +pickRandomFrequency = function() + local f = testfrequencies[math.random(1,#testfrequencies)] + local ce = tonumber(math.random(0, 100)/100) + if ce < chance_echotest then + f = "910.00" + end + return f +end + --[[ Playback loop: we use a mumble timer for this. The timer loops in @@ -190,29 +229,20 @@ end he fetches them and plays them, one packet per timer tick. ]] local playbackTimer = mumble.timer() -local freq = testfrequencies[1] playbackTimer_func = function(t) - --print("playback timer: tick; ptt=",ptt) + --fgcom.dbg("playback timer: tick; ptt=",ptt) -- So, a new timer tick started. if ptt then -- PTT is active: setup voice buffer and radio (if not done already) if voiceBuffer:size() <= 0 then - print("fgcom.callsign.. Starting new transmission...") + local freq_desc = "Normal" + if freq == "910.00" then freq_desc = "Echotest" end + fgcom.log("Starting new transmission... @"..freq.." ("..freq_desc..")") -- fill temporary buffer lastHeader, voiceBuffer = readFGCSSampleFile(sample) - if not lastHeader then print("ERROR: '"..sample.."' not readable or no FGCS file") os.exit(1) else print(sample..": successfully refreshed ("..voiceBuffer:size().." samples)") end - - -- choose a random frequency - freq = testfrequencies[math.random(1,#testfrequencies)] - local ce = tonumber(math.random(0, 100)/100) - if ce < chance_echotest then - freq = "910.00" - print(" (Echotest transmission @"..freq..")") - else - print(" (Normal transmission @"..freq..")") - end + if not lastHeader then fgcom.log("ERROR: '"..sample.."' not readable or no FGCS file") os.exit(1) else fgcom.dbg(sample..": successfully refreshed ("..voiceBuffer:size().." samples)") end -- Broadcast radio updateAllChannelUsersforSend(client) @@ -221,34 +251,37 @@ playbackTimer_func = function(t) ..",CHN="..freq ..",PWR=10" ..",PTT=1" - print(fgcom.callsign.." Bot sets radio: "..msg) + fgcom.dbg(fgcom.callsign.." Bot sets radio: "..msg) client:sendPluginData("FGCOM:UPD_COM:0:0", msg, playback_targets) end end -- Play the samples, then stop transmission. if voiceBuffer:size() > 0 then - --print("voiceBuffer is still filled, samples: "..voiceBuffer:size()) + --fgcom.dbg("voiceBuffer is still filled, samples: "..voiceBuffer:size()) -- get the next sample from the buffer and play it local nextSample = voiceBuffer:popleft() local endofStream = false if voiceBuffer:size() == 0 then endofStream = true end - print("transmit next sample @"..freq) - --print(" tgt="..playback_target:getSession()) - print(" eos="..tostring(endofStream)) - print(" cdc="..lastHeader.voicecodec) - print(" dta="..#nextSample.data) - --print(" dta="..nextSample.data) + fgcom.dbg("transmit next sample @"..freq) + --fgcom.dbg(" tgt="..playback_target:getSession()) + fgcom.dbg(" eos="..tostring(endofStream)) + fgcom.dbg(" cdc="..lastHeader.voicecodec) + fgcom.dbg(" dta="..#nextSample.data) + --fgcom.dbg(" dta="..nextSample.data) client:transmit(lastHeader.voicecodec, nextSample.data, not endofStream) -- Transmit the single frame as an audio packet (the bot "speaks") - print(" transmit ok") + fgcom.dbg(" transmit ok") if endofStream then -- no samples left? Just loop around to trigger all the checks - print(fgcom.callsign.." no samples left, playback complete") + fgcom.dbg(" no samples left, playback complete") ptt = false; - + + freq = pickRandomFrequency() -- pick a new frequency for the next transmission + fgcom.log("picked new frequency: "..freq) + -- broadcast radio updateAllChannelUsersforSend(client) if #playback_targets > 0 then @@ -256,19 +289,19 @@ playbackTimer_func = function(t) ..",CHN="..freq ..",PWR=10" ..",PTT=0" - print(" Bot sets radio: "..msg) + fgcom.dbg(" Bot sets radio: "..msg) client:sendPluginData("FGCOM:UPD_COM:0:0", msg, playback_targets) end t:stop() -- Stop the timer - print(fgcom.callsign.." Transmission complete.") + fgcom.log("Transmission complete.") end end else -- PTT is false. -- (This should never be reached, because the only place ptt is reset to false is, if the voicebuffer is empty. Somehow the timer was not stopped...) - print("ERROR: PTT=0 invalid state reached.") + fgcom.log("ERROR: PTT=0 invalid state reached.") t:stop() voiceBuffer = Queue.new() --os.exit(1) @@ -277,29 +310,23 @@ playbackTimer_func = function(t) io.flush() end - -local locUpd = mumble.timer() -local checkTimer = mumble.timer() -local lat = math.random(-150, 150)/100 + math.random(-100000, 100000)/100000 -local lon = math.random(-150, 150)/100 + math.random(-100000, 100000)/100000 -local alt = math.random(15, 8000) -local latmv = math.random(-100, 100)/100000 -local lonmv = math.random(-100, 100)/100000 +local locationUpdateTimer = mumble.timer() +local transmissionCheckTimer = mumble.timer() client:hook("OnServerSync", function(client, event) - print("Sync done; server greeted with: ", event.welcome_text) + fgcom.log("Sync done; server greeted with: ", event.welcome_text) -- try to join fgcom-mumble channel local ch = client:getChannel(fgcom.channel) event.user:move(ch) - print(fgcom.callsign.." joined channel "..fgcom.channel) + fgcom.log(fgcom.callsign.." joined channel "..fgcom.channel) updateAllChannelUsersforSend(client) local msg = "CALLSIGN="..fgcom.callsign client:sendPluginData("FGCOM:UPD_USR:0", msg, playback_targets) - -- update location - locUpd:start(function(t) - --print("locUpd: tick") + -- update location + locationUpdateTimer:start(function(t) + --fgcom.dbg("locationUpdateTimer: tick") -- update current users of channel updateAllChannelUsersforSend(client) if #playback_targets > 0 then @@ -307,28 +334,47 @@ client:hook("OnServerSync", function(client, event) lat = lat + latmv lon = lon + lonmv alt = alt + math.random(-50, 50) + + -- wrap around / limit + if lat < -80 and latmv < 0 then latmv = latmv * -1 end + if lat > 80 and latmv > 0 then latmv = latmv * -1 end + if lon < -180 then lon = lon + 360 end + if lon > 180 then lon = lon - 360 end if alt < 100 then alt = math.abs(alt) end - local msg = "LON="..lat - ..",LAT="..lon + + local msg = "LON="..lon + ..",LAT="..lat ..",ALT="..alt - --print("Bot sets location: "..msg) + --fgcom.dbg("Bot sets location: "..msg) client:sendPluginData("FGCOM:UPD_LOC:0", msg, playback_targets) end end, 0.00, locs) + -- initiate radio stack + freq = pickRandomFrequency() + fgcom.log("picked new frequency: "..freq) + if #playback_targets > 0 then + local msg = "FRQ="..freq + ..",CHN="..freq + ..",PWR=10" + ..",PTT=0" + fgcom.dbg(" Bot sets radio: "..msg) + client:sendPluginData("FGCOM:UPD_COM:0:0", msg, playback_targets) + end + -- let the pilot check every n seconds if he wants to do a transmission - checkTimer:start(function(t) - --print("checkTimer: tick") + transmissionCheckTimer:start(function(t) + --fgcom.dbg("transmissionCheckTimer: tick") local ct = math.random(0, 100)/100 if chance_transmit < ct then -- triggerTransmit, if not already transmitting if not ptt then - --print("activating PTT") + --fgcom.dbg("activating PTT") ptt = true playbackTimer:start(playbackTimer_func, 0.0, lastHeader.samplespeed) else - -- print("(not activating PTT, its still active)") + -- fgcom.dbg("(not activating PTT, its still active)") end end end, 0.00, sleep) @@ -342,11 +388,11 @@ client:hook("OnPluginData", function(client, event) --["receivers"] = { -- A table of who is receiving this data -- [1] = mumble.user, --}, - --print("OnPluginData(): DATA INCOMING FROM="..tostring(event.id)..", "..tostring(event.sender)) + --fgcom.dbg("OnPluginData(): DATA INCOMING FROM="..tostring(event.id)..", "..tostring(event.sender)) -- Answer data requests if event.id:len() > 0 and event.id:find("FGCOM:ICANHAZDATAPLZ") then - print("OnPluginData(): client asks for data: "..tostring(event.sender)) + fgcom.dbg("OnPluginData(): client asks for data: "..tostring(event.sender)) local msg = "CALLSIGN="..fgcom.callsign client:sendPluginData("FGCOM:UPD_USR:0", msg, {event.sender}) @@ -362,12 +408,12 @@ client:hook("OnPluginData", function(client, event) ..",CHN="..freq ..",PWR=10" ..",PTT=0" - client:sendPluginData("FGCOM:UPD_COM:0", msg, {event.sender}) - --event.sender:sendPluginData("FGCOM:UPD_COM:0", msg) + client:sendPluginData("FGCOM:UPD_COM:0:0", msg, {event.sender}) + --event.sender:sendPluginData("FGCOM:UPD_COM:0:0", msg) end end) mumble.loop() -print(botname.." with callsign "..fgcom.callsign.." completed.") +fgcom.log(botname.." with callsign "..fgcom.callsign.." completed.") diff --git a/server/test/fgcom-spamPluginData.bot.lua b/server/test/fgcom-spamPluginData.bot.lua index 18599e9e..8fa79642 100644 --- a/server/test/fgcom-spamPluginData.bot.lua +++ b/server/test/fgcom-spamPluginData.bot.lua @@ -25,7 +25,7 @@ The bot is depending on lua-mumble from bkacjios (https://github.com/bkacjios/l Installation of this plugin is described in the projects readme: https://github.com/bkacjios/lua-mumble/blob/master/README.md ]] -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions playback_targets = {} -- global targets for send diff --git a/server/test/fgcs-test.lua b/server/test/fgcs-test.lua index 389de6a6..3caf93e8 100644 --- a/server/test/fgcs-test.lua +++ b/server/test/fgcs-test.lua @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ]] -dofile("sharedFunctions.inc.lua") -- include shared functions +dofile("fgcom-sharedFunctions.inc.lua") -- include shared functions local botname = "FGOM-Recorder" fgcom.callsign = "FGOM-REC" diff --git a/server/test/loadTest.sh b/server/test/loadTest.sh index 9a61febc..0d921e05 100755 --- a/server/test/loadTest.sh +++ b/server/test/loadTest.sh @@ -4,27 +4,33 @@ # Supposed to be called from the server directory spacing=0.25 -botsleep=30 -host="localhost" -channel="fgcom-mumble" startat=1 numBots=1 if [[ ! -d test ]]; then echo "Please call me from the server directory, thank you" && exit 1; fi -if [[ -z "$1" ]]; then - echo "Usage $0 " +if [ "$#" -lt 3 -o "$1" = "-h" -o "$1" = "--help" ]; then + echo "Usage $0 [... more bot args ...]" + echo " 'more-bot-args' are just passed as-is to the bot instances." + echo " the generated bot-id is passed as --id=\$id." + echo "" + echo "Example: " + echo " # spawn 5 bots (ids: 1-5), 0.25s apart, and jon that host" + echo " $0 1 5 0.25 --host=fgcom.hallinger.org" + echo "--------------" + echo "Bot help:" + luajit test/fgcom-fakepilot.bot.lua --help exit 1 fi if [[ -n $1 ]]; then startat=$1; fi if [[ -n $2 ]]; then numBots=$2; fi if [[ -n $3 ]]; then spacing=$3; fi -if [[ -n $4 ]]; then botsleep=$4; fi -if [[ -n $5 ]]; then host=$5; fi -if [[ -n $6 ]]; then channel=$6; fi +shift; shift; shift +bot_args=$@ function spawnBot { - bcmd="luajit test/fgcom-fakepilot.bot.lua --id=$1 --cert=$2 --key=$3 --sample=$4 --sleep=$6 --host=$7 --channel=$8" + # 1=id, 2=cert, 3=key, 4=sample, 5=log, 6+=more-bot-args + bcmd="luajit test/fgcom-fakepilot.bot.lua --id=$1 --cert=$2 --key=$3 --sample=$4 $6" echo " cmd=$bcmd >$5 2>$5 &" $bcmd >$5 2>$5 & } @@ -41,7 +47,7 @@ for i in $(seq $startat $(expr $startat + $numBots - 1)); do # and his own sample file cp recordings/fgcom.rec.testsample.fgcs /tmp/fgcom.rec.testsample-$i.fgcs - spawnBot $i /tmp/fgcom-bot-$i.pem /tmp/fgcom-bot-$i.key /tmp/fgcom.rec.testsample-$i.fgcs /tmp/fgcom-bot-$i.log $botsleep $host $channel + spawnBot $i /tmp/fgcom-bot-$i.pem /tmp/fgcom-bot-$i.key /tmp/fgcom.rec.testsample-$i.fgcs /tmp/fgcom-bot-$i.log "$bot_args" sleep $spacing done