diff --git a/RDFPlugin/CRDFPlugin.cpp b/RDFPlugin/CRDFPlugin.cpp index 6ef552c..7d70541 100644 --- a/RDFPlugin/CRDFPlugin.cpp +++ b/RDFPlugin/CRDFPlugin.cpp @@ -6,6 +6,8 @@ using namespace std; #define VECTORAUDIO_PARAM_VERSION "/*" #define VECTORAUDIO_PARAM_TRANSMIT "/transmitting" +#define VECTORAUDIO_PARAM_TX "/tx" +#define VECTORAUDIO_PARAM_RX "/rx" #define SETTING_VECTORAUDIO_ADDRESS "VectorAudioAddress" #define SETTING_VECTORAUDIO_TIMEOUT "VectorAudioTimeout" @@ -27,6 +29,13 @@ const double EarthRadius = 3438.0; // nautical miles, referred to internal CEuro constexpr double GEOM_RAD_FROM_DEG(double deg) { return deg * pi / 180.0; }; constexpr double GEOM_DEG_FROM_RAD(double rad) { return rad / pi * 180.0; }; +inline int FrequencyConvert(double freq) { // frequency * 1000 => int + return round(freq * 1000.0); +} +inline bool FrequencyCompare(int freq1, int freq2) { // return true if same frequency, frequency *= 1000 + return abs(freq1 - freq2) <= 10; +} + CRDFPlugin::CRDFPlugin() : EuroScopePlugIn::CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, MY_PLUGIN_NAME.c_str(), @@ -34,9 +43,9 @@ CRDFPlugin::CRDFPlugin() MY_PLUGIN_DEVELOPER.c_str(), MY_PLUGIN_COPYRIGHT.c_str()) { - RegisterClass(&this->windowClass); - - this->hiddenWindow = CreateWindow( + // RDF window + RegisterClass(&this->windowClassRDF); + this->hiddenWindowRDF = CreateWindow( "RDFHiddenWindowClass", "RDFHiddenWindow", NULL, @@ -49,9 +58,27 @@ CRDFPlugin::CRDFPlugin() GetModuleHandle(NULL), reinterpret_cast(this) ); + if (GetLastError() != S_OK) { + DisplayWarnMessage("Unable to open communications for RDF"); + } + // AFV bridge window + RegisterClass(&this->windowClassAFV); + this->hiddenWindowAFV = CreateWindow( + "AfvBridgeHiddenWindowClass", + "AfvBridgeHiddenWindow", + NULL, + 0, + 0, + 0, + 0, + NULL, + NULL, + GetModuleHandle(NULL), + reinterpret_cast(this) + ); if (GetLastError() != S_OK) { - DisplayEuroScopeMessage("Unable to open communications for RDF plugin"); + DisplayWarnMessage("Unable to open communications for AFV bridge"); } LoadSettings(); @@ -60,32 +87,42 @@ CRDFPlugin::CRDFPlugin() this->disBearing = uniform_real_distribution<>(0.0, 360.0); this->disDistance = normal_distribution<>(0, 1.0); - DisplayEuroScopeMessage(string("Version " + MY_PLUGIN_VERSION + " loaded")); + DisplayInfoMessage(string("Version " + MY_PLUGIN_VERSION + " loaded")); // detach thread for VectorAudio - VectorAudioTransmission = new thread(&CRDFPlugin::VectorAudioHTTPLoop, this); - VectorAudioTransmission->detach(); + threadVectorAudioMain = new thread(&CRDFPlugin::VectorAudioMainLoop, this); + threadVectorAudioMain->detach(); + threadVectorAudioTXRX = new thread(&CRDFPlugin::VectorAudioTXRXLoop, this); + threadVectorAudioTXRX->detach(); } CRDFPlugin::~CRDFPlugin() { // close detached thread - threadRunning = false; - threadClosed.wait(false); + threadMainRunning = false; + threadTXRXRunning = false; - if (this->hiddenWindow != NULL) { - DestroyWindow(this->hiddenWindow); + if (this->hiddenWindowRDF != NULL) { + DestroyWindow(this->hiddenWindowRDF); } UnregisterClass("RDFHiddenWindowClass", NULL); + + if (this->hiddenWindowAFV != NULL) { + DestroyWindow(this->hiddenWindowAFV); + } + UnregisterClass("AfvBridgeHiddenWindowClass", NULL); + + threadMainClosed.wait(false); + threadTXRXClosed.wait(false); } -void CRDFPlugin::ProcessAFVMessage(std::string message) +void CRDFPlugin::HiddenWndProcessRDFMessage(std::string message) { { std::lock_guard lock(this->messageLock); if (message.size()) { - DisplayEuroScopeDebugMessage(string("AFV message: ") + message); + DisplayDebugMessage(string("AFV message: ") + message); set strings; istringstream f(message); string s; @@ -98,7 +135,79 @@ void CRDFPlugin::ProcessAFVMessage(std::string message) this->messages.push(set()); } } - ProcessMessageQueue(); + ProcessRDFQueue(); +} + +void CRDFPlugin::HiddenWndProcessAFVMessage(string message) +{ + // functions as AFV bridge + if (!message.size()) return; + DisplayDebugMessage(string("AFV message: ") + message); + // format: xxx.xxx:True:False + xxx.xx0:True:False + + // parse message + queue strings; + istringstream f(message); + string s; + while (getline(f, s, ':')) { + strings.push(s); + } + if (strings.size() != 3) return; // in case of incomplete message + int msgFrequency; + bool transmitX, receiveX; + try { + msgFrequency = FrequencyConvert(stod(strings.front())); + strings.pop(); + receiveX = strings.front() == "True"; + strings.pop(); + transmitX = strings.front() == "True"; + strings.pop(); + } + catch (...) { + DisplayDebugMessage("Error when parsing AFV message: " + message); + return; + } + + // abort if frequency is prim + if (FrequencyCompare(msgFrequency, FrequencyConvert(ControllerMyself().GetPrimaryFrequency()))) + return; + + // match frequency to callsign + string msgCallsign = ControllerMyself().GetCallsign(); + for (auto c = ControllerSelectFirst(); c.IsValid(); c = ControllerSelectNext(c)) { + if (FrequencyCompare(FrequencyConvert(c.GetPrimaryFrequency()), msgFrequency)) { + msgCallsign = c.GetCallsign(); + break; + } + } + + // find channel and toggle + for (auto c = GroundToArChannelSelectFirst(); c.IsValid(); c = GroundToArChannelSelectNext(c)) { + int chFreq = FrequencyConvert(c.GetFrequency()); + if (c.GetIsPrimary() || c.GetIsAtis() || !FrequencyCompare(chFreq, msgFrequency)) + continue; + string chName = c.GetName(); + if (msgCallsign == chName) { + ToggleChannels(c, transmitX, receiveX); + return; + } + else { + size_t posc = msgCallsign.find('_'); + if (chName.starts_with(msgCallsign.substr(0, posc))) { + ToggleChannels(c, transmitX, receiveX); + return; + } + } + } + + // no possible match, toggle the first same frequency + for (auto c = GroundToArChannelSelectFirst(); c.IsValid(); c = GroundToArChannelSelectNext(c)) { + if (!c.GetIsPrimary() && !c.GetIsAtis() && + FrequencyCompare(FrequencyConvert(c.GetFrequency()), msgFrequency)) { + ToggleChannels(c, transmitX, receiveX); + return; + } + } } void CRDFPlugin::GetRGB(COLORREF& color, const char* settingValue) @@ -106,7 +215,7 @@ void CRDFPlugin::GetRGB(COLORREF& color, const char* settingValue) unsigned int r, g, b; sscanf_s(settingValue, "%u:%u:%u", &r, &g, &b); if (r <= 255 && g <= 255 && b <= 255) { - DisplayEuroScopeDebugMessage(string("R: ") + to_string(r) + string(" G: ") + to_string(g) + string(" B: ") + to_string(b)); + DisplayDebugMessage(string("R: ") + to_string(r) + string(" G: ") + to_string(g) + string(" B: ") + to_string(b)); color = RGB(r, g, b); } } @@ -141,7 +250,7 @@ void CRDFPlugin::LoadSettings(void) if (cstrAddrVA != NULL) { addressVectorAudio = cstrAddrVA; - DisplayEuroScopeDebugMessage(string("Address: ") + addressVectorAudio); + DisplayDebugMessage(string("Address: ") + addressVectorAudio); } const char* cstrTimeout = GetDataFromSettings(SETTING_VECTORAUDIO_TIMEOUT); @@ -150,7 +259,7 @@ void CRDFPlugin::LoadSettings(void) int parsedTimeout = atoi(cstrTimeout); if (parsedTimeout >= 100 && parsedTimeout <= 1000) { connectionTimeout = parsedTimeout; - DisplayEuroScopeDebugMessage(string("Timeout: ") + to_string(connectionTimeout)); + DisplayDebugMessage(string("Timeout: ") + to_string(connectionTimeout)); } } @@ -160,7 +269,7 @@ void CRDFPlugin::LoadSettings(void) int parsedInterval = atoi(cstrPollInterval); if (parsedInterval >= 100) { pollInterval = parsedInterval; - DisplayEuroScopeDebugMessage(string("Poll interval: ") + to_string(pollInterval)); + DisplayDebugMessage(string("Poll interval: ") + to_string(pollInterval)); } } @@ -170,7 +279,7 @@ void CRDFPlugin::LoadSettings(void) int parsedInterval = atoi(cstrRetryInterval); if (parsedInterval >= 1) { retryInterval = parsedInterval; - DisplayEuroScopeDebugMessage(string("Retry interval: ") + to_string(retryInterval)); + DisplayDebugMessage(string("Retry interval: ") + to_string(retryInterval)); } } @@ -192,7 +301,7 @@ void CRDFPlugin::LoadSettings(void) int parsedRadius = atoi(cstrRadius); if (parsedRadius > 0) { circleRadius = parsedRadius; - DisplayEuroScopeDebugMessage(string("Radius: ") + to_string(circleRadius)); + DisplayDebugMessage(string("Radius: ") + to_string(circleRadius)); } } @@ -200,7 +309,7 @@ void CRDFPlugin::LoadSettings(void) if (cstrThreshold != NULL) { circleThreshold = atoi(cstrThreshold); - DisplayEuroScopeDebugMessage(string("Threshold: ") + to_string(circleThreshold)); + DisplayDebugMessage(string("Threshold: ") + to_string(circleThreshold)); } const char* cstrPrecision = GetDataFromSettings(SETTING_PRECISION); @@ -209,7 +318,7 @@ void CRDFPlugin::LoadSettings(void) int parsedPrecision = atoi(cstrPrecision); if (parsedPrecision >= 0) { circlePrecision = parsedPrecision; - DisplayEuroScopeDebugMessage(string("Precision: ") + to_string(circlePrecision)); + DisplayDebugMessage(string("Precision: ") + to_string(circlePrecision)); } } @@ -218,7 +327,7 @@ void CRDFPlugin::LoadSettings(void) { int parsedAlt = atoi(cstrLowAlt); lowAltitude = parsedAlt; - DisplayEuroScopeDebugMessage(string("Low Altitude: ") + to_string(lowAltitude)); + DisplayDebugMessage(string("Low Altitude: ") + to_string(lowAltitude)); } const char* cstrHighAlt = GetDataFromSettings(SETTING_HIGH_ALTITUDE); @@ -227,7 +336,7 @@ void CRDFPlugin::LoadSettings(void) int parsedAlt = atoi(cstrHighAlt); if (parsedAlt > 0) { highAltitude = parsedAlt; - DisplayEuroScopeDebugMessage(string("High Altitude: ") + to_string(highAltitude)); + DisplayDebugMessage(string("High Altitude: ") + to_string(highAltitude)); } } @@ -237,7 +346,7 @@ void CRDFPlugin::LoadSettings(void) int parsedPrecision = atoi(cstrLowPrecision); if (parsedPrecision >= 0) { lowPrecision = parsedPrecision; - DisplayEuroScopeDebugMessage(string("Low Precision: ") + to_string(lowPrecision)); + DisplayDebugMessage(string("Low Precision: ") + to_string(lowPrecision)); } } @@ -247,7 +356,7 @@ void CRDFPlugin::LoadSettings(void) int parsedPrecision = atoi(cstrHighPrecision); if (parsedPrecision >= 0) { highPrecision = parsedPrecision; - DisplayEuroScopeDebugMessage(string("High Precision: ") + to_string(highPrecision)); + DisplayDebugMessage(string("High Precision: ") + to_string(highPrecision)); } } @@ -255,21 +364,21 @@ void CRDFPlugin::LoadSettings(void) if (cstrController != NULL) { drawController = (bool)atoi(cstrController); - DisplayEuroScopeDebugMessage(string("Draw controllers and observers: ") + to_string(drawController)); + DisplayDebugMessage(string("Draw controllers and observers: ") + to_string(drawController)); } } catch (std::runtime_error const& e) { - DisplayEuroScopeMessage(string("Error: ") + e.what()); + DisplayWarnMessage(string("Error: ") + e.what()); } catch (...) { - DisplayEuroScopeMessage(string("Unexpected error: ") + to_string(GetLastError())); + DisplayWarnMessage(string("Unexpected error: ") + to_string(GetLastError())); } } -void CRDFPlugin::ProcessMessageQueue(void) +void CRDFPlugin::ProcessRDFQueue(void) { std::lock_guard lock(this->messageLock); // Process all incoming messages @@ -318,11 +427,6 @@ void CRDFPlugin::ProcessMessageQueue(void) double distance = abs(disDistance(rdGenerator)) / 3.0 * offset; double bearing = disBearing(rdGenerator); AddOffset(posnew, bearing, distance); -#ifdef _DEBUG - double _dis = pos.DistanceTo(posnew); - double _dir = pos.DirectionTo(posnew); - DisplayEuroScopeDebugMessage("d: " + to_string(_dis) + "/" + to_string(distance) + " a: " + to_string(_dir) + "/" + to_string(bearing)); -#endif // _DEBUG } activeTransmittingPilots[callsign] = { posnew, radius }; } @@ -339,6 +443,85 @@ void CRDFPlugin::ProcessMessageQueue(void) } } +void CRDFPlugin::UpdateVectorAudioChannels(string line, bool mode_tx) +{ + // parse message and returns number of total toggles + map channelFreq; + istringstream ssLine(line); + string strChnl; + int freqMe = FrequencyConvert(ControllerMyself().GetPrimaryFrequency()); + while (getline(ssLine, strChnl, ',')) { + size_t colon = strChnl.find(':'); + string channel = strChnl.substr(0, colon); + try { + int frequency = FrequencyConvert(stod(strChnl.substr(colon + 1))); + if (!FrequencyCompare(freqMe, frequency)) { + channelFreq.insert({ channel, frequency }); + } + } + catch (...) { + DisplayDebugMessage("Error when parsing frequencies: " + strChnl); + continue; + } + } + + for (auto chnl = GroundToArChannelSelectFirst(); chnl.IsValid(); chnl = GroundToArChannelSelectNext(chnl)) { + if (chnl.GetIsPrimary() || chnl.GetIsAtis()) { // make sure primary and ATIS are not affected + continue; + } + string chName = chnl.GetName(); + int chFreq = FrequencyConvert(chnl.GetFrequency()); + auto it = channelFreq.find(chName); + if (it != channelFreq.end() && FrequencyCompare(it->second, chFreq)) { // allows 0.010 of deviation + channelFreq.erase(it); + goto _toggle_on; + } + else if (it == channelFreq.end()) { + auto itc = channelFreq.begin(); + for (; itc != channelFreq.end() && !FrequencyCompare(itc->second, chFreq); itc++); // locate a matching freq + if (itc != channelFreq.end()) { + size_t posi = itc->first.find('_'); + if (chName.starts_with(itc->first.substr(0, posi))) { + channelFreq.erase(itc); + goto _toggle_on; + } + } + } + // toggle off + if (mode_tx) { + ToggleChannels(chnl, 0, -1); + } + else { + ToggleChannels(chnl, -1, 0); + } + continue; + _toggle_on: + if (mode_tx) { + ToggleChannels(chnl, 1, -1); + } + else { + ToggleChannels(chnl, -1, 1); + } + } +} + +void CRDFPlugin::ToggleChannels(CGrountToAirChannel Channel, int tx, int rx) +{ + // pass tx/rx = -1 to skip + if (tx >= 0 && tx != (int)Channel.GetIsTextTransmitOn()) { + Channel.ToggleTextTransmit(); + DisplayDebugMessage( + string("TX toggle: ") + Channel.GetName() + " frequency: " + to_string(Channel.GetFrequency()) + ); + } + if (rx >= 0 && rx != (int)Channel.GetIsTextReceiveOn()) { + Channel.ToggleTextReceive(); + DisplayDebugMessage( + string("RX toggle: ") + Channel.GetName() + " frequency: " + to_string(Channel.GetFrequency()) + ); + } +} + void CRDFPlugin::AddOffset(CPosition& position, double heading, double distance) { // from ES internal void CEuroScopeCoord :: Move ( double heading, double distance ) @@ -360,21 +543,21 @@ void CRDFPlugin::AddOffset(CPosition& position, double heading, double distance) position.m_Longitude = GEOM_DEG_FROM_RAD(lambda2); } -void CRDFPlugin::VectorAudioHTTPLoop(void) +void CRDFPlugin::VectorAudioMainLoop(void) { - threadRunning = true; - threadClosed = false; + threadMainRunning = true; + threadMainClosed = false; bool getTransmit = false; while (true) { for (int sleepRemain = getTransmit ? pollInterval : retryInterval * 1000; sleepRemain > 0;) { - if (threadRunning) { + if (threadMainRunning) { int sleepThis = min(sleepRemain, pollInterval); this_thread::sleep_for(chrono::milliseconds(sleepThis)); sleepRemain -= sleepThis; } else { - threadClosed = true; - threadClosed.notify_all(); + threadMainClosed = true; + threadMainClosed.notify_all(); return; } } @@ -383,9 +566,9 @@ void CRDFPlugin::VectorAudioHTTPLoop(void) cli.set_connection_timeout(0, connectionTimeout * 1000); if (auto res = cli.Get(getTransmit ? VECTORAUDIO_PARAM_TRANSMIT : VECTORAUDIO_PARAM_VERSION)) { if (res->status == 200) { - DisplayEuroScopeDebugMessage(string("VectorAudio message: ") + res->body); + DisplayDebugMessage(string("VectorAudio message: ") + res->body); if (!getTransmit) { - DisplayEuroScopeMessage("Connected to " + res->body); + DisplayInfoMessage("Connected to " + res->body); getTransmit = true; continue; } @@ -404,28 +587,88 @@ void CRDFPlugin::VectorAudioHTTPLoop(void) this->messages.push(strings); } } - ProcessMessageQueue(); + ProcessRDFQueue(); continue; } - DisplayEuroScopeDebugMessage("HTTP error: " + httplib::to_string(res.error())); + DisplayDebugMessage("HTTP error on MAIN: " + httplib::to_string(res.error())); } else { - DisplayEuroScopeDebugMessage("Not connected"); + DisplayDebugMessage("Not connected"); } if (getTransmit) { - DisplayEuroScopeMessage("VectorAudio disconnected"); + DisplayWarnMessage("VectorAudio disconnected"); } getTransmit = false; } } +void CRDFPlugin::VectorAudioTXRXLoop(void) +{ + threadTXRXRunning = true; + threadTXRXClosed = false; + bool suppressEmpty = false; + while (true) { + for (int sleepRemain = retryInterval * 1000; sleepRemain > 0;) { + if (threadTXRXRunning) { + int sleepThis = min(sleepRemain, pollInterval); + this_thread::sleep_for(chrono::milliseconds(sleepThis)); + sleepRemain -= sleepThis; + } + else { + threadTXRXClosed = true; + threadTXRXClosed.notify_all(); + return; + } + } + + httplib::Client cli("http://" + addressVectorAudio); + cli.set_connection_timeout(0, connectionTimeout * 1000); + bool isActive = false; // false when no active station, for warning + if (auto res = cli.Get(VECTORAUDIO_PARAM_TX)) { + if (res->status == 200) { + DisplayDebugMessage(string("VectorAudio message on TX: ") + res->body); + isActive = isActive || res->body.size(); + UpdateVectorAudioChannels(res->body, true); + } + else { + DisplayDebugMessage("HTTP error on TX: " + httplib::to_string(res.error())); + suppressEmpty = true; + } + } + else { + suppressEmpty = true; + } + if (auto res = cli.Get(VECTORAUDIO_PARAM_RX)) { + if (res->status == 200) { + DisplayDebugMessage(string("VectorAudio message on RX: ") + res->body); + isActive = isActive || res->body.size(); + UpdateVectorAudioChannels(res->body, false); + } + else { + DisplayDebugMessage("HTTP error on RX: " + httplib::to_string(res.error())); + suppressEmpty = true; + } + } + else { + suppressEmpty = true; + } + if (!isActive && !suppressEmpty) { + DisplayWarnMessage("No active stations in VecterAudio! Please check configuration."); + suppressEmpty = true; + } + else if (isActive) { + suppressEmpty = false; + } + } +} + CRadarScreen* CRDFPlugin::OnRadarScreenCreated(const char* sDisplayName, bool NeedRadarContent, bool GeoReferenced, bool CanBeSaved, bool CanBeCreated) { - DisplayEuroScopeMessage(string("Radio Direction Finder plugin activated on ") + sDisplayName); + DisplayInfoMessage(string("Radio Direction Finder plugin activated on ") + sDisplayName); return new CRDFScreen(this); } @@ -447,7 +690,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) char bufferAddr[128] = { 0 }; if (sscanf_s(cmd.c_str(), ".RDF ADDRESS %s", bufferAddr, sizeof(bufferAddr)) == 1) { addressVectorAudio = string(bufferAddr); - DisplayEuroScopeMessage(string("Address: ") + addressVectorAudio); + DisplayInfoMessage(string("Address: ") + addressVectorAudio); SaveDataToSettings(SETTING_VECTORAUDIO_ADDRESS, "VectorAudio address", addressVectorAudio.c_str()); return true; } @@ -456,7 +699,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF TIMEOUT %d", &bufferTimeout) == 1) { if (bufferTimeout >= 100 && bufferTimeout <= 1000) { connectionTimeout = bufferTimeout; - DisplayEuroScopeMessage(string("Timeout: ") + to_string(connectionTimeout)); + DisplayInfoMessage(string("Timeout: ") + to_string(connectionTimeout)); SaveDataToSettings(SETTING_VECTORAUDIO_TIMEOUT, "VectorAudio timeout", to_string(connectionTimeout).c_str()); return true; } @@ -466,7 +709,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF POLL %d", &bufferPollInterval) == 1) { if (bufferPollInterval >= 100) { pollInterval = bufferPollInterval; - DisplayEuroScopeMessage(string("Poll interval: ") + to_string(bufferPollInterval)); + DisplayInfoMessage(string("Poll interval: ") + to_string(bufferPollInterval)); SaveDataToSettings(SETTING_VECTORAUDIO_POLL_INTERVAL, "VectorAudio poll interval", to_string(pollInterval).c_str()); return true; } @@ -476,7 +719,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF RETRY %d", &bufferRetryInterval) == 1) { if (bufferRetryInterval >= 1) { retryInterval = bufferRetryInterval; - DisplayEuroScopeMessage(string("Retry interval: ") + to_string(retryInterval)); + DisplayInfoMessage(string("Retry interval: ") + to_string(retryInterval)); SaveDataToSettings(SETTING_VECTORAUDIO_RETRY_INTERVAL, "VectorAudio retry interval", to_string(retryInterval).c_str()); return true; } @@ -488,7 +731,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) GetRGB(rdfRGB, bufferRGB); if (rdfRGB != prevRGB) { SaveDataToSettings(SETTING_RGB, "RGB", bufferRGB); - DisplayEuroScopeMessage((string("RGB: ") + bufferRGB).c_str()); + DisplayInfoMessage((string("RGB: ") + bufferRGB).c_str()); return true; } } @@ -497,7 +740,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) GetRGB(rdfConcurrentTransmissionRGB, bufferRGB); if (rdfConcurrentTransmissionRGB != prevRGB) { SaveDataToSettings(SETTING_CONCURRENT_RGB, "Concurrent RGB", bufferRGB); - DisplayEuroScopeMessage((string("Concurrent RGB: ") + bufferRGB).c_str()); + DisplayInfoMessage((string("Concurrent RGB: ") + bufferRGB).c_str()); return true; } } @@ -506,14 +749,14 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF RADIUS %d", &bufferRadius) == 1) { if (bufferRadius > 0) { circleRadius = bufferRadius; - DisplayEuroScopeMessage(string("Radius: ") + to_string(circleRadius)); + DisplayInfoMessage(string("Radius: ") + to_string(circleRadius)); SaveDataToSettings(SETTING_CIRCLE_RADIUS, "Radius", to_string(circleRadius).c_str()); return true; } } if (sscanf_s(cmd.c_str(), ".RDF THRESHOLD %d", &circleThreshold) == 1) { - DisplayEuroScopeMessage(string("Threshold: ") + to_string(circleThreshold)); + DisplayInfoMessage(string("Threshold: ") + to_string(circleThreshold)); SaveDataToSettings(SETTING_THRESHOLD, "Threshold", to_string(circleThreshold).c_str()); return true; } @@ -522,20 +765,20 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF PRECISION %d", &bufferPrecision) == 1) { if (bufferPrecision >= 0) { circlePrecision = bufferPrecision; - DisplayEuroScopeMessage(string("Precision: ") + to_string(circlePrecision)); + DisplayInfoMessage(string("Precision: ") + to_string(circlePrecision)); SaveDataToSettings(SETTING_PRECISION, "Precision", to_string(circlePrecision).c_str()); return true; } } if (sscanf_s(cmd.c_str(), ".RDF ALTITUDE L%d", &lowAltitude) == 1) { - DisplayEuroScopeMessage(string("Altitude (low): ") + to_string(lowAltitude)); + DisplayInfoMessage(string("Altitude (low): ") + to_string(lowAltitude)); SaveDataToSettings(SETTING_LOW_ALTITUDE, "Altitude (low)", to_string(lowAltitude).c_str()); return true; } if (sscanf_s(cmd.c_str(), ".RDF ALTITUDE H%d", &highAltitude) == 1) { - DisplayEuroScopeMessage(string("Altitude (high): ") + to_string(highAltitude)); + DisplayInfoMessage(string("Altitude (high): ") + to_string(highAltitude)); SaveDataToSettings(SETTING_HIGH_ALTITUDE, "Altitude (high)", to_string(highAltitude).c_str()); return true; } @@ -543,7 +786,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF PRECISION L%d", &bufferPrecision) == 1) { if (bufferPrecision >= 0) { lowPrecision = bufferPrecision; - DisplayEuroScopeMessage(string("Precision (low): ") + to_string(lowPrecision)); + DisplayInfoMessage(string("Precision (low): ") + to_string(lowPrecision)); SaveDataToSettings(SETTING_LOW_PRECISION, "Precision (low)", to_string(lowPrecision).c_str()); return true; } @@ -552,7 +795,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) if (sscanf_s(cmd.c_str(), ".RDF PRECISION H%d", &bufferPrecision) == 1) { if (bufferPrecision >= 0) { highPrecision = bufferPrecision; - DisplayEuroScopeMessage(string("Precision (high): ") + to_string(highPrecision)); + DisplayInfoMessage(string("Precision (high): ") + to_string(highPrecision)); SaveDataToSettings(SETTING_HIGH_PRECISION, "Precision (high)", to_string(highPrecision).c_str()); return true; } @@ -561,7 +804,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) int bufferCtrl; if (sscanf_s(cmd.c_str(), ".RDF CONTROLLER %d", &bufferCtrl) == 1) { drawController = bufferCtrl; - DisplayEuroScopeMessage(string("Draw controllers: ") + to_string(drawController)); + DisplayInfoMessage(string("Draw controllers: ") + to_string(drawController)); SaveDataToSettings(SETTING_DRAW_CONTROLLERS, "Draw controllers", to_string(bufferCtrl).c_str()); return true; } @@ -569,7 +812,7 @@ bool CRDFPlugin::OnCompileCommand(const char* sCommandLine) } catch (const std::exception& e) { - DisplayEuroScopeDebugMessage(e.what()); + DisplayWarnMessage(e.what()); } return false; } diff --git a/RDFPlugin/CRDFPlugin.h b/RDFPlugin/CRDFPlugin.h index 0139d07..ca3a230 100644 --- a/RDFPlugin/CRDFPlugin.h +++ b/RDFPlugin/CRDFPlugin.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -45,21 +46,25 @@ class CRDFPlugin : public EuroScopePlugIn::CPlugIn normal_distribution<> disDistance; string addressVectorAudio; - thread* VectorAudioTransmission; int connectionTimeout, pollInterval, retryInterval; - atomic_bool threadRunning, threadClosed; // for thread control - void VectorAudioHTTPLoop(void); + // thread controls + thread* threadVectorAudioMain, * threadVectorAudioTXRX; + atomic_bool threadMainRunning, threadMainClosed, + threadTXRXRunning, threadTXRXClosed; + void VectorAudioMainLoop(void); + void VectorAudioTXRXLoop(void); - HWND hiddenWindow = NULL; + HWND hiddenWindowRDF = NULL; + HWND hiddenWindowAFV = NULL; mutex messageLock; // Lock for the message queue // Internal message quque queue> messages; // Class for our window - WNDCLASS windowClass = { + WNDCLASS windowClassRDF = { NULL, - HiddenWindow, + HiddenWindowRDF, NULL, NULL, GetModuleHandle(NULL), @@ -69,27 +74,47 @@ class CRDFPlugin : public EuroScopePlugIn::CPlugIn NULL, "RDFHiddenWindowClass" }; + WNDCLASS windowClassAFV = { + NULL, + HiddenWindowAFV, + NULL, + NULL, + GetModuleHandle(NULL), + NULL, + NULL, + NULL, + NULL, + "AfvBridgeHiddenWindowClass" + }; bool drawController; void GetRGB(COLORREF& color, const char* settingValue); void LoadSettings(void); - void ProcessMessageQueue(void); + void ProcessRDFQueue(void); - inline void DisplayEuroScopeDebugMessage(string msg) { + void UpdateVectorAudioChannels(string line, bool mode_tx); + void ToggleChannels(CGrountToAirChannel Channel, int tx = -1, int rx = -1); + + inline void DisplayDebugMessage(string msg) { #ifdef _DEBUG DisplayUserMessage("RDF-DEBUG", "", msg.c_str(), true, true, true, false, false); #endif // _DEBUG } - inline void DisplayEuroScopeMessage(string msg) { + inline void DisplayInfoMessage(string msg) { DisplayUserMessage("Message", "RDF Plugin", msg.c_str(), false, false, false, false, false); } + inline void DisplayWarnMessage(string msg) { + DisplayUserMessage("Message", "RDF Plugin", msg.c_str(), true, true, true, false, false); + } + public: CRDFPlugin(); virtual ~CRDFPlugin(); - void ProcessAFVMessage(string message); + void HiddenWndProcessRDFMessage(string message); + void HiddenWndProcessAFVMessage(string message); virtual CRadarScreen* OnRadarScreenCreated(const char* sDisplayName, bool NeedRadarContent, bool GeoReferenced, bool CanBeSaved, bool CanBeCreated); virtual bool OnCompileCommand(const char* sCommandLine); diff --git a/RDFPlugin/HiddenWindow.cpp b/RDFPlugin/HiddenWindow.cpp index 0b540bd..787ef76 100644 --- a/RDFPlugin/HiddenWindow.cpp +++ b/RDFPlugin/HiddenWindow.cpp @@ -5,7 +5,7 @@ CRDFPlugin* rdfPlugin; -LRESULT CALLBACK HiddenWindow(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +LRESULT CALLBACK HiddenWindowRDF(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { @@ -16,7 +16,27 @@ LRESULT CALLBACK HiddenWindow(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) COPYDATASTRUCT* data = reinterpret_cast(lParam); if (data != nullptr && data->dwData == 666 && data->lpData != nullptr && rdfPlugin != nullptr) { - rdfPlugin->ProcessAFVMessage(reinterpret_cast(data->lpData)); + rdfPlugin->HiddenWndProcessRDFMessage(reinterpret_cast(data->lpData)); + } + return TRUE; + } + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK HiddenWindowAFV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CREATE: { + rdfPlugin = reinterpret_cast(reinterpret_cast(lParam)->lpCreateParams); + return TRUE; + } + case WM_COPYDATA: { + COPYDATASTRUCT* data = reinterpret_cast(lParam); + + if (data != nullptr && data->dwData == 666 && data->lpData != nullptr && rdfPlugin != nullptr) { + rdfPlugin->HiddenWndProcessAFVMessage(reinterpret_cast(data->lpData)); } return TRUE; } @@ -26,4 +46,3 @@ LRESULT CALLBACK HiddenWindow(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) } - \ No newline at end of file diff --git a/RDFPlugin/HiddenWindow.h b/RDFPlugin/HiddenWindow.h index 061d260..5caef9f 100644 --- a/RDFPlugin/HiddenWindow.h +++ b/RDFPlugin/HiddenWindow.h @@ -2,4 +2,6 @@ #include "stdafx.h" -LRESULT CALLBACK HiddenWindow(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); \ No newline at end of file +LRESULT CALLBACK HiddenWindowRDF(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +LRESULT CALLBACK HiddenWindowAFV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); \ No newline at end of file diff --git a/RDFPlugin/RDFPlugin.aps b/RDFPlugin/RDFPlugin.aps index e359fbd..3b89eae 100644 Binary files a/RDFPlugin/RDFPlugin.aps and b/RDFPlugin/RDFPlugin.aps differ diff --git a/README.md b/README.md index 2470fef..e8a42a8 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ # RDF -## Support VectorAudio/0.5.0+ +## Support *VectorAudio*/0.5.0+ -[VectorAudio](https://github.com/pierr3/VectorAudio) is an Audio-For-VATSIM ATC client for macOS and Linux. It provides better audio quality when EuroScope is running in a Windows virtual machine. This improved RDF plugin utilizes VectorAudio's SDK and sends HTTP GET request every second to get transmitting pilots *and controllers*. +[*VectorAudio*](https://github.com/pierr3/VectorAudio) is an officially recognized multi-platform Audio-For-VATSIM ATC client for Windows, macOS and Linux. This improved RDF plugin utilizes *VectorAudio*'s SDK and sends HTTP GET requests to get transmitting stations and RX/TX status. ## More Customizations + Random radio direction offsets to simulate measuring errors in real life. -+ Customizable circle radius in nautical miles (instead of pixels) meanwhile radius follows precision. ++ Simulate precision via variable circle radius in nautical miles. + Hide radio-direction-finders for low altitude aircrafts. + (Old feature) RGB settings for circle or line, and different color for concurrent transmission. +## Integrate afv-bridge + +Do the same work as [*afv-euroscope-bridge*](https://github.com/AndyTWF/afv-euroscope-bridge). Supports both *VectorAudio* and *Audio for VATSIM standalone client*. When a new radio station is set up for RX/TX, RDF detects its status and toggles respective channels in EuroScope. + ++ For *Audio for VATSIM standalone client*, toggles are made instantly. ++ For *VectorAudio*, the status are updated at an interval of **VectorAudioRetryInterval**, by default 5 seconds. See below. + ## Configurations There are two ways to modify the plugin settings - by settings file and by commandline functions. @@ -59,12 +66,12 @@ END ``` + **VectorAudioAddress** should include address and port only. E.g. 127.0.0.1:49080 or localhost:49080, etc. -+ **VectorAudioTimeout** is in milliseconds. For VectorAudio HTTP requests. -+ **VectorAudioPollInterval** is in milliseconds. For VectorAudio normal refresh. -+ **VectorAudioRetryInterval** is in seconds. If the plugin disconnets from VectorAudio, it will attempt to re-establish connection every 5 seconds by default. ++ **VectorAudioTimeout** is in milliseconds. For *VectorAudio* HTTP requests. ++ **VectorAudioPollInterval** is in milliseconds. For *VectorAudio* normal refresh. ++ **VectorAudioRetryInterval** is in seconds. If the plugin disconnets from *VectorAudio*, it will attempt to re-establish connection every 5 seconds by default. This value is also used to update RX/TX channels. + **RGB, ConcurrentTransmissionRGB**, see [Previous README](#installation-and-previous-readme) below. + **Radius, Threshold, Precision, LowAltitude, HighAltitude, LowPrecision, HighPrecision** see [Random Offset Schematic](#random-offset-schematic) below. -+ **DrawControllers** is compatible with both VectorAudio and AFV. Other transimitting controllers will be circled as well but without offset. 0 means OFF and other numeric value means ON. ++ **DrawControllers** is compatible with both *VectorAudio* and *Audio for VATSIM standalone client*. Other transimitting controllers will be circled as well but without offset. 0 means OFF and other numeric value means ON. When EuroScope is running, you can reload settings in *Settings File Setup* and then enter ***".RDF RELOAD"*** (case-insensitive) in command line. @@ -89,14 +96,17 @@ When EuroScope is running, you can reload settings in *Settings File Setup* and ## Known Issues -+ It is possible to crash EuroScope when using TopSky at the same time under certain TopSky settings due to conflicting API method to communicate with AFC standalone client. Goto *TopSkySettings.txt* and add *RDF_Mode=-1* to prevent such cases. ++ EuroScope may crash when using TopSky at the same time with certain TopSky settings due to conflicting API method to communicate with *Audio for VATSIM standalone client*. Goto *TopSkySettings.txt* and add *RDF_Mode=-1* to prevent such cases. ++ Do not simultaneously load this plugin along with the older version of RDF, or with the original *afv-euroscope-bridge* plugin, which may cause unexpected behavior. ++ *Audio for VATSIM standalone client* doesn't provide callsign for RX/TX, so this plugin has to guess the corresponding callsign and it don't guarantee 100% correct toggles. But it shouldn't affect text receive and transmit function. + When using professional correlation mode (S or C) in EuroScope, it's possible some aircraft won't be radio-direction-found because the plugin doesn't know the callsign for an uncorrelated radar target. + For dual pilot situation where the transmitting pilot logs in as observer, this plugin will try to drop the last character of the observer callsign and find again if this dropped character is between A-Z. This feature may cause inaccurate radio-direction. ## Credits + [pierr3/VectorAudio](https://github.com/pierr3/VectorAudio): initiative. -+ [chembergj/RDF](https://github.com/chembergj/RDF): basic drawings and AFV message handling. ++ [chembergj/RDF](https://github.com/chembergj/RDF): basic drawings. ++ [AndyTWF/afv-euroscope-bridge](https://github.com/AndyTWF/afv-euroscope-bridge): *Audio for VATSIM standalone client* message handling. + [LeoChen98](https://github.com/LeoChen98), [websterzh](https://github.com/websterzh): idea of using HTTP requests. + [yhirose/cpp-httplib](https://github.com/yhirose/cpp-httplib): HTTP library. + [vaccfr/CoFrance](https://github.com/vaccfr/CoFrance): method to use async HTTP requests (deprecated since v1.3.2).