v1.3.0.0
WASimClient.cpp
1/*
2This file is part of the WASimCommander project.
3https://github.com/mpaperno/WASimCommander
4
5COPYRIGHT: (c) Maxim Paperno; All Rights Reserved.
6
7This file may be used under the terms of either the GNU General Public License (GPL)
8or the GNU Lesser General Public License (LGPL), as published by the Free Software
9Foundation, either version 3 of the Licenses, or (at your option) any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16Copies of the GNU GPL and LGPL are included with this project
17and are available at <http://www.gnu.org/licenses/>.
18
19Except as contained in this notice, the names of the authors or
20their institutions shall not be used in advertising or otherwise to
21promote the sale, use or other dealings in this Software without
22prior written authorization from the authors.
23*/
24
25#include <chrono>
26#include <condition_variable>
27#include <cstdio>
28#include <cstring>
29#include <ctime>
30#include <filesystem>
31#include <fstream>
32#include <future>
33#include <iomanip>
34#include <iostream>
35#include <list>
36#include <set>
37#include <shared_mutex>
38#include <string>
39#include <sstream>
40#include <thread>
41
42#define WIN32_LEAN_AND_MEAN
43#include <Windows.h>
44#include <SimConnect.h>
45
46#define LOGFAULT_THREAD_NAME WSMCMND_CLIENT_NAME
47
48#include "client/WASimClient.h"
49//#include "private/wasimclient_p.h"
50#include "utilities.h"
51#include "SimConnectHelper.h"
52#include "inipp.h"
53
55{
56
57using namespace std;
58using namespace WASimCommander::Utilities;
59using namespace WASimCommander::Enums;
60/// \private
61using Clock = std::chrono::steady_clock;
62
63static const uint32_t CUSTOM_KEY_EVENT_LEGACY_TRIGGER_FLAG = 0x80000000;
64
65// -------------------------------------------------------------
66#pragma region DataRequestRecord struct
67// -------------------------------------------------------------
68
71{ }
72
74 DataRequest(req),
75 data(Utilities::getActualValueSize(valueSize), 0xFF) { }
76
78 DataRequest(req),
79 data(Utilities::getActualValueSize(valueSize), 0xFF) { }
80
81#pragma endregion
82
83// -------------------------------------------------------------
84#pragma region RegisteredEvent struct
85// -------------------------------------------------------------
86
87RegisteredEvent::RegisteredEvent(uint32_t eventId, const std::string &code, const std::string &name) :
88 eventId{eventId}, code{code}, name{name} { }
89
90#pragma endregion
91
92
93// -------------------------------------------------------------
94#pragma region WASimClient::Private class
95// -------------------------------------------------------------
96
97class WASimClient::Private
98{
99#pragma region Locals
100
101 static const uint32_t DISPATCH_LOOP_WAIT_TIME = 5000;
102 static const uint32_t SIMCONNECT_DATA_ALLOC_LIMIT = 1024UL * 1024UL;
103
104 enum SimConnectIDs : uint8_t
105 {
106 SIMCONNECTID_INIT = 0,
107 // SIMCONNECT_NOTIFICATION_GROUP_ID
108 NOTIFY_GROUP_PING, // ping response notification group
109 // SIMCONNECT_CLIENT_EVENT_ID - custom named events
110 CLI_EVENT_CONNECT, // connect to server
111 CLI_EVENT_PING, // ping server
112 CLI_EVENT_PING_RESP, // ping response
113 // SIMCONNECT_CLIENT_DATA_ID - data area and definition IDs
114 CLI_DATA_COMMAND,
115 CLI_DATA_RESPONSE,
116 CLI_DATA_REQUEST,
117 CLI_DATA_KEYEVENT,
118 CLI_DATA_LOG,
119 // SIMCONNECT_DATA_REQUEST_ID - requests for data updates
120 DATA_REQ_RESPONSE, // command response data
121 DATA_REQ_LOG, // server log data
122
123 SIMCONNECTID_LAST // dynamic IDs start at this value
124
125 };
126
127 struct TrackedRequest : public DataRequest
128 {
129 uint32_t dataId; // our area and def ID
130 time_t lastUpdate = 0;
131 uint32_t dataSize;
132 mutable shared_mutex m_dataMutex;
133 vector<uint8_t> data;
134
135 explicit TrackedRequest(const DataRequest &req, uint32_t dataId) :
136 DataRequest(req),
137 dataId{dataId},
138 dataSize{Utilities::getActualValueSize(valueSize)},
139 data(dataSize, 0xFF)
140 { }
141
142 TrackedRequest & operator=(const DataRequest &req) {
143 if (req.valueSize != valueSize) {
144 unique_lock lock(m_dataMutex);
145 dataSize = Utilities::getActualValueSize(req.valueSize);
146 data = vector<uint8_t>(dataSize, 0xFF);
147 }
148 DataRequest::operator=(req);
149 return *this;
150 }
151
152 DataRequestRecord toRequestRecord() const {
153 DataRequestRecord drr = DataRequestRecord(static_cast<const DataRequest &>(*this));
154 drr.data.assign(data.cbegin(), data.cend());
155 drr.lastUpdate = lastUpdate;
156 return drr;
157 }
158
159 friend inline std::ostream& operator<<(std::ostream& os, const TrackedRequest &r) {
160 os << (const DataRequest &)r;
161 return os << " TrackedRequest{" << " dataId: " << r.dataId << "; lastUpdate: " << r.lastUpdate << "; dataSize: " << r.dataSize << "; data: " << Utilities::byteArrayToHex(r.data.data(), r.dataSize) << '}';
162 }
163
164 };
165
166 struct TrackedResponse
167 {
168 uint32_t token;
169 chrono::system_clock::time_point sent { chrono::system_clock::now() };
170 weak_ptr<condition_variable_any> cv {};
171 shared_mutex mutex;
172 Command response {};
173
174 explicit TrackedResponse(uint32_t t, weak_ptr<condition_variable_any> cv) : token(t), cv(cv) {}
175 };
176
177 struct TrackedEvent : public RegisteredEvent
178 {
179 bool sentToServer = false;
181 };
182
183 using responseMap_t = map<uint32_t, TrackedResponse>;
184 using requestMap_t = map<uint32_t, TrackedRequest>;
185 using eventMap_t = map<uint32_t, TrackedEvent>;
186
187 struct TempListResult {
188 atomic<LookupItemType> listType { LookupItemType::None };
189 uint32_t token { 0 };
190 shared_ptr<condition_variable_any> cv = make_shared<condition_variable_any>();
191 atomic<Clock::time_point> nextTimeout {};
193 shared_mutex mutex;
194
195 ListResult::listResult_t getResult()
196 {
197 shared_lock lock(mutex);
198 return ListResult::listResult_t(result);
199 }
200
201 void reset() {
202 unique_lock lock(mutex);
203 listType = LookupItemType::None;
204 result.clear();
205 }
206
207 } listResult;
208
209 struct ProgramSettings {
210 filesystem::path logFilePath;
211 int networkConfigId = -1;
212 uint32_t networkTimeout = 1000;
213 LogLevel logLevels[2][3] = {
214 // Console File Remote
215 { LogLevel::Info, LogLevel::Info, LogLevel::None }, // Client
216 { LogLevel::None, LogLevel::None, LogLevel::None } // Server
217 };
218 LogLevel &logLevel(LogSource s, LogFacility f) { return logLevels[srcIndex(s)][facIndex(f)]; }
219 LogLevel logLevel(LogSource s, LogFacility f) const { return f > LogFacility::None && f <= LogFacility::Remote ? logLevels[srcIndex(s)][facIndex(f)] : LogLevel::None; }
220 static int srcIndex(LogSource s) { return s == LogSource::Server ? 1 : 0; }
221 static int facIndex(LogFacility f) { return f == LogFacility::Remote ? 2 : f == LogFacility::File ? 1 : 0; }
222 } settings;
223
224 friend class WASimClient;
225 WASimClient *const q;
226
227 const uint32_t clientId;
228 const string clientName;
229 atomic<ClientStatus> status = ClientStatus::Idle;
230 uint32_t serverVersion = 0;
231 atomic<Clock::time_point> serverLastSeen = Clock::time_point();
232 atomic_size_t totalDataAlloc = 0;
233 atomic_uint32_t nextDefId = SIMCONNECTID_LAST;
234 atomic_uint32_t nextCmdToken = 1;
235 atomic_bool runDispatchLoop = false;
236 atomic_bool simConnected = false;
237 atomic_bool serverConnected = false;
238 atomic_bool logCDAcreated = false;
239 atomic_bool requestsPaused = false;
240
241 HANDLE hSim = nullptr;
242 HANDLE hSimEvent = nullptr;
243 HANDLE hDispatchStopEvent = nullptr;
244 thread dispatchThread;
245
246 clientEventCallback_t eventCb = nullptr; // main and dispatch threads (+ "on SimConnect_Quit" thread)
247 listResultsCallback_t listCb = nullptr; // list result wait thread
248 dataCallback_t dataCb = nullptr; // dispatch thread
249 logCallback_t logCb = nullptr; // main and dispatch threads
250 commandCallback_t cmdResultCb = nullptr; // dispatch thread
251 commandCallback_t respCb = nullptr; // dispatch thread
252
253 mutable shared_mutex mtxResponses;
254 mutable shared_mutex mtxRequests;
255 mutable shared_mutex mtxEvents;
256
257 responseMap_t reponses {};
258 requestMap_t requests {};
259 eventMap_t events {};
260
261 // Cached mapping of Key Event names to actual IDs, used in `sendKeyEvent(string)` convenience overload,
262 // and also to track registered (mapped) custom-named sim Events as used in `registerCustomKeyEvent()` and related methods.
263 unordered_map<std::string, uint32_t> keyEventNameCache {};
264 shared_mutex mtxKeyEventNames;
265 uint32_t nextCustomEventID = CUSTOM_KEY_EVENT_ID_MIN; // auto-generated IDs for custom event registrations
266
267 // Saved mappings of variable names to SIMCONNECT_DATA_DEFINITION_ID values we assigned to them.
268 // Used for setting string values on SimVars, which we do locally since WASM module can't.
269 map<std::string, uint32_t> mappedVarsNameCache {};
270 shared_mutex mtxMappedVarNames;
271 uint32_t nextMappedVarID = 1; // auto-generated IDs for mapping variable names to SimConnect IDs
272
273#pragma endregion
274#pragma region Callback handling templates ----------------------------------------------
275
276#if defined(WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS) && !WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS
277 mutable mutex mtxCallbacks;
278#endif
279
280 // Rvalue arguments version
281 template<typename... Args>
282 void invokeCallbackImpl(function<void(Args...)> f, Args&&... args) const
283 {
284 if (f) {
285 try {
286#if defined(WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS) && !WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS
287 lock_guard lock(mtxCallbacks);
288#endif
289 bind(f, forward<Args>(args)...)();
290 }
291 catch (exception *e) { LOG_ERR << "Exception in callback handler: " << e->what(); }
292 }
293 }
294
295 // const Lvalue arguments version
296 template<typename... Args>
297 void invokeCallbackImpl(function<void(const Args...)> f, const Args... args) const
298 {
299 if (f) {
300 try {
301#if defined(WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS) && !WSMCMND_CLIENT_USE_CONCURRENT_CALLBACKS
302 lock_guard lock(mtxCallbacks);
303#endif
304 bind(f, args...)();
305 }
306 catch (exception *e) { LOG_ERR << "Exception in callback handler: " << e->what(); }
307 }
308 }
309
310 // Type deduction helper
311 template<typename F, typename... Args>
312 void invokeCallback(F&& f, Args&&... args) const {
313 invokeCallbackImpl<remove_cv_t<remove_reference_t<Args>>...>(forward<F>(f), forward<Args>(args)...);
314 }
315
316#pragma endregion
317#pragma region Constructor and status ----------------------------------------------
318
319 Private(WASimClient *qptr, uint32_t clientId, const std::string &config) :
320 q{qptr},
321 clientId{clientId},
322 clientName{(ostringstream() << STREAM_HEX8(clientId)).str()}
323 {
324 error_code ec;
325 filesystem::path cwd = filesystem::current_path(ec);
326 if (ec) {
327 cerr << ec.message() << endl;
328 cwd = ".";
329 }
330 settings.logFilePath = cwd;
331 // set default tracked requests limit based on current global setting
332 int requestTrackingMaxRecords = SimConnectHelper::ENABLE_SIMCONNECT_REQUEST_TRACKING ? 200 : 0;
333
334 // Determine config file to use.
335 const char *fn = "client_conf.ini";
336 filesystem::path cfgFile(cwd /+ fn);
337 if (!config.empty()) {
338 error_code ec;
339 filesystem::file_status fs = filesystem::status(config, ec);
340 if (!ec && filesystem::exists(fs)) {
341 cfgFile = config;
342 if (filesystem::is_directory(fs))
343 cfgFile /= fn;
344 }
345 else {
346 cerr << "Config file/path '" << config << "' not found or not accessible. Using default config location " << cwd << endl;;
347 }
348 }
349 // Read initial config from file
350 ifstream is(cfgFile);
351 if (is.good()) {
352 inipp::Ini<char> ini;
353 ini.parse(is);
354 for (const auto &e : ini.errors)
355 cerr << e << endl;
356 string consoleLevel(getEnumName(settings.logLevel(LogSource::Client, LogFacility::Console), LogLevelNames)),
357 fileLevel(getEnumName(settings.logLevel(LogSource::Client, LogFacility::File), LogLevelNames));
358 const auto &logSect = ini.sections["logging"];
359 inipp::get_value(logSect, "logFilepath", settings.logFilePath);
360 inipp::get_value(logSect, "fileLogLevel", fileLevel);
361 inipp::get_value(logSect, "consoleLogLevel", consoleLevel);
362 const auto &netSect = ini.sections["network"];
363 inipp::get_value(netSect, "networkConfigId", settings.networkConfigId);
364 inipp::get_value(netSect, "networkTimeout", settings.networkTimeout);
365 inipp::get_value(netSect, "requestTrackingMaxRecords", requestTrackingMaxRecords);
366 int idx;
367 if ((idx = indexOfString(LogLevelNames, fileLevel.c_str())) > -1)
368 settings.logLevel(LogSource::Client, LogFacility::File) = LogLevel(idx);
369 if ((idx = indexOfString(LogLevelNames, consoleLevel.c_str())) > -1)
370 settings.logLevel(LogSource::Client, LogFacility::Console) = LogLevel(idx);
371 //cout << settings.logFilePath << ';' << fileLevel << '=' << (int)settings.fileLogLevel << ';' << consoleLevel << '=' << (int)settings.consoleLogLevel << ';' << settings.networkConfigId << ';' << settings.networkTimeout << endl;
372 }
373 else {
374 cerr << "Config file '" << cfgFile << "' not found or not accessible. Setting all logging to " << getEnumName(settings.logLevel(LogSource::Client, LogFacility::Console), LogLevelNames)
375 << " levels, with file in " << quoted(settings.logFilePath.string()) << endl;
376 }
377
378 // set up logging
379 settings.logFilePath /= WSMCMND_CLIENT_NAME;
380 setFileLogLevel(settings.logLevel(LogSource::Client, LogFacility::File));
381 setConsoleLogLevel(settings.logLevel(LogSource::Client, LogFacility::Console));
382 // set up request tracking
383 if ((SimConnectHelper::ENABLE_SIMCONNECT_REQUEST_TRACKING = (requestTrackingMaxRecords > 0)))
384 SimConnectHelper::setMaxTrackedRequests(requestTrackingMaxRecords);
385 }
386
387 bool checkInit() const { return hSim != nullptr; }
388 bool isConnected() const { return checkInit() && (status & ClientStatus::Connected) == ClientStatus::Connected; }
389
390 void setStatus(ClientStatus s, const string &msg = "")
391 {
392 static const char * evTypeNames[] = {
393 "None",
394 "Simulator Connecting", "Simulator Connected", "Simulator Disconnecting", "Simulator Disconnected",
395 "Server Connecting", "Server Connected", "Server Disconnected"
396 };
397
398 ClientStatus newStat = status;
399 ClientEvent ev;
400 switch (s) {
403 newStat = s;
404 break;
407 newStat = s;
408 break;
412 break;
415 newStat = (newStat & ~ClientStatus::Connected) | s;
416 break;
419 newStat = (newStat & ~ClientStatus::Connecting) | s;
420 break;
423 newStat |= s;
424 break;
425 }
426 if (newStat == status)
427 return;
428 status = newStat;
429 if (eventCb) {
430 ev.status = status;
431 if (msg == "")
432 ev.message = Utilities::getEnumName(ev.eventType, evTypeNames);
433 else
434 ev.message = msg;
435 invokeCallback(eventCb, move(ev));
436 }
437 }
438
439 // simple thread burner loop to wait on a condition... don't abuse.
440 bool waitCondition(const std::function<bool(void)> predicate, uint32_t timeout, uint32_t sleepMs = 1)
441 {
442 const auto endTime = Clock::now() + chrono::milliseconds(timeout);
443 const auto sf = chrono::milliseconds(sleepMs);
444 while (!predicate() && endTime > Clock::now())
445 this_thread::sleep_for(sf);
446 return predicate();
447 }
448
449#if 0 // unused
450 HRESULT waitConditionAsync(std::function<bool(void)> predicate, uint32_t timeout = 0)
451 {
452 if (predicate())
453 return S_OK;
454 if (!timeout)
455 timeout = settings.networkTimeout;
456 future<bool> taskResult = async(launch::async, &Private::waitCondition, this, predicate, timeout, 1);
457 future_status taskStat = taskResult.wait_until(Clock::now() + chrono::milliseconds(timeout + 50));
458 return (taskStat == future_status::ready ? S_OK : E_TIMEOUT);
459 }
460#endif
461
462#pragma endregion
463#pragma region Local logging ----------------------------------------------
464
465 void setFileLogLevel(LogLevel level)
466 {
467 settings.logLevel(LogSource::Client, LogFacility::File) = level;
468 const string loggerName = string(WSMCMND_CLIENT_NAME) + '_' + clientName;
469 if (logfault::LogManager::Instance().HaveHandler(loggerName))
470 logfault::LogManager::Instance().SetLevel(loggerName, logfault::LogLevel(level));
471 else if (level != LogLevel::None)
472 logfault::LogManager::Instance().AddHandler(
473 make_unique<logfault::FileHandler>(settings.logFilePath.string().c_str(), logfault::LogLevel(level), logfault::FileHandler::Handling::Rotate, loggerName)
474 );
475 }
476
477 void setConsoleLogLevel(LogLevel level)
478 {
479 settings.logLevel(LogSource::Client, LogFacility::Console) = level;
480 if (logfault::LogManager::Instance().HaveHandler(WSMCMND_CLIENT_NAME))
481 logfault::LogManager::Instance().SetLevel(WSMCMND_CLIENT_NAME, logfault::LogLevel(level));
482 else if (level != LogLevel::None)
483 logfault::LogManager::Instance().AddHandler(
484 make_unique<logfault::StreamHandler>(cout, logfault::LogLevel(level), WSMCMND_CLIENT_NAME)
485 );
486 }
487
488 void setCallbackLogLevel(LogLevel level)
489 {
490 settings.logLevel(LogSource::Client, LogFacility::Remote) = level;
491 if (logfault::LogManager::Instance().HaveHandler(clientName))
492 logfault::LogManager::Instance().SetLevel(clientName, logfault::LogLevel(level));
493 }
494
495 // proxy log handler callback method for delivery of log records to remote listener
496 void onProxyLoggerMessage(const logfault::Message &msg) {
497 invokeCallback(logCb, LogRecord((LogLevel)msg.level_, msg.msg_.c_str(), msg.when_), LogSource::Client);
498 }
499
501 {
502 logCb = cb;
503 if (cb && !logfault::LogManager::Instance().HaveHandler(clientName)) {
504 logfault::LogManager::Instance().SetHandler(clientName,
505 make_unique<logfault::ProxyHandler>(
506 bind(&Private::onProxyLoggerMessage, this, placeholders::_1),
507 logfault::LogLevel(settings.logLevel(LogSource::Client, LogFacility::Remote)),
508 clientName
509 )
510 );
511 }
512 else if (!cb && logfault::LogManager::Instance().HaveHandler(clientName)) {
513 logfault::LogManager::Instance().RemoveHandler(clientName);
514 }
515 }
516
517#pragma endregion
518#pragma region SimConnect and Server core utilities ----------------------------------------------
519
520 HRESULT connectSimulator(uint32_t timeout) {
521 return connectSimulator(settings.networkConfigId, timeout);
522 }
523
524 HRESULT connectSimulator(int networkConfigId, uint32_t timeout)
525 {
527 return E_FAIL; // unlikely, but prevent recursion
528
529 if (checkInit()) {
530 LOG_INF << WSMCMND_CLIENT_NAME " already initialized.";
531 return E_FAIL;
532 }
533 settings.networkConfigId = networkConfigId;
534 LOG_INF << "Initializing " << WSMCMND_CLIENT_NAME << " v" WSMCMND_VERSION_INFO " for client " << clientName << " with net config ID " << settings.networkConfigId;
536
537 if (!timeout)
538 timeout = settings.networkTimeout;
539
540 if (!hDispatchStopEvent) {
541 hDispatchStopEvent = CreateEvent(nullptr, true, false, nullptr);
542 if (!hDispatchStopEvent) {
543 LOG_ERR << "Failed to CreateEvent() for dispatch loop: " << GetLastError();
544 return E_FAIL;
545 }
546 }
547
548 hSimEvent = CreateEvent(nullptr, false, false, nullptr);
549 simConnected = false;
550
551 // try to open connection; may return S_OK, E_FAIL, or E_INVALIDARG. May also time out and never actually send the Open event message.
552 HRESULT hr = SimConnect_Open(&hSim, clientName.c_str(), nullptr, 0, hSimEvent, settings.networkConfigId);
553
554 if FAILED(hr) {
555 LOG_ERR << "Network connection to Simulator failed with result: " << LOG_HR(hr);
556 hSim = nullptr;
557 setStatus(ClientStatus::Idle);
558 return hr;
559 }
560 // start message dispatch loop. required to do this now in order to receive the SimConnect Open event and finish the connection process.
561 ResetEvent(hDispatchStopEvent);
562 dispatchThread = thread(&Private::dispatchLoop, this);
563 dispatchThread.detach();
564 // wait for dispatch loop to start
565 if (!waitCondition([&]() { return !!runDispatchLoop; }, 10)) {
566 LOG_CRT << "Could not start dispatch loop thread.";
568 return E_FAIL;
569 }
570 // wait until sim connects or times out or disconnectSimulator() is called.
571 if (!waitCondition([&]() { return !runDispatchLoop || simConnected; }, timeout)) {
572 LOG_ERR << "Network connection to Simulator timed out after " << timeout << "ms.";
574 return E_TIMEOUT;
575 }
576
577 // register server's Connect command event, exit on failure
578 if FAILED(hr = SimConnectHelper::newClientEvent(hSim, CLI_EVENT_CONNECT, EVENT_NAME_CONNECT)) {
580 return hr;
581 }
582 // register server's ping command event
583 SimConnectHelper::newClientEvent(hSim, CLI_EVENT_PING, EVENT_NAME_PING);
584 // register and subscribe to server ping response (the server will append the name of our client to a new named event and trigger it).
585 SimConnectHelper::newClientEvent(hSim, CLI_EVENT_PING_RESP, EVENT_NAME_PING_PFX + clientName, NOTIFY_GROUP_PING, SIMCONNECT_GROUP_PRIORITY_HIGHEST);
586
587 // register area for writing commands (this is read-only for the server)
588 if FAILED(hr = registerDataArea(CDA_NAME_CMD_PFX, CLI_DATA_COMMAND, CLI_DATA_COMMAND, sizeof(Command), true)) {
590 return hr;
591 }
592 // register command response area for reading (server can write to this channel)
593 if FAILED(hr = registerDataArea(CDA_NAME_RESP_PFX, CLI_DATA_RESPONSE, CLI_DATA_RESPONSE, sizeof(Command), false)) {
595 return hr;
596 }
597 // register CDA for writing data requests (this is read-only for the server)
598 registerDataArea(CDA_NAME_DATA_PFX, CLI_DATA_REQUEST, CLI_DATA_REQUEST, sizeof(DataRequest), true);
599 // register CDA for writing key events (this is read-only for the server)
600 registerDataArea(CDA_NAME_KEYEV_PFX, CLI_DATA_KEYEVENT, CLI_DATA_KEYEVENT, sizeof(KeyEvent), true);
601
602 // start listening on the response channel
603 if FAILED(hr = INVOKE_SIMCONNECT(
604 RequestClientData, hSim, (SIMCONNECT_CLIENT_DATA_ID)CLI_DATA_RESPONSE, (SIMCONNECT_DATA_REQUEST_ID)DATA_REQ_RESPONSE,
605 (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)CLI_DATA_RESPONSE, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET, (SIMCONNECT_CLIENT_DATA_REQUEST_FLAG)0, 0UL, 0UL, 0UL
606 )) {
608 return hr;
609 }
610
611 // possibly re-register SimConnect CDAs for any existing data requests
612 registerAllDataRequestAreas();
613 // re-register all Simulator Custom Events
614 mapAllCustomKeyEvents();
615
617 return S_OK;
618 }
619
620 // onQuitEvent == true when SimConnect is shutting down and sends Quit, in which case we don't try to use SimConnect again.
621 void disconnectSimulator(bool onQuitEvent = false)
622 {
623 if (!checkInit()) {
624 LOG_INF << "No network connection. Shutdown complete.";
625 setStatus(ClientStatus::Idle);
626 return;
627 }
628
629 LOG_DBG << "Shutting down...";
630 disconnectServer(!onQuitEvent);
632 SetEvent(hDispatchStopEvent); // signal thread to exit
633 //d->dispatchThread.join();
634
635 // check for dispatch loop thread shutdown (this is really just for logging/debug purposes)
636 if (!waitCondition([&]() { return !runDispatchLoop; }, DISPATCH_LOOP_WAIT_TIME + 100))
637 LOG_CRT << "Dispatch loop thread still running!";
638
639 // reset flags/counters
640 simConnected = false;
641 logCDAcreated = false;
642 totalDataAlloc = 0;
643
644 // dispose objects
645 if (hSim && !onQuitEvent)
646 SimConnect_Close(hSim);
647 hSim = nullptr;
648 if (hSimEvent)
649 CloseHandle(hSimEvent);
650 hSimEvent = nullptr;
651
652 {
653 // clear cached var name mappings, new mapping will need to be made if re-connecting
654 unique_lock lock { mtxMappedVarNames };
655 mappedVarsNameCache.clear();
656 nextMappedVarID = 1;
657 }
658
659 setStatus(ClientStatus::Idle);
660 LOG_INF << "Shutdown complete, network disconnected.";
661 }
662
663 HRESULT connectServer(uint32_t timeout)
664 {
666 return E_FAIL; // unlikely, but prevent recursion
667
668 if (!timeout)
669 timeout = settings.networkTimeout;
670
671 HRESULT hr;
672 if (!checkInit() && FAILED(hr = connectSimulator(timeout)))
673 return hr;
674
675 LOG_INF << "Connecting to " WSMCMND_PROJECT_NAME " server...";
676 setStatus(ClientStatus::Connecting);
677
678 // Send the initial "connect" event which we registered in connectSimulator(), using this client's ID as the data parameter.
679 if FAILED(hr = INVOKE_SIMCONNECT(TransmitClientEvent, hSim, SIMCONNECT_OBJECT_ID_USER, (SIMCONNECT_CLIENT_EVENT_ID)CLI_EVENT_CONNECT, (DWORD)clientId, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY)) {
681 return hr;
682 }
683 // The connection response, if any, is written by the server into the "command response" client data area, CLI_DATA_ID_RESPONSE, processed in SIMCONNECT_RECV_ID_CLIENT_DATA
684
685 // wait until server connects or times out or disconnectSimulator() is called.
686 if (!waitCondition([&]() { return !runDispatchLoop || serverConnected; }, timeout)) {
687 LOG_ERR << "Server connection timed out or refused after " << timeout << "ms.";
689 return E_TIMEOUT;
690 }
691
692 LOG_INF << "Connected to " WSMCMND_PROJECT_NAME " server v" << STREAM_HEX8(serverVersion);
693 if ((serverVersion & 0xFF000000) != (WSMCMND_VERSION & 0xFF000000))
694 LOG_WRN << "Server major version does not match WASimClient version " << STREAM_HEX8(WSMCMND_VERSION);
695 setStatus(ClientStatus::Connected);
696
697 // clear any pending list request (unlikely)
698 listResult.reset();
699 // make sure server knows our desired log level and set up data area/request if needed
700 updateServerLogLevel();
701 // set update status of data requests before adding any, in case we don't actually want results yet
702 sendServerCommand(Command(CommandId::Subscribe, (requestsPaused ? 0 : 1)));
703 // (re-)register (or delete) any saved DataRequests
704 registerAllDataRequests();
705 // same with calculator events
706 registerAllEvents();
707
708 return S_OK;
709 }
710
711 void disconnectServer(bool notifyServer = true)
712 {
713 if (!serverConnected || (status & ClientStatus::Connecting) == ClientStatus::Connecting)
714 return;
715
716 // send disconnect command
717 if (notifyServer)
718 sendServerCommand(Command(CommandId::Disconnect));
719
720 // clear command tracking queue
721 unique_lock lock(mtxResponses);
722 reponses.clear();
723
724 serverConnected = false;
725 LOG_INF << "Disconnected from " WSMCMND_PROJECT_NAME " server.";
727 }
728
729 HRESULT registerDataArea(const string &name, SIMCONNECT_CLIENT_DATA_ID cdaID, SIMCONNECT_CLIENT_DATA_DEFINITION_ID cddId, DWORD szOrType, bool readonly, bool nameIsFull = false, float deltaE = 0.0f)
730 {
731 // Map a unique named data storage area to CDA ID.
732 if (!checkInit())
733 return E_NOT_CONNECTED;
734 HRESULT hr;
735 const string cdaName(nameIsFull ? name : name + clientName);
736 if FAILED(hr = SimConnectHelper::registerDataArea(hSim, cdaName, cdaID, cddId, szOrType, true, readonly, deltaE))
737 return hr;
738 const uint32_t alloc = Utilities::getActualValueSize(szOrType);;
739 totalDataAlloc += alloc;
740 LOG_DBG << "Created CDA ID " << cdaID << " named " << quoted(cdaName) << " of size " << alloc << "; Total data allocation : " << totalDataAlloc;
741 return S_OK;
742 }
743
744#pragma endregion
745#pragma region Command sending ----------------------------------------------
746
747 // Sends a const command as provided.
748 HRESULT sendServerCommand(const Command &command) const
749 {
750 if (!isConnected()) {
751 LOG_ERR << "Server not connected, cannot send " << command;
752 return E_NOT_CONNECTED;
753 }
754 LOG_TRC << "Sending command: " << command;
755 return INVOKE_SIMCONNECT(SetClientData, hSim,
756 (SIMCONNECT_CLIENT_DATA_ID)CLI_DATA_COMMAND, (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)CLI_DATA_COMMAND,
757 SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0UL, (DWORD)sizeof(Command), (void *)&command
758 );
759 }
760
761 // Sends command and enqueues a tracked response record with given condition var. Optionally returns new token.
762 HRESULT sendServerCommand(Command &&command, weak_ptr<condition_variable_any> cv, uint32_t *newToken = nullptr)
763 {
764 if (cv.expired())
765 return E_INVALIDARG;
766 if (!command.token)
767 command.token = nextCmdToken++;
768 enqueueTrackedResponse(command.token, cv);
769 const HRESULT hr = sendServerCommand(command);
770 if FAILED(hr) {
771 unique_lock vlock(mtxResponses);
772 reponses.erase(command.token);
773 }
774 if (newToken)
775 *newToken = command.token;
776 return hr;
777 }
778
779 // Sends command and waits for response. Default timeout is settings.networkTimeout.
780 HRESULT sendCommandWithResponse(Command &&command, Command *response, uint32_t timeout = 0)
781 {
782 uint32_t token;
783 shared_ptr<condition_variable_any> cv = make_shared<condition_variable_any>();
784 const CommandId origId = command.commandId;
785 HRESULT hr = sendServerCommand(move(command), weak_ptr(cv), &token);
786 if SUCCEEDED(hr) {
787 if FAILED(hr = waitCommandResponse(token, response, timeout))
788 LOG_ERR << "Command " << Utilities::getEnumName(origId, CommandIdNames) << " timed out after " << (timeout ? timeout : settings.networkTimeout) << "ms.";
789 }
790 return hr;
791 }
792
793#pragma endregion
794#pragma region Command Response tracking ----------------------------------------------
795
796 TrackedResponse *findTrackedResponse(uint32_t token)
797 {
798 shared_lock lock{mtxResponses};
799 const responseMap_t::iterator pos = reponses.find(token);
800 if (pos != reponses.cend())
801 return &pos->second;
802 return nullptr;
803 }
804
805 TrackedResponse *enqueueTrackedResponse(uint32_t token, weak_ptr<condition_variable_any> cv)
806 {
807 unique_lock lock{mtxResponses};
808 return &reponses.try_emplace(token, token, cv).first->second;
809 }
810
811 // Blocks and waits for a response to a specific command token. `timeout` can be -1 to use `extraPredicate` only (which is then required).
812 HRESULT waitCommandResponse(uint32_t token, Command *response, uint32_t timeout = 0, std::function<bool(void)> extraPredicate = nullptr)
813 {
814 TrackedResponse *tr = findTrackedResponse(token);
815 shared_ptr<condition_variable_any> cv {};
816 if (!tr || !(cv = tr->cv.lock()))
817 return E_FAIL;
818
819 if (!timeout)
820 timeout = settings.networkTimeout;
821
822 bool stopped = false;
823 auto stop_waiting = [=, &stopped]() {
824 return stopped =
825 !serverConnected || (tr->response.commandId != CommandId::None && tr->response.token == token && (!extraPredicate || extraPredicate()));
826 };
827
828 HRESULT hr = E_TIMEOUT;
829 if (stop_waiting()) {
830 hr = S_OK;
831 }
832 else {
833 unique_lock lock(tr->mutex);
834 if (timeout > 0) {
835 if (cv->wait_for(lock, chrono::milliseconds(timeout), stop_waiting))
836 hr = S_OK;
837 }
838 else if (!!extraPredicate) {
839 cv->wait(lock, stop_waiting);
840 hr = stopped ? ERROR_ABANDONED_WAIT_0 : S_OK;
841 }
842 else {
843 hr = E_INVALIDARG;
844 LOG_DBG << "waitCommandResponse() requires a predicate condition when timeout parameter is < 0.";
845 }
846 }
847 if (SUCCEEDED(hr) && !!response) {
848 unique_lock lock(tr->mutex);
849 *response = move(tr->response);
850 }
851
852 unique_lock vlock(mtxResponses);
853 reponses.erase(token);
854 return hr;
855 }
856
857#if 0 // not used
858 // Waits for a response to a specific command token using an async thread.
859 HRESULT waitCommandResponseAsync(uint32_t token, Command *response, uint32_t timeout = 0, std::function<bool(void)> extraPredicate = nullptr)
860 {
861 if (!timeout)
862 timeout = settings.networkTimeout;
863 future<HRESULT> taskResult = async(launch::async, &Private::waitCommandResponse, this, token, response, timeout, extraPredicate);
864 const future_status taskStat = taskResult.wait_for(chrono::milliseconds(timeout * 2));
865 if (taskStat == future_status::ready)
866 return taskResult.get();
867 return E_TIMEOUT;
868 }
869#endif
870
871 // Waits for completion of list request results, or a timeout. This is used on a new thread.
872 void waitListRequestEnd()
873 {
874 auto stop_waiting = [this]() {
875 return listResult.nextTimeout.load() >= Clock::now();
876 };
877
878 Command response;
879 HRESULT hr = waitCommandResponse(listResult.token, &response, -1, stop_waiting);
880 if (hr == ERROR_ABANDONED_WAIT_0) {
881 LOG_ERR << "List request timed out.";
882 hr = E_TIMEOUT;
883 }
884 else if (hr != S_OK) {
885 LOG_ERR << "List request failed with result: " << LOG_HR(hr);
886 }
887 else if (response.commandId != CommandId::Ack) {
888 LOG_WRN << "Server returned Nak for list request of " << Utilities::getEnumName(listResult.listType.load(), LookupItemTypeNames);
889 hr = E_FAIL;
890 }
891 if (listCb)
892 invokeCallback(listCb, ListResult { listResult.listType.load(), hr, listResult.getResult() });
893 listResult.reset(); // reset to none to indicate the current list request is finished
894 }
895
896#pragma endregion
897#pragma region Server-side logging commands ----------------------------------------------
898
899 void setServerLogLevel(LogLevel level, LogFacility fac = LogFacility::Remote) {
900 if (fac == LogFacility::None || fac > LogFacility::All)
901 return;
902 if (+fac & +LogFacility::Remote)
903 settings.logLevel(LogSource::Server, LogFacility::Remote) = level;
904 if (+fac & +LogFacility::Console)
905 settings.logLevel(LogSource::Server, LogFacility::Console) = level;
906 if (+fac & +LogFacility::File)
907 settings.logLevel(LogSource::Server, LogFacility::File) = level;
908
909 updateServerLogLevel(fac);
910 }
911
912 void updateServerLogLevel(LogFacility fac = LogFacility::Remote) {
913 if (checkInit() && (!(+fac & +LogFacility::Remote) || SUCCEEDED(registerLogDataArea())))
914 sendServerCommand(Command(CommandId::Log, +settings.logLevel(LogSource::Server, fac), nullptr, (double)fac));
915 }
916
917 HRESULT registerLogDataArea() {
918 if (logCDAcreated || settings.logLevel(LogSource::Server, LogFacility::Remote) == LogLevel::None)
919 return S_OK;
920 HRESULT hr;
921 // register log area for reading; server can write to this channel
922 if SUCCEEDED(hr = registerDataArea(CDA_NAME_LOG_PFX, CLI_DATA_LOG, CLI_DATA_LOG, sizeof(LogRecord), false)) {
923 // start/stop listening on the log channel
924 INVOKE_SIMCONNECT(RequestClientData, hSim, (SIMCONNECT_CLIENT_DATA_ID)CLI_DATA_LOG, (SIMCONNECT_DATA_REQUEST_ID)DATA_REQ_LOG,
925 (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)CLI_DATA_LOG, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET, 0UL, 0UL, 0UL, 0UL);
926 }
927 logCDAcreated = SUCCEEDED(hr);
928 return hr;
929 }
930
931#pragma endregion
932#pragma region Remote Variables accessors ----------------------------------------------
933
934 // shared for getVariable() and setLocalVariable()
935 const string buildVariableCommandString(const VariableRequest &v, bool isLocal) const
936 {
937 const bool isIndexed = isLocal || Utilities::isIndexedVariableType(v.variableType);
938 const bool hasUnits = isLocal || Utilities::isUnitBasedVariableType(v.variableType);
939 string sValue = (isIndexed && v.variableId > -1 ? to_string(v.variableId) : v.variableName);
940 if (sValue.empty())
941 return sValue;
942 if (v.variableType == 'A' && v.simVarIndex)
943 sValue += ':' + to_string(v.simVarIndex);
944 if (hasUnits) {
945 if (isIndexed && v.unitId > -1)
946 sValue += ',' + to_string(v.unitId);
947 else if (!v.unitName.empty())
948 sValue += ',' + v.unitName;
949 }
950 return sValue;
951 }
952
953 HRESULT getVariable(const VariableRequest &v, double *result, std::string *sResult = nullptr, double dflt = 0.0)
954 {
955 const string sValue = buildVariableCommandString(v, false);
956 if (sValue.empty() || sValue.length() >= STRSZ_CMD)
957 return E_INVALIDARG;
958
959 HRESULT hr;
960 Command response;
961 if FAILED(hr = sendCommandWithResponse(Command(v.createLVar && v.variableType == 'L' ? CommandId::GetCreate : CommandId::Get, v.variableType, sValue.c_str(), dflt), &response))
962 return hr;
963 if (response.commandId != CommandId::Ack) {
964 LOG_WRN << "Get Variable request for " << quoted(sValue) << " returned Nak response. Reason, if any: " << quoted(response.sData);
965 return E_FAIL;
966 }
967 if (result)
968 *result = response.fData;
969 if (sResult)
970 *sResult = response.sData;
971 return S_OK;
972 }
973
974 HRESULT setLocalVariable(const VariableRequest &v, const double value)
975 {
976 const string sValue = buildVariableCommandString(v, true);
977 if (sValue.empty() || sValue.length() >= STRSZ_CMD)
978 return E_INVALIDARG;
979 return sendServerCommand(Command(v.createLVar ? CommandId::SetCreate : CommandId::Set, 'L', sValue.c_str(), value));
980 }
981
982 HRESULT setVariable(const VariableRequest &v, const double value)
983 {
984 if (v.variableType == 'L')
985 return setLocalVariable(v, value);
986
987 if (!Utilities::isSettableVariableType(v.variableType)) {
988 LOG_WRN << "Cannot Set a variable of type '" << v.variableType << "'.";
989 return E_INVALIDARG;
990 }
991 // All other variable types can only be set via calculator code and require a string-based name
992 if (v.variableName.empty()) {
993 LOG_WRN << "A variable name is required to set variable of type '" << v.variableType << "'.";
994 return E_INVALIDARG;
995 }
996 if (v.unitId > -1 && v.unitName.empty()) {
997 LOG_WRN << "A variable of type '" << v.variableType << "' unit ID; use a unit name instead.";
998 return E_INVALIDARG;
999 }
1000
1001 // Build the RPN code string.
1002 ostringstream codeStr = ostringstream();
1003 codeStr << fixed << setprecision(7) << value;
1004 codeStr << " (>" << v.variableType << ':' << v.variableName;
1005 if (v.variableType == 'A' && v.simVarIndex)
1006 codeStr << ':' << (uint16_t)v.simVarIndex;
1007 if (!v.unitName.empty())
1008 codeStr << ',' << v.unitName;
1009 codeStr << ')';
1010
1011 if (codeStr.tellp() == -1 || (size_t)codeStr.tellp() >= STRSZ_CMD)
1012 return E_INVALIDARG;
1013
1014 // Send it.
1015 return sendServerCommand(Command(CommandId::Exec, +CalcResultType::None, codeStr.str().c_str()));
1016 }
1017
1018 HRESULT setStringVariable(const VariableRequest &v, const std::string &value)
1019 {
1020 if (!checkInit()) {
1021 LOG_ERR << "Simulator not connected, cannot set string variable.";
1022 return E_NOT_CONNECTED;
1023 }
1024 if (v.variableType != 'A') {
1025 LOG_WRN << "Cannot Set a string value on variable of type '" << v.variableType << "'.";
1026 return E_INVALIDARG;
1027 }
1028 if (v.variableName.empty()) {
1029 LOG_WRN << "A variable name is required to set variable of type '" << v.variableType << "'.";
1030 return E_INVALIDARG;
1031 }
1032
1033 uint32_t mapId = 0;
1034 {
1035 // Check for existing mapped ID
1036 shared_lock lock { mtxMappedVarNames };
1037 const auto pos = mappedVarsNameCache.find(v.variableName);
1038 if (pos != mappedVarsNameCache.cend())
1039 mapId = pos->second;
1040 }
1041 if (!mapId) {
1042 // Create a new mapping and save it
1043 HRESULT hr;
1044 if FAILED(hr = INVOKE_SIMCONNECT(AddToDataDefinition, hSim, (SIMCONNECT_DATA_DEFINITION_ID)nextMappedVarID, v.variableName.c_str(), (const char*)nullptr, SIMCONNECT_DATATYPE_STRINGV, 0.0f, SIMCONNECT_UNUSED))
1045 return hr;
1046 mapId = nextMappedVarID++;
1047 unique_lock lock { mtxMappedVarNames };
1048 mappedVarsNameCache.insert(std::pair{ v.variableName, mapId });
1049 }
1050 // Use SimConnect to set the value.
1051 return INVOKE_SIMCONNECT(SetDataOnSimObject, hSim, (SIMCONNECT_DATA_DEFINITION_ID)mapId, SIMCONNECT_OBJECT_ID_USER, 0UL, 0UL, (DWORD)(value.length() + 1), (void*)value.c_str());
1052 }
1053
1054#pragma endregion
1055#pragma region Data Requests ----------------------------------------------
1056
1057 TrackedRequest *findRequest(uint32_t id)
1058 {
1059 shared_lock lock{mtxRequests};
1060 const requestMap_t::iterator pos = requests.find(id);
1061 if (pos != requests.cend())
1062 return &pos->second;
1063 return nullptr;
1064 }
1065
1066 const TrackedRequest *findRequest(uint32_t id) const {
1067 shared_lock lock{mtxRequests};
1068 const requestMap_t::const_iterator pos = requests.find(id);
1069 if (pos != requests.cend())
1070 return &pos->second;
1071 return nullptr;
1072 }
1073
1074 // Writes DataRequest data to the corresponding CDA. If the DataRequest::requestType == None, the request will be deleted by the server.
1075 HRESULT writeDataRequest(const DataRequest &req)
1076 {
1077 if (!isConnected()) {
1078 LOG_ERR << "Server not connected, cannot submit DataRequest " << req;
1079 return E_NOT_CONNECTED;
1080 }
1081 LOG_DBG << "Sending request: " << req;
1082 return INVOKE_SIMCONNECT(SetClientData, hSim, (SIMCONNECT_CLIENT_DATA_ID)CLI_DATA_REQUEST, (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)CLI_DATA_REQUEST, SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0UL, (DWORD)sizeof(DataRequest), (void *)&req);
1083 }
1084
1085 // Writes DataRequest data to the corresponding CDA and waits for an Ack/Nak from server.
1086 // If the DataRequest::requestType == None, the request will be deleted by the server and the response wait is skipped.
1087 HRESULT sendDataRequest(const DataRequest &req, bool async)
1088 {
1089 HRESULT hr;
1090 if FAILED(hr = writeDataRequest(req))
1091 return hr;
1092
1093 // check if just deleting an existing request and don't wait around for that response
1094 if (async || req.requestType == RequestType::None)
1095 return hr;
1096
1097 shared_ptr<condition_variable_any> cv = make_shared<condition_variable_any>();
1098 enqueueTrackedResponse(req.requestId, weak_ptr(cv));
1099 Command response;
1100 if FAILED(hr = waitCommandResponse(req.requestId, &response)) {
1101 LOG_ERR << "Data Request timed out after " << settings.networkTimeout << "ms.";
1102 }
1103 else if (response.commandId != CommandId::Ack) {
1104 hr = E_FAIL;
1105 LOG_ERR << "Data Request returned Nak response for DataRequest ID " << req.requestId << ". Reason, if any: " << quoted(response.sData);
1106 }
1107 return hr;
1108 }
1109
1110 HRESULT registerDataRequestArea(const TrackedRequest * const tr, bool isNewRequest, bool dataAllocChanged = false)
1111 {
1112 HRESULT hr;
1113 shared_lock lock{mtxRequests};
1114 if (isNewRequest || dataAllocChanged) {
1115 if (isNewRequest) {
1116 // Create & allocate the data area which will hold result value (server can write to this channel)
1117 if FAILED(hr = registerDataArea(CDA_NAME_DATA_PFX + clientName + '.' + to_string(tr->requestId), tr->dataId, tr->dataId, tr->valueSize, false, true, max(tr->deltaEpsilon, 0.0f)))
1118 return hr;
1119 }
1120 else if (dataAllocChanged) {
1121 // remove definition, ignore errors (they will be logged)
1122 deregisterDataRequestArea(tr);
1123 // re-add definition, and now do not ignore errors
1124 if FAILED(hr = SimConnectHelper::addClientDataDefinition(hSim, tr->dataId, tr->valueSize, max(tr->deltaEpsilon, 0.0f)))
1125 return hr;
1126 }
1127 }
1128 // (re)start listening for result
1129 return INVOKE_SIMCONNECT(
1130 RequestClientData, hSim,
1131 (SIMCONNECT_CLIENT_DATA_ID)tr->dataId, (SIMCONNECT_DATA_REQUEST_ID)tr->requestId + SIMCONNECTID_LAST,
1132 (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)tr->dataId, SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET, (tr->deltaEpsilon < 0.0f ? 0UL : SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED), 0UL, 0UL, 0UL
1133 );
1134 }
1135
1136 // Note that this method does NOT acquire the requests mutex.
1137 HRESULT deregisterDataRequestArea(const TrackedRequest * const tr) const
1138 {
1139 // We need to first suspend updates for this request before removing it, otherwise it seems SimConnect will sometimes crash
1140 INVOKE_SIMCONNECT(
1141 RequestClientData, hSim,
1142 (SIMCONNECT_CLIENT_DATA_ID)tr->dataId, (SIMCONNECT_DATA_REQUEST_ID)tr->requestId + SIMCONNECTID_LAST,
1143 (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)tr->dataId, SIMCONNECT_CLIENT_DATA_PERIOD_NEVER, 0UL, 0UL, 0UL, 0UL
1144 );
1145 // Now we can safely (we hope) remove the SimConnect definition.
1146 return SimConnectHelper::removeClientDataDefinition(hSim, tr->dataId);
1147 }
1148
1149 HRESULT addOrUpdateRequest(const DataRequest &req, bool async)
1150 {
1151 if (req.requestType == RequestType::None)
1152 return removeRequest(req.requestId);
1153
1154 // preliminary validation
1155 if (req.nameOrCode[0] == '\0') {
1156 LOG_ERR << "Error in DataRequest ID: " << req.requestId << "; Parameter 'nameOrCode' cannot be empty.";
1157 return E_INVALIDARG;
1158 }
1159
1160 const uint32_t actualValSize = Utilities::getActualValueSize(req.valueSize);
1161 if (actualValSize > SIMCONNECT_CLIENTDATA_MAX_SIZE) {
1162 LOG_ERR << "Error in DataRequest ID: " << req.requestId << "; Value size " << actualValSize << " exceeds SimConnect maximum size " << SIMCONNECT_CLIENTDATA_MAX_SIZE;
1163 return E_INVALIDARG;
1164 }
1165 if (totalDataAlloc + actualValSize > SIMCONNECT_DATA_ALLOC_LIMIT) {
1166 LOG_ERR << "Error in DataRequest ID: " << req.requestId << "; Adding request with value size " << actualValSize << " would exceed SimConnect total maximum size of " << SIMCONNECT_DATA_ALLOC_LIMIT;
1167 return E_INVALIDARG;
1168 }
1169
1170 TrackedRequest *tr = findRequest(req.requestId);
1171 const bool isNewRequest = (tr == nullptr);
1172 bool dataAllocationChanged = false;
1173
1174 if (isNewRequest) {
1175 unique_lock lock{mtxRequests};
1176 tr = &requests.try_emplace(req.requestId, req, nextDefId++).first->second;
1177 }
1178 else {
1179 if (actualValSize > tr->dataSize) {
1180 LOG_ERR << "Value size cannot be increased after request is created.";
1181 return E_INVALIDARG;
1182 }
1183 // flag if the definition size or the delta E has changed
1184 dataAllocationChanged = (actualValSize != tr->dataSize || !fuzzyCompare(req.deltaEpsilon, tr->deltaEpsilon));
1185 // update the tracked request from new request data
1186 unique_lock lock{mtxRequests};
1187 *tr = req;
1188 }
1189
1190 HRESULT hr = S_OK;
1191 if (isConnected()) {
1192 // Register the CDA and subscribe to data value changes
1193 if (isNewRequest || dataAllocationChanged)
1194 hr = registerDataRequestArea(tr, isNewRequest, dataAllocationChanged);
1195 if SUCCEEDED(hr) {
1196 // send the request and wait for Ack; Request may timeout or return a Nak.
1197 hr = sendDataRequest(req, async);
1198 }
1199 if (FAILED(hr) && isNewRequest) {
1200 // delete a new request if anything failed
1201 removeRequest(req.requestId);
1202 }
1203 LOG_TRC << (FAILED(hr) ? "FAILED" : isNewRequest ? "Added" : "Updated") << " request: " << req;
1204 }
1205 else {
1206 LOG_TRC << "Queued " << (isNewRequest ? "New" : "Updated") << " request: " << req;
1207 }
1208
1209 return hr;
1210 }
1211
1212 HRESULT removeRequest(const uint32_t requestId)
1213 {
1214 TrackedRequest *tr = findRequest(requestId);
1215 if (!tr) {
1216 LOG_WRN << "DataRequest ID " << requestId << " not found.";
1217 return E_FAIL;
1218 }
1219
1220 unique_lock lock{mtxRequests};
1221 if (isConnected()) {
1222 if FAILED(deregisterDataRequestArea(tr))
1223 LOG_WRN << "Failed to clear ClientDataDefinition in SimConnect, check log messages.";
1224 if FAILED(writeDataRequest(DataRequest(requestId, 0, RequestType::None)))
1225 LOG_WRN << "Server removal of request " << requestId << " failed or timed out, check log messages.";
1226 }
1227 requests.erase(requestId);
1228 LOG_TRC << "Removed Data Request " << requestId;
1229 return S_OK;
1230 }
1231
1232 // this (re)registers all saved data requests with the server (not SimConnect), or deletes any requests flagged for deletion while offline.
1233 // called from connectServer()
1234 void registerAllDataRequests()
1235 {
1236 if (!isConnected())
1237 return;
1238 shared_lock lock(mtxRequests);
1239 for (const auto & [_, tr] : requests) {
1240 if (tr.requestType != RequestType::None)
1241 writeDataRequest(tr);
1242 }
1243 }
1244
1245 // this only creates the SimConnect data definitions, not the server-side entries
1246 // called from connectSimulator()
1247 void registerAllDataRequestAreas()
1248 {
1249 if (!checkInit())
1250 return;
1251 for (const auto & [_, tr] : requests)
1252 registerDataRequestArea(&tr, true);
1253 }
1254
1255#pragma endregion Data Requests
1256
1257#pragma region Calculator Events --------------------------------------------
1258
1259 TrackedEvent *findTrackedEvent(uint32_t eventId)
1260 {
1261 shared_lock lock{mtxEvents};
1262 const eventMap_t::iterator pos = events.find(eventId);
1263 if (pos != events.cend())
1264 return &pos->second;
1265 return nullptr;
1266 }
1267
1268 // sends event registration to server. If event is being deleted and sending succeeds, event is removed from tracked events list.
1269 HRESULT sendEventRegistration(TrackedEvent *ev, bool resendName = false)
1270 {
1271 if (!ev)
1272 return E_INVALIDARG;
1273 HRESULT hr;
1274 if (ev->code.empty()) { // delete event
1275 hr = sendServerCommand(Command(CommandId::Register, ev->eventId));
1276 if SUCCEEDED(hr) {
1277 unique_lock lock(mtxEvents);
1278 events.erase(ev->eventId);
1279 }
1280 return hr;
1281 }
1282
1283 const string evStr = (!ev->sentToServer || resendName) && !ev->name.empty() ? ev->name + '$' + ev->code : ev->code;
1284 return sendServerCommand(Command(CommandId::Register, ev->eventId, evStr.c_str()));
1285 }
1286
1287 HRESULT sendEventRegistration(uint32_t eventId) {
1288 return sendEventRegistration(findTrackedEvent(eventId));
1289 }
1290
1291 // called from connectServer()
1292 void registerAllEvents()
1293 {
1294 vector<uint32_t> removals {};
1295 shared_lock lock(mtxEvents);
1296 for (auto & [key, ev] : events) {
1297 if (ev.code.empty())
1298 removals.push_back(key);
1299 else
1300 sendEventRegistration(&ev, true);
1301 }
1302 lock.unlock();
1303 for (const uint32_t &id : removals)
1304 sendEventRegistration(id);
1305 }
1306
1307#pragma endregion
1308
1309#pragma region Simulator Key Events -------------------------------------------
1310
1311 // Writes KeyEvent data to the corresponding CDA.
1312 HRESULT writeKeyEvent(const KeyEvent &kev) const
1313 {
1314 if (!isConnected()) {
1315 LOG_ERR << "Server not connected, cannot send " << kev;
1316 return E_NOT_CONNECTED;
1317 }
1318 LOG_TRC << "Sending: " << kev;
1319 return INVOKE_SIMCONNECT(SetClientData, hSim, (SIMCONNECT_CLIENT_DATA_ID)CLI_DATA_KEYEVENT, (SIMCONNECT_CLIENT_DATA_DEFINITION_ID)CLI_DATA_KEYEVENT, SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0UL, (DWORD)sizeof(KeyEvent), (void *)&kev);
1320 }
1321
1322 // Custom Events
1323
1324 // This private version does _not_ check if the event name is valid. The `mtxKeyEventNames` _must_ be unlocked before entry.
1325 HRESULT registerCustomKeyEvent(const std::string &name, uint32_t *pRetId, bool useLegacy)
1326 {
1327 // Prevent multiple threads from registering the same Event
1328 unique_lock lock(mtxKeyEventNames);
1329 // Check for duplicates.
1330 const auto pos = keyEventNameCache.find(name);
1331 if (pos != keyEventNameCache.cend()) {
1332 // Custom Event already exists - return the eventId if required
1333 if (pRetId)
1334 *pRetId = pos->second;
1335 return S_OK;
1336 }
1337
1338 // Register a new event mapping with a unique ID. These start at CUSTOM_KEY_EVENT_ID_MIN.
1339 uint32_t keyId = nextCustomEventID++;
1340 // Use high bit as flag of ID for forcing "legacy" SimConnect_TransmitClientEvent() usage as trigger.
1341 if (useLegacy)
1342 keyId |= CUSTOM_KEY_EVENT_LEGACY_TRIGGER_FLAG;
1343 // Cache the event name and ID. This is possibly used for lookups later in `sendKeyEvent(string)` and/or to register event mappings when (re)connecting to SimConnect.
1344 keyEventNameCache.insert(std::pair{ name, keyId });
1345
1346 if (pRetId)
1347 *pRetId = keyId;
1348
1349 // Try to register the new Event now if already connected to SimConnect.
1350 if (simConnected)
1351 return mapCustomKeyEvent(keyId, name);
1352 LOG_DBG << "Queued registration of custom simulator key event " << quoted(name) << " with ID " << keyId << ".";
1353 return S_OK;
1354 }
1355
1356 // Invokes SimConnect_MapClientEventToSimEvent() with given name and id
1357 HRESULT mapCustomKeyEvent(uint32_t id, const std::string &name) const
1358 {
1359 const HRESULT hr = SimConnectHelper::newClientEvent(hSim, id, name);
1360 if SUCCEEDED(hr)
1361 LOG_DBG << "Registered custom simulator key event " << quoted(name) << " with id " << id << ".";
1362 else
1363 LOG_ERR << "Registration custom simulator key event " << quoted(name) << " for id " << id << " failed with HRESULT " << LOG_HR(hr);
1364 return hr;
1365 }
1366
1367 // called from connectSimulator()
1368 void mapAllCustomKeyEvents()
1369 {
1370 shared_lock lock(mtxKeyEventNames);
1371 for (const auto& [name, id] : keyEventNameCache) {
1372 if (id >= CUSTOM_KEY_EVENT_ID_MIN)
1373 mapCustomKeyEvent(id, name);
1374 }
1375 }
1376
1377 // Sends a registered Custom Event using TransmitClientEvent(_EX1)
1378 HRESULT sendSimCustomKeyEvent(uint32_t keyEventId, DWORD v1, DWORD v2, DWORD v3, DWORD v4, DWORD v5) const
1379 {
1380 if (!simConnected) {
1381 LOG_ERR << "Simulator not connected, cannot send a Custom Key Event.";
1382 return E_NOT_CONNECTED;
1383 }
1384
1385 LOG_TRC << "Invoking " << ((keyEventId & CUSTOM_KEY_EVENT_LEGACY_TRIGGER_FLAG) ? "TransmitClientEvent" : "TransmitClientEvent_EX1") << " with Event ID " << keyEventId << "; values: " << v1 << "; " << v2 << "; " << v3 << "; " << v4 << "; " << v5 << ';';
1386 if (keyEventId & CUSTOM_KEY_EVENT_LEGACY_TRIGGER_FLAG)
1387 return INVOKE_SIMCONNECT(TransmitClientEvent, hSim, SIMCONNECT_OBJECT_ID_USER, (SIMCONNECT_CLIENT_EVENT_ID)keyEventId, v1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
1388 return INVOKE_SIMCONNECT(TransmitClientEvent_EX1, hSim, SIMCONNECT_OBJECT_ID_USER, (SIMCONNECT_CLIENT_EVENT_ID)keyEventId, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY, v1, v2, v3, v4, v5);
1389 }
1390
1391#pragma endregion
1392
1393#pragma region SimConnect message processing ----------------------------------------------
1394
1395 static void CALLBACK dispatchMessage(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext) {
1396 static_cast<Private*>(pContext)->onSimConnectMessage(pData, cbData);
1397 }
1398
1399 void dispatchLoop()
1400 {
1401 LOG_DBG << "Dispatch loop started.";
1402 const HANDLE waitEvents[] = { hDispatchStopEvent, hSimEvent };
1403 DWORD hr;
1404 runDispatchLoop = true;
1405 while (runDispatchLoop) {
1406 hr = WaitForMultipleObjects(2, waitEvents, false, DISPATCH_LOOP_WAIT_TIME);
1407 switch (hr) {
1408 case WAIT_TIMEOUT:
1409 continue;
1410 case WAIT_OBJECT_0 + 1: // hSimEvent
1411 SimConnect_CallDispatch(hSim, Private::dispatchMessage, this);
1412 continue;
1413 case WAIT_OBJECT_0: // hDispatchStopEvent
1414 break;
1415 default:
1416 LOG_ERR << "Dispatch loop WaitForMultipleObjects returned: " << LOG_HR(hr) << " Error: " << LOG_HR(GetLastError());
1417 break;
1418 }
1419 runDispatchLoop = false;
1420 }
1421 LOG_DBG << "Dispatch loop stopped.";
1422 }
1423
1424 void onSimConnectMessage(SIMCONNECT_RECV *pData, DWORD cbData)
1425 {
1426 LOG_TRC << LOG_SC_RECV(pData);
1427 switch (pData->dwID) {
1428
1429 case SIMCONNECT_RECV_ID_CLIENT_DATA: {
1430 SIMCONNECT_RECV_CLIENT_DATA* data = (SIMCONNECT_RECV_CLIENT_DATA*)pData;
1431 LOG_TRC << LOG_SC_RCV_CLIENT_DATA(data);
1432 // dwSize always under-reports by 4 bytes when sizeof(SIMCONNECT_RECV_CLIENT_DATA) is subtracted, and the minimum reported size is 4 bytes even for 0-3 bytes of actual data.
1433 const size_t dataSize = (size_t)pData->dwSize + 4 - sizeof(SIMCONNECT_RECV_CLIENT_DATA);
1434 switch (data->dwRequestID)
1435 {
1436 case DATA_REQ_RESPONSE: {
1437 // be paranoid
1438 if (dataSize != sizeof(Command)) {
1439 LOG_CRT << "Invalid Command struct data size! Expected " << sizeof(Command) << " but got " << dataSize;
1440 return;
1441 }
1442 // dwData is our command struct
1443 const Command *const cmd = reinterpret_cast<const Command *const>(&data->dwData);
1444 LOG_DBG << "Got Command: " << *cmd;
1445 bool checkTracking = true;
1446 switch (cmd->commandId)
1447 {
1448 case CommandId::Ack:
1449 case CommandId::Nak: {
1450 // which command is this ack/nak for?
1451 switch ((CommandId)cmd->uData) {
1452 // Connected response
1453 case CommandId::Connect:
1454 serverConnected = cmd->commandId == CommandId::Ack && cmd->token == clientId;
1455 serverVersion = (uint32_t)cmd->fData;
1456 checkTracking = false;
1457 break;
1458 default:
1459 break;
1460 }
1461 // invoke result callback if anyone is listening
1462 invokeCallback(cmdResultCb, *cmd);
1463 break;
1464 }
1465
1466 // respond to pings to stay alive
1467 case CommandId::Ping:
1468 sendServerCommand(Command(CommandId::Ack, (uint32_t)CommandId::Ping));
1469 checkTracking = false;
1470 break;
1471
1472 // incoming list results, one at a time until Ack of List command.
1473 case CommandId::List: {
1474 unique_lock lock(listResult.mutex);
1475 if (cmd->token == listResult.token) {
1476 listResult.nextTimeout = Clock::now() + chrono::milliseconds(settings.networkTimeout);
1477 listResult.result.emplace_back((int)cmd->uData, cmd->sData);
1478 }
1479 else {
1480 LOG_WRN << "Received unexpected list result for wrong command token. Expected " << listResult.token << " got " << cmd->token;
1481 }
1482 checkTracking = false;
1483 break;
1484 }
1485
1486 // Server is disconnecting (shutting down/etc).
1487 case CommandId::Disconnect:
1488 disconnectServer(false);
1489 checkTracking = false;
1490 break;
1491
1492 default:
1493 break;
1494 } // commandId
1495
1496 // Check if this command response is tracked and possibly awaited.
1497 if (checkTracking) {
1498 if (TrackedResponse *tr = findTrackedResponse(cmd->token)) {
1499 if (auto cv = tr->cv.lock()) {
1500 LOG_TRC << "Got awaited command response, notifying CV.";
1501 //unique_lock lock(mtxResponses);
1502 unique_lock lock(tr->mutex);
1503 tr->response = *cmd;
1504 lock.unlock();
1505 cv->notify_all();
1506 }
1507 else {
1508 unique_lock lock(mtxResponses);
1509 reponses.erase(cmd->token);
1510 LOG_TRC << "Got tracked command response but no CV, removing record.";
1511 }
1512 }
1513 }
1514 // invoke response callback if anyone is listening
1515 invokeCallback(respCb, *cmd);
1516 break;
1517 } // DATA_REQ_RESPONSE
1518
1519 case DATA_REQ_LOG: {
1520 // be paranoid
1521 if (dataSize != sizeof(LogRecord)) {
1522 LOG_CRT << "Invalid LogRecord struct data size! Expected " << sizeof(LogRecord) << " but got " << dataSize;
1523 return;
1524 }
1525 // dwData is our log record struct
1526 const LogRecord *const log = reinterpret_cast<const LogRecord *const>(&data->dwData);
1527 LOG_TRC << "Got Log Record: " << *log;
1528 invokeCallback(logCb, *log, LogSource::Server);
1529 break;
1530 } // DATA_REQ_LOG
1531
1532 // possible request data
1533 default:
1534 if (data->dwRequestID >= SIMCONNECTID_LAST) {
1535 TrackedRequest *tr = findRequest(data->dwRequestID - SIMCONNECTID_LAST);
1536 if (!tr){
1537 LOG_WRN << "DataRequest ID " << data->dwRequestID - SIMCONNECTID_LAST << " not found in tracked requests.";
1538 return;
1539 }
1540 // be paranoid; note that the reported pData->dwSize is never less than 4 bytes.
1541 if (dataSize < tr->dataSize) {
1542 LOG_CRT << "Invalid data result size! Expected " << tr->dataSize << " but got " << dataSize;
1543 return;
1544 }
1545 unique_lock datalock(tr->m_dataMutex);
1546 memcpy(tr->data.data(), (void*)&data->dwData, tr->dataSize);
1547 tr->lastUpdate = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
1548 datalock.unlock();
1549 shared_lock rdlock(mtxRequests);
1550 LOG_TRC << "Got data result for request: " << *tr;
1551 invokeCallback(dataCb, tr->toRequestRecord());
1552 break;
1553 }
1554 LOG_WRN << "Got unknown RequestID in SIMCONNECT_RECV_CLIENT_DATA struct: " << data->dwRequestID;
1555 return;
1556 } // data->dwRequestID
1557
1558 //serverLastSeen = Clock::now();
1559 break;
1560 } // SIMCONNECT_RECV_ID_CLIENT_DATA
1561
1562 case SIMCONNECT_RECV_ID_EVENT: {
1563 SIMCONNECT_RECV_EVENT *data = static_cast<SIMCONNECT_RECV_EVENT *>(pData);
1564 LOG_TRC << LOG_SC_RECV_EVENT(data);
1565
1566 switch (data->uEventID) {
1567 case CLI_EVENT_PING_RESP:
1568 serverVersion = data->dwData;
1569 serverLastSeen = Clock::now();
1570 LOG_DBG << "Got ping response at " << Utilities::timePointToString(serverLastSeen.load()) << " with version " << STREAM_HEX8(serverVersion);
1571 break;
1572
1573 default:
1574 return;
1575 }
1576 break;
1577 } // SIMCONNECT_RECV_ID_EVENT
1578
1579 case SIMCONNECT_RECV_ID_OPEN:
1580 simConnected = true;
1581 SimConnectHelper::logSimVersion(pData);
1582 break;
1583
1584 case SIMCONNECT_RECV_ID_QUIT: {
1585 LOG_INF << "Received quit command from SimConnect, disconnecting...";
1586 // we're being called from within the message dispatch loop thread; to avoid deadlock we must disconnect asynchronously.
1587 thread([=]() { disconnectSimulator(true); }).detach();
1588 break;
1589 }
1590
1591 case SIMCONNECT_RECV_ID_EXCEPTION:
1592 SimConnectHelper::logSimConnectException(pData);
1593 break;
1594
1595 default:
1596 break;
1597 }
1598 } // onSimConnectMessage()
1599
1600#pragma endregion SimConnect
1601
1602};
1603#pragma endregion WASimClient::Private
1604
1605
1606// -------------------------------------------------------------
1607#pragma region WASimClient class
1608// -------------------------------------------------------------
1609
1610
1611#define d_const const_cast<const Private *>(d.get())
1612
1613WASimClient::WASimClient(uint32_t clientId, const std::string &configFile) :
1614 d(new Private(this, clientId, configFile))
1615{ }
1616
1618 if (isInitialized())
1620 if (d->hDispatchStopEvent)
1621 CloseHandle(d->hDispatchStopEvent);
1622}
1623
1624#pragma region Connections ----------------------------------------------
1625
1626HRESULT WASimClient::connectSimulator(uint32_t timeout)
1627{
1628 if (!d->clientId) {
1629 LOG_ERR << "Client ID must be greater than zero.";
1630 return E_INVALIDARG;
1631 }
1632 return d->connectSimulator(timeout);
1633}
1634
1635HRESULT WASimClient::connectSimulator(int networkConfigId, uint32_t timeout) {
1636 return d->connectSimulator(networkConfigId, timeout);
1637}
1638
1640 d->disconnectSimulator(false);
1641}
1642
1643HRESULT WASimClient::connectServer(uint32_t timeout) {
1644 return d->connectServer(timeout);
1645}
1646
1648 d->disconnectServer(true);
1649}
1650
1651uint32_t WASimClient::pingServer(uint32_t timeout)
1652{
1653 if (!d->checkInit() && FAILED(d->connectSimulator(timeout)))
1654 return 0;
1655
1656 // save the last time we saw the server
1657 const Clock::time_point refTp = d->serverLastSeen;
1658 // Send the previously-registered PING event with this client's ID as the data parameter.
1659 if FAILED(INVOKE_SIMCONNECT(
1660 TransmitClientEvent, d->hSim, SIMCONNECT_OBJECT_ID_USER, (SIMCONNECT_CLIENT_EVENT_ID)Private::CLI_EVENT_PING, (DWORD)d->clientId, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY
1661 ))
1662 return 0;
1663
1664 if (!timeout)
1665 timeout = defaultTimeout();
1666 // The ping response (if any) should come back via the CLI_EVENT_PING_RESP event which we've subscribed to (SIMCONNECT_RECV_ID_EVENT).
1667 if (d->waitCondition([&]() { return !d->runDispatchLoop || d->serverLastSeen.load() > refTp; }, timeout)) {
1668 LOG_INF << "Server responded to ping, reported version: " << STREAM_HEX8(d->serverVersion);
1669 return d->serverVersion;
1670 }
1671 LOG_WRN << "Server did not respond to ping after " << timeout << "ms";
1672 return 0;
1673}
1674
1675#pragma endregion
1676
1677#pragma region Exec calc ----------------------------------------------
1678
1679HRESULT WASimClient::executeCalculatorCode(const std::string &code, CalcResultType resultType, double *fResult, std::string *sResult) const
1680{
1681 if (code.length() >= STRSZ_CMD) {
1682 LOG_ERR << "Code string length " << code.length() << " is greater then maximum size of " << STRSZ_CMD-1;
1683 return E_INVALIDARG;
1684 }
1685
1686 if (resultType == CalcResultType::None)
1687 return d->sendServerCommand(Command(CommandId::Exec, +resultType, code.c_str()));
1688
1689 HRESULT hr;
1690 Command response;
1691 if FAILED(hr = d->sendCommandWithResponse(Command(CommandId::Exec, +resultType, code.c_str()), &response))
1692 return hr;
1693 if (response.commandId != CommandId::Ack) {
1694 LOG_WRN << "Calculator Exec with code " << quoted(code) << " returned Nak response. Reason, if any: " << quoted(response.sData);
1695 return E_FAIL;
1696 }
1697 if (fResult)
1698 *fResult = response.fData;
1699 if (sResult)
1700 *sResult = response.sData;
1701 return hr;
1702}
1703
1704#pragma endregion
1705
1706#pragma region Variable accessors ----------------------------------------------
1707
1708HRESULT WASimClient::getVariable(const VariableRequest & variable, double * pfResult, std::string *psResult)
1709{
1710 if (variable.variableId > -1 && !Utilities::isIndexedVariableType(variable.variableType)) {
1711 LOG_ERR << "Cannot get variable type '" << variable.variableType << "' by index.";
1712 return E_INVALIDARG;
1713 }
1714 return d->getVariable(variable, pfResult, psResult);
1715}
1716
1717HRESULT WASimClient::getLocalVariable(const std::string &variableName, double *pfResult, const std::string &unitName) {
1718 return d->getVariable(VariableRequest(variableName, false, unitName), pfResult);
1719}
1720
1721HRESULT WASimClient::getOrCreateLocalVariable(const std::string &variableName, double *pfResult, double defaultValue, const std::string &unitName) {
1722 return d->getVariable(VariableRequest(variableName, true, unitName), pfResult, nullptr, defaultValue);
1723}
1724
1725
1726HRESULT WASimClient::setVariable(const VariableRequest & variable, const double value) {
1727 return d->setVariable(variable, value);
1728}
1729
1730HRESULT WASimClient::setVariable(const VariableRequest &variable, const std::string &value) {
1731 return d->setStringVariable(variable, value);
1732}
1733
1734HRESULT WASimClient::setLocalVariable(const std::string &variableName, const double value, const std::string &unitName) {
1735 return d->setLocalVariable(VariableRequest(variableName, false, unitName), value);
1736}
1737
1738HRESULT WASimClient::setOrCreateLocalVariable(const std::string &variableName, const double value, const std::string &unitName) {
1739 return d->setLocalVariable(VariableRequest(variableName, true, unitName), value);
1740}
1741
1742#pragma endregion
1743
1744#pragma region Data Requests ----------------------------------------------
1745
1746HRESULT WASimClient::saveDataRequest(const DataRequest &request, bool async) {
1747 return d->addOrUpdateRequest(request, async);
1748}
1749
1750HRESULT WASimClient::removeDataRequest(const uint32_t requestId) {
1751 return d->removeRequest(requestId);
1752}
1753
1754HRESULT WASimClient::updateDataRequest(uint32_t requestId)
1755{
1756 if (d->requests.count(requestId))
1757 return d->sendServerCommand(Command(CommandId::Update, requestId));
1758 return E_FAIL;
1759}
1760
1762{
1763 static const DataRequestRecord nullRecord{ DataRequest((uint32_t)-1, 0, RequestType::None) };
1764
1765 const Private::TrackedRequest *r = d_const->findRequest(requestId);
1766 if (r)
1767 return r->toRequestRecord();
1768 LOG_ERR << "Data Request ID " << requestId << " not found.";
1769 return nullRecord;
1770}
1771
1772vector<DataRequestRecord> WASimClient::dataRequests() const
1773{
1774 vector<DataRequestRecord> ret;
1775 shared_lock lock(d_const->mtxRequests);
1776 ret.reserve(d_const->requests.size());
1777 for (const auto & [_, value] : d_const->requests) {
1778 if (value.requestType != RequestType::None)
1779 ret.emplace_back(value.toRequestRecord());
1780 }
1781 return ret;
1782}
1783
1784vector<uint32_t> WASimClient::dataRequestIdsList() const
1785{
1786 vector<uint32_t> ret;
1787 shared_lock lock(d_const->mtxRequests);
1788 ret.reserve(d_const->requests.size());
1789 for (const auto & [key, value] : d_const->requests) {
1790 if (value.requestType != RequestType::None)
1791 ret.push_back(key);
1792 }
1793 return ret;
1794}
1795
1796HRESULT WASimClient::setDataRequestsPaused(bool paused) const {
1797 if (isConnected()) {
1798 HRESULT hr = d_const->sendServerCommand(Command(CommandId::Subscribe, (paused ? 0 : 1)));
1799 if SUCCEEDED(hr)
1800 d->requestsPaused = paused;
1801 return hr;
1802 }
1803 d->requestsPaused = paused;
1804 return S_OK;
1805}
1806
1807#pragma endregion Data
1808
1809#pragma region Calculator Events ----------------------------------------------
1810
1812{
1813 if (eventData.code.empty())
1814 return removeEvent(eventData.eventId);
1815
1816 const size_t len = eventData.code.length() + (eventData.name.empty() ? 0 : eventData.name.length() + 1);
1817 if (len >= STRSZ_CMD) {
1818 LOG_ERR << "Resulting command string length " << len << " is greater then maximum size of " << STRSZ_CMD-1 << " bytes.";
1819 return E_INVALIDARG;
1820 }
1821
1822 Private::TrackedEvent *ev = d->findTrackedEvent(eventData.eventId);
1823 if (ev) {
1824 if (!eventData.name.empty() && ev->name != eventData.name) {
1825 LOG_ERR << "Cannot change name of event ID " << eventData.eventId << " after it has been registered.";
1826 return E_INVALIDARG;
1827 }
1828 if (ev->code == eventData.code) {
1829 LOG_INF << "No changes detected in code for event ID " << eventData.eventId << ", skipping update";
1830 return S_OK;
1831 }
1832 ev->code = eventData.code;
1833 }
1834 else {
1835 unique_lock lock(d->mtxEvents);
1836 ev = &d->events.emplace(eventData.eventId, Private::TrackedEvent(eventData.eventId, eventData.code, eventData.name)).first->second;
1837 }
1838 if (isConnected())
1839 ev->sentToServer = SUCCEEDED(d->sendEventRegistration(ev)) || ev->sentToServer;
1840 else
1841 LOG_DBG << "Queued event ID " << eventData.eventId << " for next server connection.";
1842
1843 return S_OK;
1844}
1845
1846HRESULT WASimClient::removeEvent(uint32_t eventId)
1847{
1848 Private::TrackedEvent *ev = d->findTrackedEvent(eventId);
1849 if (!ev) {
1850 LOG_ERR << "Event ID " << eventId << " not found.";
1851 return E_INVALIDARG;
1852 }
1853 ev->code.clear();
1854 if (!ev->sentToServer) {
1855 unique_lock lock(d->mtxEvents);
1856 d->events.erase(eventId);
1857 return S_OK;
1858 }
1859 if (isConnected())
1860 d->sendEventRegistration(ev);
1861 else
1862 LOG_DBG << "Queued event ID " << eventId << " for deletion on next server connection.";
1863 return S_OK;
1864}
1865
1866HRESULT WASimClient::transmitEvent(uint32_t eventId) {
1867 return d->sendServerCommand(Command(CommandId::Transmit, eventId));
1868}
1869
1871{
1872 static const RegisteredEvent nullRecord { };
1873 if (Private::TrackedEvent *ev = d->findTrackedEvent(eventId))
1874 return RegisteredEvent(*ev);
1875 LOG_ERR << "Event ID " << eventId << " not found.";
1876 return nullRecord;
1877}
1878
1879vector<RegisteredEvent> WASimClient::registeredEvents() const
1880{
1881 vector<RegisteredEvent> ret;
1882 shared_lock lock(d_const->mtxEvents);
1883 ret.reserve(d_const->events.size());
1884 for (const auto & [_, value] : d_const->events) {
1885 if (!value.code.empty())
1886 ret.emplace_back(RegisteredEvent(value));
1887 }
1888 return ret;
1889}
1890
1891#pragma endregion Calculator Events
1892
1893#pragma region Key Events ----------------------------------------------
1894
1895HRESULT WASimClient::sendKeyEvent(uint32_t keyEventId, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4, uint32_t v5) const
1896{
1897 if (keyEventId < CUSTOM_KEY_EVENT_ID_MIN)
1898 return d_const->writeKeyEvent(KeyEvent(keyEventId, { v1, v2, v3, v4, v5 }, d->nextCmdToken++));
1899 else
1900 return d_const->sendSimCustomKeyEvent(keyEventId, v1, v2, v3, v4, v5);
1901}
1902
1903inline static bool isCustomKeyEventName(const std::string &name) {
1904 return name[0] == '#' || name.find('.') != string::npos;
1905}
1906
1907HRESULT WASimClient::sendKeyEvent(const std::string &keyEventName, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4, uint32_t v5)
1908{
1909 if (keyEventName.empty())
1910 return E_INVALIDARG;
1911
1912 // check the cache first -- this stores both standard key events and custom (mapped) types.
1913 {
1914 shared_lock rdlock(d->mtxKeyEventNames);
1915 const auto pos = d_const->keyEventNameCache.find(keyEventName);
1916 if (pos != d_const->keyEventNameCache.cend())
1917 return sendKeyEvent(pos->second, v1, v2, v3, v4, v5);
1918 // mutex lock and iterator go out of scope
1919 }
1920
1921 // The name wasn't in the cache...
1922 uint32_t keyId;
1923 if (isCustomKeyEventName(keyEventName)) {
1924 // If the event name looks like a "custom" event then try to register it now.
1925 const HRESULT hr = d->registerCustomKeyEvent(keyEventName, &keyId, false);
1926 if FAILED(hr)
1927 return hr;
1928 }
1929 else {
1930 // Otherwise try a lookup() -- this can only work for standard Key Events, and only if we're currently connected.
1931 int32_t id;
1932 const HRESULT hr = lookup(LookupItemType::KeyEventId, keyEventName, &id);
1933 if FAILED(hr)
1934 return hr == E_FAIL ? E_INVALIDARG : hr; // E_FAIL means event name wasn't found at server
1935 keyId = (uint32_t)id;
1936 // save the lookup result
1937 unique_lock rwlock(d->mtxKeyEventNames);
1938 d->keyEventNameCache.insert(std::pair{ keyEventName, keyId });
1939 }
1940
1941 return sendKeyEvent(keyId, v1, v2, v3, v4, v5);
1942}
1943
1944// Custom-named Key Event registration
1945
1946HRESULT WASimClient::registerCustomKeyEvent(const std::string &customEventName, uint32_t *puiCustomEventId, bool useLegacyTransmit)
1947{
1948 if (isCustomKeyEventName(customEventName))
1949 return d->registerCustomKeyEvent(customEventName, puiCustomEventId, useLegacyTransmit);
1950
1951 LOG_ERR << "Custom event name " << quoted(customEventName) << " must start with '#' or include a period.";
1952 return E_INVALIDARG;
1953}
1954
1955HRESULT WASimClient::removeCustomKeyEvent(uint32_t eventId)
1956{
1957 unique_lock lock(d->mtxKeyEventNames);
1958 const auto pos = find_if(cbegin(d_const->keyEventNameCache), cend(d_const->keyEventNameCache), [eventId](const auto &p) { return p.second == eventId; });
1959 if (pos == d_const->keyEventNameCache.cend())
1960 return E_INVALIDARG;
1961 d->keyEventNameCache.erase(pos);
1962 return S_OK;
1963}
1964
1965HRESULT WASimClient::removeCustomKeyEvent(const std::string &customEventName)
1966{
1967 unique_lock lock(d->mtxKeyEventNames);
1968 return d->keyEventNameCache.erase(customEventName) > 0 ? S_OK : E_INVALIDARG;
1969}
1970
1971#pragma endregion Key Events
1972
1973#pragma region Meta Data ----------------------------------------------
1974
1976{
1977 // validation
1978 static const set<LookupItemType> validListLookups { LookupItemType::LocalVariable, LookupItemType::DataRequest, LookupItemType::RegisteredEvent };
1979 if (!validListLookups.count(itemsType)) {
1980 LOG_ERR << "Cannot list item of type " << Utilities::getEnumName(itemsType, LookupItemTypeNames) << ".";
1981 return E_INVALIDARG;
1982 }
1983 // TODO: Allow multiple simultaneous list requests for different item types
1984 if (d->listResult.listType != LookupItemType::None) {
1985 LOG_ERR << "A List request for " << Utilities::getEnumName(d->listResult.listType.load(), LookupItemTypeNames) << " is currently pending.";
1986 return E_INVALIDARG;
1987 }
1988 unique_lock lock(d->listResult.mutex);
1989 d->listResult.listType = itemsType;
1990 d->listResult.nextTimeout = Clock::now() + chrono::milliseconds(defaultTimeout());
1991 HRESULT hr;
1992 if FAILED(hr = d->sendServerCommand(Command(CommandId::List, +itemsType), weak_ptr(d->listResult.cv), &d->listResult.token))
1993 return hr;
1994 lock.unlock();
1995 try {
1996 thread(&Private::waitListRequestEnd, d.get()).detach();
1997 }
1998 catch (exception *e) {
1999 LOG_ERR << "Exception trying to schedule thread for List command: " << e->what();
2000 return E_FAIL;
2001 }
2002 return S_OK;
2003}
2004
2005HRESULT WASimClient::lookup(LookupItemType itemType, const std::string &itemName, int32_t *piResult)
2006{
2007 if (itemName.length() >= STRSZ_CMD) {
2008 LOG_ERR << "Item name length " << itemName.length() << " is greater then maximum size of " << STRSZ_CMD-1 << " bytes.";
2009 return E_INVALIDARG;
2010 }
2011 HRESULT hr;
2012 Command response;
2013 if FAILED(hr = d->sendCommandWithResponse(Command(CommandId::Lookup, +itemType, itemName.c_str()), &response))
2014 return hr;
2015 if (response.commandId != CommandId::Ack) {
2016 LOG_WRN << "Lookup command returned Nak response. Reason, if any: " << quoted(response.sData);
2017 return E_FAIL;
2018 }
2019 LOG_DBG << "Lookup: Server returned ID " << (int)response.fData << " and echo name " << quoted(response.sData);
2020 if (piResult)
2021 *piResult = (int)response.fData;
2022 return hr;
2023}
2024
2025#pragma endregion Meta
2026
2027#pragma region Low Level ----------------------------------------------
2028
2029HRESULT WASimClient::sendCommand(const Command &command) const {
2030 return d->sendServerCommand(command);
2031}
2032
2033HRESULT WASimClient::sendCommandWithResponse(const Command &command, Command *response, uint32_t timeout) {
2034 return d->sendCommandWithResponse(Command(command), response, timeout);
2035}
2036
2037#pragma endregion Low Level
2038
2039#pragma region Status / Network / Logging / Callbacks ----------------------------------------------
2040
2041ClientStatus WASimClient::status() const { return d_const->status; }
2042bool WASimClient::isInitialized() const { return d_const->checkInit(); }
2043bool WASimClient::isConnected() const { return d_const->isConnected(); }
2044uint32_t WASimClient::clientVersion() const { return WSMCMND_VERSION; }
2045uint32_t WASimClient::serverVersion() const { return d_const->serverVersion; }
2046
2047uint32_t WASimClient::defaultTimeout() const { return d_const->settings.networkTimeout; }
2048void WASimClient::setDefaultTimeout(uint32_t ms) { d->settings.networkTimeout = ms; }
2049
2050int WASimClient::networkConfigurationId() const { return d_const->settings.networkConfigId; }
2051void WASimClient::setNetworkConfigurationId(int configId) { d->settings.networkConfigId = configId; }
2052
2053LogLevel WASimClient::logLevel(LogFacility facility, LogSource source) const { return d_const->settings.logLevel(source, facility); }
2055{
2056 switch (source) {
2057 case LogSource::Client:
2058 if ((+facility & +LogFacility::Console) && d_const->settings.logLevel(source, LogFacility::Console) != level)
2059 d->setConsoleLogLevel(level);
2060 if ((+facility & +LogFacility::File) && d_const->settings.logLevel(source, LogFacility::File) != level)
2061 d->setFileLogLevel(level);
2062 if ((+facility & +LogFacility::Remote) && d_const->settings.logLevel(source, LogFacility::Remote) != level)
2063 d->setCallbackLogLevel(level);
2064 break;
2065 case LogSource::Server:
2066 d->setServerLogLevel(level, facility);
2067 }
2068}
2069
2073void WASimClient::setLogCallback(logCallback_t cb) { d->setLogCallback(cb); }
2076
2077#pragma endregion Misc
2078
2079#pragma endregion WASimClient
2080
2081#undef d_const
2082#undef WAIT_CONDITION
2083
2084}; // namespace WASimCommander::Client
bool isInitialized() const
Check if simulator network link is established.
HRESULT registerEvent(const RegisteredEvent &eventData)
Register a reusable event which executes a pre-set RPN calculator code string. The code is pre-compil...
HRESULT sendCommandWithResponse(const Command &command, Command *response, uint32_t timeout=0)
Sends a command, in the form of a WASimCommander::Command structure, to the server for processing and...
RegisteredEvent registeredEvent(uint32_t eventId)
Returns a copy of a RegisteredEvent which has been previously added with registerEvent()....
bool isConnected() const
Check WASimCommander server connection status.
void setDataCallback(dataCallback_t cb)
Sets a callback for value update data arriving from the server. Pass a nullptr value to remove a prev...
uint32_t defaultTimeout() const
Get the current default server response timeout value, which is used in all network requests....
HRESULT getLocalVariable(const std::string &variableName, double *pfResult, const std::string &unitName=std::string())
A convenience version of getVariable(VariableRequest(variableName, false, unitName),...
HRESULT executeCalculatorCode(const std::string &code, WASimCommander::Enums::CalcResultType resultType=WASimCommander::Enums::CalcResultType::None, double *pfResult=nullptr, std::string *psResult=nullptr) const
Run a string of MSFS Gauge API calculator code in RPN format, possibly with some kind of result expec...
~WASimClient()
Any open network connections are automatically closed upon destruction, though it is better to close ...
HRESULT getOrCreateLocalVariable(const std::string &variableName, double *pfResult, double defaultValue=0.0, const std::string &unitName=std::string())
Gets the value of a local variable just like getLocalVariable() but will also create the variable on ...
uint32_t clientVersion() const
Return the current WASimClient version number. Version numbers are in "BCD" format: MAJOR << 24 | MIN...
HRESULT getVariable(const VariableRequest &variable, double *pfResult, std::string *psResult=nullptr)
Get a Variable value by name, with optional named unit type. This is primarily useful for local ('L')...
ClientStatus status() const
Get current connection status of this client.
HRESULT updateDataRequest(uint32_t requestId)
Trigger a data update on a previously-added DataRequest. Designed to refresh data on subscriptions wi...
void setLogCallback(logCallback_t cb)
Sets a callback for logging activity, both from the server and the client itself. Pass a nullptr valu...
void setLogLevel(WASimCommander::Enums::LogLevel level, WASimCommander::Enums::LogFacility facility=WASimCommander::Enums::LogFacility::Remote, LogSource source=LogSource::Client)
Set the current minimum logging severity level for the specified facility and source to level.
HRESULT lookup(WASimCommander::Enums::LookupItemType itemType, const std::string &itemName, int32_t *piResult)
Request server-side lookup of an named item to find the corresponding numeric ID.
HRESULT connectSimulator(uint32_t timeout=0)
Initialize the simulator network link and set up minimum necessary for WASimCommander server ping or ...
HRESULT removeDataRequest(const uint32_t requestId)
Remove a previously-added DataRequest. This clears the subscription and any tracking/meta data from b...
std::vector< DataRequestRecord > dataRequests() const
Returns a list of all data requests which have been added to the Client so far. (These are returned b...
HRESULT sendCommand(const Command &command) const
Sends a command, in the form of a WASimCommander::Command structure, to the server for processing....
HRESULT connectServer(uint32_t timeout=0)
Connect to WASimCommander server. This will implicitly call connectSimulator() first if it hasn't alr...
HRESULT registerCustomKeyEvent(const std::string &customEventName, uint32_t *puiCustomEventId=nullptr, bool useLegacyTransmit=false)
Register a "Custom Simulator [Key] Event" by providing an event name. The method optionally returns t...
HRESULT removeEvent(uint32_t eventId)
Remove an event previously registered with registerEvent() method. This is effectively the same as ca...
void setNetworkConfigurationId(int configId)
SimConnect is used for the network layer. This setting specifies the SimConnect.cfg index to use,...
HRESULT transmitEvent(uint32_t eventId)
Trigger an event previously registered with registerEvent(). This is a more direct alternative to tri...
DataRequestRecord dataRequest(uint32_t requestId) const
Returns a copy of a DataRequestRecord which has been previously added. If the request with the given ...
WASimClient(uint32_t clientId, const std::string &configFile=std::string())
Instantiate the client with a unique ID and optional configuration file path.
HRESULT setOrCreateLocalVariable(const std::string &variableName, const double value, const std::string &unitName=std::string())
Set a Local Variable value by variable name, creating it first if it does not already exist....
HRESULT removeCustomKeyEvent(const std::string &customEventName)
Remove a Custom Event previously registered with registerCustomEvent() method using the event's name....
HRESULT setVariable(const VariableRequest &variable, const double value)
Set a Variable value by name, with optional named unit type. Although any settable variable type can ...
void setCommandResultCallback(commandCallback_t cb)
Sets a callback for delivering command results returned by the server. Pass a nullptr value to remove...
std::vector< uint32_t > dataRequestIdsList() const
Returns a list of all DataRequest::requestIds which have been added to the Client so far.
void disconnectSimulator()
Shut down all network connections (and disconnect WASimCommander server if connected)....
HRESULT setDataRequestsPaused(bool paused) const
Enables or disables all data request subscription updates at the same time. Use this to temporarily s...
void setListResultsCallback(listResultsCallback_t cb)
Sets a callback for list results arriving from the server. Pass a nullptr value to remove a previousl...
uint32_t pingServer(uint32_t timeout=0)
Check if WASimCommander Server exists (Simulator running, the WASIM module is installed and working)....
uint32_t serverVersion() const
Return the version number of the last-connected, or successfully pinged, WASimModule (sever),...
void setResponseCallback(commandCallback_t cb)
Sets a callback for delivering response commands sent to this client by the server module....
HRESULT sendKeyEvent(uint32_t keyEventId, uint32_t v1=0, uint32_t v2=0, uint32_t v3=0, uint32_t v4=0, uint32_t v5=0) const
Can be used to trigger standard Simulator "Key Events" as well as "custom" Gauge API/SimConnect event...
HRESULT list(WASimCommander::Enums::LookupItemType itemsType=WASimCommander::Enums::LookupItemType::LocalVariable)
Send a request for a list update to the server. The results are delivered using the callback set in s...
WASimCommander::Enums::LogLevel logLevel(WASimCommander::Enums::LogFacility facility, LogSource source=LogSource::Client) const
Get the current minimum logging severity level for the specified facility and source.
int networkConfigurationId() const
SimConnect is used for the network layer. This setting specifies the SimConnect.cfg index to use....
void disconnectServer()
Disconnect from the WASimCommander server. This does not close the Simulator network connection (use ...
void setClientEventCallback(clientEventCallback_t cb)
Sets a callback for Client event updates which indicate status changes. Pass a nullptr value to remov...
HRESULT setLocalVariable(const std::string &variableName, const double value, const std::string &unitName=std::string())
A convenience version of setVariable() for Local variable types. Equivalent to setVariable(VariableRe...
HRESULT saveDataRequest(const DataRequest &request, bool async=false)
Add a new WASimCommander::DataRequest for a variable or calculated result, or update an existing data...
std::vector< RegisteredEvent > registeredEvents() const
Returns a list of all registered events which have been added to the Client with registerEvent()....
void setDefaultTimeout(uint32_t ms)
Get current connection status of this client.
WASimCommander::Client namespace. Defines/declares everything needed to interact with the WASimComman...
Definition: enums_impl.h:32
static const HRESULT E_TIMEOUT
Error result: timeout communicating with server.
Definition: WASimClient.h:59
std::function< void __stdcall(const LogRecord &, LogSource)> logCallback_t
Callback function for log entries (from both Client and Server).
Definition: WASimClient.h:75
std::function< void __stdcall(const DataRequestRecord &)> dataCallback_t
Callback function for subscription result data.
Definition: WASimClient.h:74
std::function< void __stdcall(const ListResult &)> listResultsCallback_t
Callback function for delivering list results, eg. of local variables sent from Server.
Definition: WASimClient.h:73
std::function< void __stdcall(const Command &)> commandCallback_t
Callback function for commands sent from server.
Definition: WASimClient.h:76
LogSource
Log entry source, Client or Server.
Definition: enums_impl.h:74
@ Client
Log record from WASimClient.
@ Server
Log record from WASimModule (Server)
std::vector< std::pair< int, std::string > > listResult_t
A mapping of IDs to names.
Definition: structs.h:43
static const HRESULT E_NOT_CONNECTED
Error result: server not connected.
Definition: WASimClient.h:56
static const uint32_t CUSTOM_KEY_EVENT_ID_MIN
Starting ID range for "Custom Key Events" for use with registerCustomKeyEvent() generated IDs....
Definition: WASimClient.h:65
std::function< void __stdcall(const ClientEvent &)> clientEventCallback_t
Callback function for Client events.
Definition: WASimClient.h:72
@ SimDisconnecting
ShuttingDown status.
@ ServerDisconnected
From Connected to SimConnected status change.
@ SimDisconnected
From SimConnected to Initializing status change.
ClientStatus
Client status flags.
Definition: enums_impl.h:36
@ ShuttingDown
closing SimConnect link, before being back in Idle status mode
@ Connected
connected to WASim server
@ Initializing
trying to connect to SimConnect
@ SimConnected
have SimConnect link
@ Connecting
attempting connection to WASim server
@ Idle
no active SimConnect or WASim server connection
WASimCommander::Enums namespace. Contains all enum definitions for the base API.
Definition: enums_impl.h:33
LogLevel
Logging levels.
Definition: enums_impl.h:155
LookupItemType
Types of things to look up or list.
Definition: enums_impl.h:136
@ DataRequest
Saved value subscription for current Client, indexed by requestId and nameOrCode values....
@ RegisteredEvent
Saved calculator event for current Client, indexed by eventId and code values. Available for List and...
CalcResultType
The type of result that calculator code is expected to produce.
Definition: enums_impl.h:109
CommandId
Commands for client-server interaction. Both sides can send commands via dedicated channels by writin...
Definition: enums_impl.h:39
RequestType
Types of things to request or set.
Definition: enums_impl.h:97
LogFacility
Logging destination type.
Definition: enums_impl.h:171
static const size_t STRSZ_CMD
Maximum size of Command::sData member. Size optimizes alignment of Command struct.
DataRequestRecord inherits and extends WASimCommander::DataRequest with data pertinent for use by a d...
Definition: structs.h:70
DataRequestRecord()
< Inherits DataRequest assignment operators.
Definition: WASimClient.cpp:69
Structure to hold data for registered (reusable) calculator events. Used to submit events with WASimC...
Definition: structs.h:150
std::string name
Optional custom name for this event. The name is for use with SimConnect_MapClientEventToSimEvent(id,...
Definition: structs.h:154
std::string code
The calculator code string to execute as the event action. The code is pre-compiled and stored on the...
Definition: structs.h:152
RegisteredEvent(uint32_t eventId=-1, const std::string &code="", const std::string &name="")
Default implicit constructor.
Definition: WASimClient.cpp:87
uint32_t eventId
A unique ID for this event. The ID can later be used to modify, trigger, or remove this event.
Definition: structs.h:151
Structure for using with WASimClient::getVariable() and WASimClient::setVariable() to specify informa...
Definition: structs.h:110
int variableId
Numeric ID of the variable to get/set. Overrides the variableName field if greater than -1....
Definition: structs.h:117
char variableType
A single character variable type identifier as documented in the MSFS SDK documentation (plus 'T' for...
Definition: structs.h:111
Command data structure. The member contents depend on the command type as described in each command t...
char sData[STRSZ_CMD]
String command parameter value, meaning depends on the command being issued.
WASimCommander::Enums::CommandId commandId
What to do.
double fData
double-precision floating point command parameter value, meaning depends on the command being issued.
uint32_t token
A unique ID for this command instance. Echoed back by server in command responses....
uint32_t uData
DWORD command parameter value, meaning depends on the command being issued.
Structure for variable value subscription requests.
uint32_t requestId
Unique ID for the request, subsequent use of this ID overwrites any previous request definition (but ...
WASimCommander::Enums::RequestType requestType
Named variable or calculated value.
uint32_t valueSize
Byte size of stored value; can also be one of the predefined DATA_TYPE_* constants.
float deltaEpsilon
Minimum change in numeric value required to trigger an update. The default of 0.0 is to send updates ...
char nameOrCode[STRSZ_REQ]
Variable name or full calculator string.
Data structure for sending Key Events to the sim with up to 5 event values. Events are specified usin...
Log record structure.