Skip to content

Commit

Permalink
Change handling of PTT; add support for mumbles internal PTT binding
Browse files Browse the repository at this point in the history
PTT handling was moved from the UDP-Server to the mumble API callback mumble_onUserTalkingStateChanged().
The UDP COMn_PTT messages now just set a "request" bit in the affected radio. This is then evaluated in
fgcom_handlePTT() to see if the plugin should activate the mumble requestMicrophoneActivationOvewrite()
in order to open the mic.
Wehn the mic opens (either by the override, or by mumbles own ptt binding, or by mumbles voice activation),
the actual radio's ptt-state is calculated and (if changed) broadcast to remote clients.

To know which radios should PTT by mumbles ptt/voiceact, the config file was enhanced:
Configured radios will be set to PTT only when mumbles own activation was used, and not if just the
protocol demand mic-opening by the override in fgcom_handlePTT().
This allows us easily to activate the mapping alongside any active protocol ptt-signals; the user thus
can use both. Because of this, COM1 was activated as default in the config.

(Resolves #110)
  • Loading branch information
hbeni committed May 10, 2021
1 parent fabe817 commit e01a2ab
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 47 deletions.
2 changes: 2 additions & 0 deletions README-de_DE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ Bitte beachte, dass "Frequenzen" alles mögliche sein können. Dies bedeutet, da
Dies sollte daher der kleinste gemeinsame Nenner sein, d.h. die physikalische Frequenz der Trägerwelle (vor allem mit 8.33kHz Kanälen, bei denen die im Gerät angewählte Frequenz nicht immer der physikalischen entspricht).
Im Protokoll haben Fließkommazahlen außerdem immer den Punkt (`.`) als Dezimaltrenner; das Komma ist als Feldtrenner nicht erlaubt.

Obwohl wir davon ausgehen, dass die verbundenen Simulatoren Informationen für das PTT der Funkgeräte übermitteln, kannst du über die Konfigurationsdatei Zuordnungen für mumble's interne Sendeaktivierung definieren. Auf diese Weise kannst du beispielsweise mit mumbles eigenem PTT-Tastenkürzel das Senden deiner Funkgeräte aktivieren. Standardmäßig ist bereits das erste Funkgerät entsprechend konfiguriert, d.h. mumbles internes PTT aktiviert gleichzeitig das PTT des ersten Funkgerätes.


### RadioGUI
FGCom-mumble liefert eine plattformunabhängige Java-Applikation mit, die die meisten UDP-Protokollfelder implementiert. Dadurch eignet sich RadioGUI nicht nur zum testen, sondern auch für echte Aufgaben wie ATC ohne die Notwendigkeit eines weiteren Clients.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ The plugin aims to be compatible to the legacy fgcom-standalone protocol, so vey
Note that frequencies can be arbitary strings. That said, all participating clients must share a common definition of "frequency", this should be the physical radio wave frequency in MHz and not the "channel" (esp. with 8.3 channels spacing).
Also note that callsigns and frequencies are not allowed to contain the comma symbol (`,`). Decimal point symbol has always to be a point (`.`).

Despite we expect the connected simulator to provide PTT-information in order to activate radio transmissions, you may also use the configfile to define mappings for mumble's internal voice activation. This way, you can use mumbles own PTT-binding to activate the radios you mapped. By default, the first Radio is already mapped for your convinience.


### RadioGUI
FGCom-mumble releases ship with a cross-plattform java application that implements most of the UDP protocol and thus can be used not only for testing purposes, but also real operations without the need for another client.
Expand Down
106 changes: 96 additions & 10 deletions client/mumble-plugin/fgcom-mumble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,17 @@ bool fgcom_isPluginActive() {
}

/*
* Handle PTT change of local user
*
* Handle UDP protocol PTT-Request change of local user
*
* This will check the local radio state and activate the mic if all is operable.
* When no PTT or no radio is operable, mic is closed.
*
* Note: Opening the mic this way will trigger mumble_onUserTalkingStateChanged() which will
* calculate the to-be-synced PTT state to remotes.
*/
void fgcom_handlePTT() {
if (fgcom_isPluginActive()) {
pluginDbg("Handling PTT state");
pluginDbg("Handling PTT protocol request state");
// see which radio was used and if its operational.
bool radio_serviceable, radio_powered, radio_switchedOn, radio_ptt;
bool radio_ptt_result = false; // if we should open or close the mic, default no
Expand All @@ -142,19 +145,19 @@ void fgcom_handlePTT() {
fgcom_client lcl = lcl_idty.second;
if (lcl.radios.size() > 0) {
for (int i=0; i<lcl.radios.size(); i++) {
radio_ptt = lcl.radios[i].ptt;
radio_ptt = lcl.radios[i].ptt_req;

if (radio_ptt) {
//if (radio_serviceable && radio_switchedOn && radio_powered) {
if ( lcl.radios[i].operable) {
pluginDbg(" COM"+std::to_string(i+1)+" PTT active and radio is operable -> open mic");
pluginDbg(" COM"+std::to_string(i+1)+" PTT_REQ active and radio is operable -> open mic");
radio_ptt_result = true;
break; // we only have one output stream, so further search makes no sense
} else {
pluginLog(" COM"+std::to_string(i+1)+" PTT active but radio not operable!");
pluginLog(" COM"+std::to_string(i+1)+" PTT_REQ active but radio not operable!");
}
} else {
pluginDbg(" COM"+std::to_string(i+1)+" PTT off");
pluginDbg(" COM"+std::to_string(i+1)+" PTT_REQ off");
}
}
}
Expand All @@ -168,7 +171,7 @@ void fgcom_handlePTT() {
// Todo: do we need to reset something or so? i think no:
// plugin deactivation will already handle setting the old transmission mode,
// so the mic will be open according to that...
pluginDbg("Handling PTT state: PLUGIN NOT ACTIVE");
pluginDbg("Handling PTT protocol request state: PLUGIN NOT ACTIVE");
}
}

Expand Down Expand Up @@ -297,19 +300,39 @@ mumble_error_t fgcom_loadConfig() {
std::string token_value = sm[2];
pluginDbg("[CFG] Parsing token: "+token_key+"="+token_value);

if (token_key == "radioAudioEffects") fgcom_cfg.radioAudioEffects = (token_value == "0" || token_value == "false" || token_value == "off")? false : true;
if (token_key == "allowHearingNonPluginUsers") fgcom_cfg.allowHearingNonPluginUsers = (token_value == "1" || token_value == "true" || token_value == "on")? true : false;
if (token_key == "radioAudioEffects") fgcom_cfg.radioAudioEffects = (token_value == "0" || token_value == "false" || token_value == "off" || token_value == "no")? false : true;
if (token_key == "allowHearingNonPluginUsers") fgcom_cfg.allowHearingNonPluginUsers = (token_value == "1" || token_value == "true" || token_value == "on" || token_value == "yes")? true : false;
if (token_key == "specialChannel") fgcom_cfg.specialChannel = token_value;
if (token_key == "udpServerHost") fgcom_cfg.udpServerHost = token_value;
if (token_key == "udpServerPort") fgcom_cfg.udpServerPort = std::stoi(token_value);
if (token_key == "logfile") fgcom_cfg.logfile = token_value;

std::smatch sm_m;
std::regex re_mblmap ("^mapMumblePTT(\\d+)$");
if (std::regex_search(token_key, sm_m, re_mblmap)) {
int radio_id = std::stoi(sm_m[1]);
radio_id--; // convert to array index
fgcom_cfg.mapMumblePTT[radio_id] = (token_value == "1" || token_value == "true" || token_value == "on" || token_value == "yes")? true : false;
}
}
}
} else {
pluginLog("[CFG] not using '"+cfgFilePath+"' (not existing or invalid format)");
}
}

// Debug print final parsed config
pluginDbg("[CFG] final parsed config:");
pluginDbg("[CFG] allowHearingNonPluginUsers="+std::to_string(fgcom_cfg.allowHearingNonPluginUsers));
pluginDbg("[CFG] radioAudioEffects="+std::to_string(fgcom_cfg.radioAudioEffects));
pluginDbg("[CFG] specialChannel="+fgcom_cfg.specialChannel);
pluginDbg("[CFG] udpServerHost="+fgcom_cfg.udpServerHost);
pluginDbg("[CFG] udpServerPort="+std::to_string(fgcom_cfg.udpServerPort));
pluginDbg("[CFG] logfile="+fgcom_cfg.logfile);
for (const auto& cv : fgcom_cfg.mapMumblePTT) {
pluginDbg("[CFG] mapMumblePTT["+std::to_string(cv.first)+"]="+std::to_string(cv.second));
}

return STATUS_OK;
}

Expand Down Expand Up @@ -769,6 +792,69 @@ void mumble_onChannelExited(mumble_connection_t connection, mumble_userid_t user

}

// Called when any user changes his/her talking state.
// Handles the calculation of the PTT state that is sent to remotes.
void mumble_onUserTalkingStateChanged(mumble_connection_t connection, mumble_userid_t userID, mumble_talking_state_t talkingState) {
pluginDbg("User with ID "+ std::to_string(userID) + " changed talking state: " + std::to_string(talkingState) + ". (ServerConnection: " + std::to_string(connection) + ")");

if (userID == localMumId && fgcom_isPluginActive() ){
// Current user is speaking. Either this activated trough the PTT button, or manually pushed mumble-ptt/voiceActivation
bool mumble_talk_detected = talkingState == TALKING || talkingState == WHISPERING || talkingState == SHOUTING;

// look if there is some PTT_REQ set.
// If we have a PTT requested from the udp protocol, we are not activating the
// radios configured to respond to mumbles talk state change.
bool udp_protocol_ptt_detected = false;
for (const auto &lcl_idty : fgcom_local_client) {
int iid = lcl_idty.first;
fgcom_client lcl = lcl_idty.second;
if (lcl.radios.size() > 0) {
for (int radio_id=0; radio_id<lcl.radios.size(); radio_id++) {
if (lcl.radios[radio_id].ptt_req) udp_protocol_ptt_detected = true;
}
}
}

// update identities radios depending on config options
fgcom_localcfg_mtx.lock();
pluginDbg(" checking identities/radios for local users PTT...");
for (const auto &lcl_idty : fgcom_local_client) {
int iid = lcl_idty.first;
fgcom_client lcl = lcl_idty.second;
if (lcl.radios.size() > 0) {
for (int radio_id=0; radio_id<lcl.radios.size(); radio_id++) {
bool radio_ptt_req = lcl.radios[radio_id].ptt_req; // requested from UDP state
auto radio_mapmumbleptt_srch = fgcom_cfg.mapMumblePTT.find(radio_id);
bool radio_mapmumbleptt = (radio_mapmumbleptt_srch != fgcom_cfg.mapMumblePTT.end())? radio_mapmumbleptt_srch->second : false;
pluginDbg(" IID="+std::to_string(iid)+"; radio_id="+std::to_string(radio_id)+"; operable="+std::to_string(lcl.radios[radio_id].operable));
pluginDbg(" radio_ptt_req="+std::to_string(radio_ptt_req));
pluginDbg(" radio_mapmumbleptt="+std::to_string(radio_mapmumbleptt));
for (const auto& cv : fgcom_cfg.mapMumblePTT) {
pluginDbg(" mapMumblePTT["+std::to_string(cv.first)+"]="+std::to_string(cv.second));
}

bool oldValue = fgcom_local_client[iid].radios[radio_id].ptt;
bool newValue = false;
pluginDbg(" old_ptt="+std::to_string(oldValue));
pluginDbg(" mumble_talk_detected="+std::to_string(mumble_talk_detected));
if ( radio_ptt_req || !udp_protocol_ptt_detected && radio_mapmumbleptt ) {
// We should activate/deactivate PTT on the radio; either it's ptt was pressed in the UDP client, or we are configured for honoring mumbles talk state
newValue = mumble_talk_detected && lcl.radios[radio_id].operable;
}
pluginDbg(" new_ptt="+std::to_string(lcl.radios[radio_id].ptt));

// broadcast changed PTT state to clients
fgcom_local_client[iid].radios[radio_id].ptt = newValue;
if (oldValue != newValue) {
pluginDbg(" COM"+std::to_string(radio_id+1)+" PTT changed: notifying remotes");
notifyRemotes(iid, NTFY_COM, radio_id);
}
}
}
}
fgcom_localcfg_mtx.unlock();
}
}

// Note: Audio input is only possible with open mic. fgcom_hanldePTT() takes care of that.
bool mumble_onAudioInput(short *inputPCM, uint32_t sampleCount, uint16_t channelCount, bool isSpeech) {
Expand Down
4 changes: 2 additions & 2 deletions client/mumble-plugin/fgcom-mumble.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

// Plugin Version
#define FGCOM_VERSION_MAJOR 0
#define FGCOM_VERSION_MINOR 12
#define FGCOM_VERSION_PATCH 1
#define FGCOM_VERSION_MINOR 13
#define FGCOM_VERSION_PATCH 0

/*
* Is the plugin currently active?
Expand Down
15 changes: 15 additions & 0 deletions client/mumble-plugin/fgcom-mumble.ini
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@
;allowHearingNonPluginUsers=0


;; Use mumbles Talking state to activate PTT of your radios.
;; With this setting you can map mumbles talk-activation (mumble's ptt button or
;; voice activation) to your radio's virtual ptt buttons. Activation of mumbles ptt
;; will then also activate PTT on the configured radios.
;;
;; Note: Usually the connected client is expected to send COMn_PTT=1 packets to signify
;; that the PTT button of a specific radio was pressed. Some clients however
;; don't do that, which makes this mappings here the only way to signify PTT.
;;
;; You can define multiple mappings in the form mapMumblePTT{N}, where {N} is the
;; ID of the radio you want to map (eg. "mapMumblePTT1" maps COM1).
;mapMumblePTT1=1
;mapMumblePTT2=0


;; FGCom channel name(s)
;; The plugin will activate radio channel handling when inside this channel(s).
;; The parameter is a default ECMA regular expression (case ignore) and will match channel
Expand Down
1 change: 1 addition & 0 deletions client/mumble-plugin/lib/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void debug_out_internal_state() {
state_str += " Radio "+std::to_string(i)+": serviceable='"+std::to_string(lcl.radios[i].serviceable)+"'\n";
state_str += " Radio "+std::to_string(i)+": operable='"+std::to_string(lcl.radios[i].operable)+"'\n";
state_str += " Radio "+std::to_string(i)+": ptt='"+std::to_string(lcl.radios[i].ptt)+"'\n";
state_str += " Radio "+std::to_string(i)+": ptt_req='"+std::to_string(lcl.radios[i].ptt_req)+"'\n";
state_str += " Radio "+std::to_string(i)+": volume='"+std::to_string(lcl.radios[i].volume)+"'\n";
state_str += " Radio "+std::to_string(i)+": pwr='"+std::to_string(lcl.radios[i].pwr)+"'\n";
state_str += " Radio "+std::to_string(i)+": squelch='"+std::to_string(lcl.radios[i].squelch)+"'\n";
Expand Down
2 changes: 2 additions & 0 deletions client/mumble-plugin/lib/globalVars.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct fgcom_config {
std::string udpServerHost;
int udpServerPort;
std::string logfile;
std::map<int, bool> mapMumblePTT; // which radios to activate when mumble-internal talk activation is used

fgcom_config() {
allowHearingNonPluginUsers = false;
Expand All @@ -48,6 +49,7 @@ struct fgcom_config {
udpServerHost = "127.0.0.1";
udpServerPort = 16661;
logfile = "";
mapMumblePTT = {{0,true}};
};
};
extern struct fgcom_config fgcom_cfg;
Expand Down
28 changes: 16 additions & 12 deletions client/mumble-plugin/lib/io_UDPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,9 @@ std::map<int, fgcom_udp_parseMsg_result> fgcom_udp_parseMsg(char buffer[MAXLINE]
if (parsedPTT && fgcom_com_ptt_compatmode) fgcom_com_ptt_compatmode = false;

if (!fgcom_com_ptt_compatmode) {
bool oldValue = fgcom_local_client[iid].radios[radio_id].ptt;
fgcom_local_client[iid].radios[radio_id].ptt = parsedPTT;
if (fgcom_local_client[iid].radios[radio_id].ptt != oldValue ) parseResult[iid].radioData.insert(radio_id);
bool oldValue = fgcom_local_client[iid].radios[radio_id].ptt_req;
fgcom_local_client[iid].radios[radio_id].ptt_req = parsedPTT;
//if (fgcom_local_client[iid].radios[radio_id].ptt_req != oldValue ) parseResult[iid].radioData.insert(radio_id);
needToHandlePTT = true;
}

Expand Down Expand Up @@ -338,6 +338,10 @@ std::map<int, fgcom_udp_parseMsg_result> fgcom_udp_parseMsg(char buffer[MAXLINE]
fgcom_local_client[iid].radios[radio_id].publish = (token_value == "1" || token_value == "true")? true : false;
// must never be sended - it's a local config property
}
if (radio_var == "MAPMUMBLEPTT") {
fgcom_cfg.mapMumblePTT[radio_id] = (token_value == "1" || token_value == "true" || token_value == "on" || token_value == "yes")? true : false;
// must never be sended - it's a local config property
}

}

Expand Down Expand Up @@ -385,18 +389,18 @@ std::map<int, fgcom_udp_parseMsg_result> fgcom_udp_parseMsg(char buffer[MAXLINE]
if (ptt_id > 0) fgcom_com_ptt_compatmode = true;

if (fgcom_com_ptt_compatmode) {
pluginDbg("DBG_PTT: ptt_id="+std::to_string(ptt_id));
//pluginDbg("DBG_PTT: ptt_id="+std::to_string(ptt_id));
for (int i = 0; i<fgcom_local_client[iid].radios.size(); i++) {
pluginDbg("DBG_PTT: check i("+std::to_string(i)+")==ptt_id-1("+std::to_string(ptt_id-1)+")");
//pluginDbg("DBG_PTT: check i("+std::to_string(i)+")==ptt_id-1("+std::to_string(ptt_id-1)+")");
if (i == ptt_id - 1) {
if (fgcom_local_client[iid].radios[i].ptt != 1){
parseResult[iid].radioData.insert(i);
fgcom_local_client[iid].radios[i].ptt = 1;
if (fgcom_local_client[iid].radios[i].ptt_req != 1){
//parseResult[iid].radioData.insert(i);
fgcom_local_client[iid].radios[i].ptt_req = 1;
}
} else {
if (fgcom_local_client[iid].radios[i].ptt == 1){
parseResult[iid].radioData.insert(i);
fgcom_local_client[iid].radios[i].ptt = 0;
if (fgcom_local_client[iid].radios[i].ptt_req == 1){
//parseResult[iid].radioData.insert(i);
fgcom_local_client[iid].radios[i].ptt_req = 0;
}
}
}
Expand Down Expand Up @@ -673,7 +677,7 @@ void fgcom_spawnUDPServer() {
for (std::set<int>::iterator it=ures.radioData.begin(); it!=ures.radioData.end(); ++it) {
// iterate trough changed radio instances
//std::cout << "ITERATOR: " << ' ' << *it;
pluginDbg("FGCom: [UDP-server] radioData for iid='"+std::to_string(iid)+"', radio_id="+std::to_string(*it)+" has changed, notifying other clients");
pluginDbg("[UDP-server] radioData for iid='"+std::to_string(iid)+"', radio_id="+std::to_string(*it)+" has changed, notifying other clients");
notifyRemotes(iid, NTFY_COM, *it);
fgcom_updateClientComment();
}
Expand Down
Loading

0 comments on commit e01a2ab

Please sign in to comment.