├── README.md ├── binding.gyp ├── index.js ├── package-lock.json ├── package.json └── src ├── addon.cc └── addon.h /README.md: -------------------------------------------------------------------------------- 1 | *** CURRENTLY IN DEVELOPMENT *** 2 | 3 | # msfs-simconnect-nodejs 4 | Microsoft Flight Simulator 2020 SimConnect SDK wrapper for NodeJS. 5 | 6 | Works on 64 bit version of NodeJS. Currently, for Windows only. 7 | 8 | ## Installation 9 | msfs-simconnect-nodejs uses a native NodeJS addon and therefore, it must be compiled first before you can use it as module within your project. 10 | 11 | ### 1.) Install the Node module 12 | 13 | `npm install msfs-simconnect-nodejs` 14 | 15 | ### 2.) Copy your SimConnect SDK files 16 | You need to copy your own SimConnect SDK files to msfs-simconnect-nodejs. 64 bit version is also supported. 17 | 18 | Default, the SimConnect SDK installed in C:\MSFS SDK\SimConnect SDK. Copy this SimConnect SDK directory and paste it in the root of msfs-simconnect-nodejs. 19 | 20 | ### 3.) Compile 21 | Compile the library: 22 | 23 | `npm run build` 24 | 25 | Once compiled, at the moment you need to copy your SimConnect.dll file from the SimConnect SDK directory into the build/Release directory. You need to this everytime you've done a rebuild. 26 | 27 | ## Usage 28 | Import the module: 29 | 30 | `const simConnect = require('msfs-simconnect-nodejs');` 31 | 32 | ## Differences between MSFS SimConnect and node SimConnect 33 | 34 | Calls to `requestDataOnSimObject` and `requestDataOnSimObjectType` will return the simulation ObjectID for the object in the callback data object in addition to the requested Simulation Variables. 35 | 36 | ObjectID 1 is the user. 37 | 38 | ## Requirements 39 | * NodeJS v12.18 64 bit 40 | * NPM v6.14 41 | * Microsoft Visual Studio 2019 (Community) 42 | * Python 3 (for compilation) 43 | * SimConnect SDK 44 | 45 | ### 2020-11-10 46 | * Works with SimConnect SDK 0.7.1 47 | 48 | ## Thanks 49 | Inspired by https://github.com/EvenAR/node-simconnect 50 | 51 | ## Licence 52 | [MIT](https://opensource.org/licenses/MIT) 53 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "msfs-simconnect-nodejs", 5 | "sources": [ "src/addon.cc" ], 6 | "include_dirs": [ 7 | " 4 | #include 5 | 6 | uv_loop_t *loop; 7 | uv_async_t async; 8 | 9 | std::map dataDefinitions; 10 | std::map systemEventCallbacks; 11 | std::map systemStateCallbacks; 12 | std::map dataRequestCallbacks; 13 | Nan::Callback *errorCallback; 14 | 15 | // Special events to listen for from the beginning 16 | SIMCONNECT_CLIENT_EVENT_ID openEventId; 17 | SIMCONNECT_CLIENT_EVENT_ID quitEventId; 18 | SIMCONNECT_CLIENT_EVENT_ID exceptionEventId; 19 | 20 | // Counters for creating unique IDs for SimConnect 21 | SIMCONNECT_DATA_DEFINITION_ID defineIdCounter; 22 | SIMCONNECT_CLIENT_EVENT_ID eventIdCounter; 23 | SIMCONNECT_DATA_REQUEST_ID requestIdCounter; 24 | 25 | std::stack unusedReqIds; 26 | 27 | // Semaphores 28 | uv_sem_t workerSem; 29 | uv_sem_t defineIdSem; 30 | uv_sem_t eventIdSem; 31 | uv_sem_t reqIdSem; 32 | 33 | HANDLE ghSimConnect = NULL; 34 | 35 | class DispatchWorker : public Nan::AsyncWorker 36 | { 37 | public: 38 | DispatchWorker(Nan::Callback *callback) : AsyncWorker(callback) 39 | { 40 | } 41 | ~DispatchWorker() {} 42 | 43 | void Execute() 44 | { 45 | uv_async_init(loop, &async, messageReceiver); // Must be called from worker thread 46 | 47 | while (true) 48 | { 49 | 50 | if (ghSimConnect) 51 | { 52 | uv_sem_wait(&workerSem); // Wait for mainthread to process the previous dispatch 53 | 54 | SIMCONNECT_RECV *pData; 55 | DWORD cbData; 56 | 57 | HRESULT hr = SimConnect_GetNextDispatch(ghSimConnect, &pData, &cbData); 58 | 59 | if (SUCCEEDED(hr)) 60 | { 61 | CallbackData data; 62 | data.pData = pData; 63 | data.cbData = cbData; 64 | data.ntstatus = 0; 65 | async.data = &data; 66 | uv_async_send(&async); 67 | } 68 | else if (NT_ERROR(hr)) 69 | { 70 | CallbackData data; 71 | data.ntstatus = (NTSTATUS)hr; 72 | async.data = &data; 73 | uv_async_send(&async); 74 | } 75 | else 76 | { 77 | uv_sem_post(&workerSem); // Continue 78 | Sleep(1); 79 | } 80 | } 81 | else 82 | { 83 | Sleep(10); 84 | } 85 | } 86 | } 87 | }; 88 | 89 | SIMCONNECT_DATA_DEFINITION_ID getUniqueDefineId() 90 | { 91 | uv_sem_wait(&defineIdSem); 92 | SIMCONNECT_DATA_DEFINITION_ID id = defineIdCounter; 93 | defineIdCounter++; 94 | uv_sem_post(&defineIdSem); 95 | return id; 96 | } 97 | 98 | SIMCONNECT_CLIENT_EVENT_ID getUniqueEventId() 99 | { 100 | uv_sem_wait(&eventIdSem); 101 | SIMCONNECT_CLIENT_EVENT_ID id = eventIdCounter; 102 | eventIdCounter++; 103 | uv_sem_post(&eventIdSem); 104 | return id; 105 | } 106 | 107 | SIMCONNECT_DATA_REQUEST_ID getUniqueRequestId() 108 | { 109 | uv_sem_wait(&reqIdSem); 110 | SIMCONNECT_DATA_REQUEST_ID id; 111 | if (!unusedReqIds.empty()) 112 | { 113 | id = unusedReqIds.top(); 114 | unusedReqIds.pop(); 115 | } 116 | else 117 | { 118 | id = requestIdCounter; 119 | requestIdCounter++; 120 | } 121 | uv_sem_post(&reqIdSem); 122 | return id; 123 | } 124 | 125 | // Runs on main thread after uv_async_send() is called 126 | void messageReceiver(uv_async_t *handle) 127 | { 128 | 129 | Nan::HandleScope scope; 130 | v8::Isolate *isolate = v8::Isolate::GetCurrent(); 131 | 132 | CallbackData *data = (CallbackData *)handle->data; 133 | 134 | if (NT_SUCCESS(data->ntstatus)) 135 | { 136 | switch (data->pData->dwID) 137 | { 138 | case SIMCONNECT_RECV_ID_EVENT: 139 | handleReceived_Event(isolate, data->pData, data->cbData); 140 | break; 141 | case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: 142 | handleReceived_Data(isolate, data->pData, data->cbData); 143 | break; 144 | case SIMCONNECT_RECV_ID_QUIT: 145 | handleReceived_Quit(isolate); 146 | break; 147 | case SIMCONNECT_RECV_ID_EXCEPTION: 148 | handleReceived_Exception(isolate, data->pData, data->cbData); 149 | break; 150 | case SIMCONNECT_RECV_ID_EVENT_FILENAME: 151 | handleReceived_Filename(isolate, data->pData, data->cbData); 152 | break; 153 | case SIMCONNECT_RECV_ID_OPEN: 154 | handleReceived_Open(isolate, data->pData, data->cbData); 155 | break; 156 | case SIMCONNECT_RECV_ID_SYSTEM_STATE: 157 | handleReceived_SystemState(isolate, data->pData, data->cbData); 158 | break; 159 | case SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE: 160 | handleReceived_DataByType(isolate, data->pData, data->cbData); 161 | break; 162 | case SIMCONNECT_RECV_ID_EVENT_FRAME: 163 | handleReceived_Frame(isolate, data->pData, data->cbData); 164 | break; 165 | default: 166 | printf("Unexpected message received (dwId: %i)\n", data->pData->dwID); 167 | break; 168 | } 169 | } 170 | else 171 | { 172 | handle_Error(isolate, data->ntstatus); 173 | } 174 | 175 | uv_sem_post(&workerSem); // The dispatch-worker can now continue 176 | } 177 | 178 | // Handles data requested with requestDataOnSimObject or requestDataOnSimObjectType 179 | void handleReceived_Data(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 180 | { 181 | SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA *)pData; 182 | int numVars = dataDefinitions[pObjData->dwDefineID].num_values; 183 | std::vector valTypes = dataDefinitions[pObjData->dwDefineID].datum_types; 184 | std::vector valIds = dataDefinitions[pObjData->dwDefineID].datum_names; 185 | 186 | Local result_list = Object::New(isolate); 187 | int dataValueOffset = 0; 188 | 189 | // always add the Object ID to the response payload 190 | result_list->Set(String::NewFromUtf8(isolate, "ObjectID"), Number::New(isolate, pObjData->dwObjectID)); 191 | 192 | for (int i = 0; i < numVars; i++) 193 | { 194 | int varSize = 0; 195 | 196 | if (valTypes[i] == SIMCONNECT_DATATYPE_STRINGV) 197 | { 198 | dataValueOffset += 8; // Not really sure why this is needed, but it fixes problems like this: "F-22 RapF-22 Raptor - 525th Fighter Squadron" 199 | char *pOutString; 200 | DWORD cbString; 201 | char *pStringv = ((char *)(&pObjData->dwData)); 202 | HRESULT hr = SimConnect_RetrieveString(pData, cbData, dataValueOffset + pStringv, &pOutString, &cbString); 203 | if (NT_ERROR(hr)) 204 | { 205 | handle_Error(isolate, hr); 206 | return; 207 | } 208 | 209 | v8::Local key = String::NewFromUtf8(isolate, valIds.at(i).c_str()); 210 | try 211 | { 212 | v8::Local value = String::NewFromOneByte(isolate, (const uint8_t *)pOutString, v8::NewStringType::kNormal).ToLocalChecked(); 213 | result_list->Set(key, value); 214 | } 215 | catch (...) 216 | { 217 | v8::Local value = String::NewFromUtf8(isolate, "ERROR"); 218 | result_list->Set(key, value); 219 | } 220 | 221 | varSize = cbString; 222 | } 223 | else 224 | { 225 | //printf("------ %s -----\n", valIds.at(i).c_str()); 226 | varSize = sizeMap[valTypes[i]]; 227 | char *p = ((char *)(&pObjData->dwData) + dataValueOffset); 228 | double *var = (double *)p; 229 | result_list->Set(String::NewFromUtf8(isolate, valIds.at(i).c_str()), Number::New(isolate, *var)); 230 | } 231 | dataValueOffset += varSize; 232 | } 233 | 234 | const int argc = 1; 235 | Local argv[argc] = { 236 | result_list}; 237 | dataRequestCallbacks[pObjData->dwRequestID]->Call(isolate->GetCurrentContext()->Global(), argc, argv); 238 | } 239 | 240 | void handleReceived_DataByType(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 241 | { 242 | SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA *)pData; 243 | handleReceived_Data(isolate, pData, cbData); 244 | unusedReqIds.push(pObjData->dwRequestID); // The id can be re-used in next request 245 | } 246 | 247 | void handleReceived_Frame(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 248 | { 249 | SIMCONNECT_RECV_EVENT_FRAME *pFrame = (SIMCONNECT_RECV_EVENT_FRAME *)pData; 250 | // printf("frame data recived: %f FPS\n",pFrame->fFrameRate); 251 | 252 | const int argc = 2; 253 | 254 | Local argv[argc] = { 255 | Number::New(isolate, pFrame->fFrameRate), 256 | Number::New(isolate, pFrame->fSimSpeed)}; 257 | 258 | systemEventCallbacks[pFrame->uEventID]->Call(isolate->GetCurrentContext()->Global(), argc, argv); 259 | 260 | // Local obj = Object::New(isolate); 261 | 262 | // float fFrameRate; 263 | // float fSimSpeed; 264 | // DWORD dwFlags; 265 | 266 | // obj->Set(String::NewFromUtf8(isolate, "float"), Number::New(isolate, pFrame->fFrameRate)); 267 | // obj->Set(String::NewFromUtf8(isolate, "float"), Number::New(isolate, pFrame->fSimSpeed)); 268 | 269 | // Local argv[1] = { obj }; 270 | // unusedReqIds.push(pFrame->dwRequestID); // The id can be re-used in next request 271 | } 272 | 273 | void handle_Error(Isolate *isolate, NTSTATUS code) 274 | { 275 | // Codes found so far: 0xC000014B, 0xC000020D, 0xC000013C 276 | ghSimConnect = NULL; 277 | char errorCode[32]; 278 | sprintf(errorCode, "0x%08X", code); 279 | 280 | const int argc = 1; 281 | Local argv[argc] = { 282 | String::NewFromUtf8(isolate, errorCode)}; 283 | 284 | errorCallback->Call(isolate->GetCurrentContext()->Global(), argc, argv); 285 | } 286 | 287 | void handleReceived_Event(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 288 | { 289 | SIMCONNECT_RECV_EVENT *myEvent = (SIMCONNECT_RECV_EVENT *)pData; 290 | 291 | const int argc = 1; 292 | Local argv[argc] = { 293 | Number::New(isolate, myEvent->dwData)}; 294 | 295 | systemEventCallbacks[myEvent->uEventID]->Call(isolate->GetCurrentContext()->Global(), argc, argv); 296 | } 297 | 298 | void handleReceived_Exception(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 299 | { 300 | SIMCONNECT_RECV_EXCEPTION *except = (SIMCONNECT_RECV_EXCEPTION *)pData; 301 | 302 | Local obj = Object::New(isolate); 303 | obj->Set(String::NewFromUtf8(isolate, "dwException"), Number::New(isolate, except->dwException)); 304 | obj->Set(String::NewFromUtf8(isolate, "dwSendID"), Number::New(isolate, except->dwSendID)); 305 | obj->Set(String::NewFromUtf8(isolate, "dwIndex"), Number::New(isolate, except->dwIndex)); 306 | obj->Set(String::NewFromUtf8(isolate, "cbData"), Number::New(isolate, cbData)); 307 | obj->Set(String::NewFromUtf8(isolate, "cbVersion"), Number::New(isolate, except->dwException)); 308 | obj->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, exceptionNames[SIMCONNECT_EXCEPTION(except->dwException)])); 309 | 310 | Local argv[1] = {obj}; 311 | 312 | systemEventCallbacks[exceptionEventId]->Call(isolate->GetCurrentContext()->Global(), 1, argv); 313 | } 314 | 315 | void handleReceived_Filename(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 316 | { 317 | SIMCONNECT_RECV_EVENT_FILENAME *fileName = (SIMCONNECT_RECV_EVENT_FILENAME *)pData; 318 | const int argc = 1; 319 | Local argv[argc] = { 320 | String::NewFromUtf8(isolate, (const char *)fileName->szFileName)}; 321 | 322 | systemEventCallbacks[fileName->uEventID]->Call(isolate->GetCurrentContext()->Global(), argc, argv); 323 | } 324 | 325 | void handleReceived_Open(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 326 | { 327 | SIMCONNECT_RECV_OPEN *pOpen = (SIMCONNECT_RECV_OPEN *)pData; 328 | 329 | char simconnVersion[32]; 330 | sprintf(simconnVersion, "%d.%d.%d.%d", pOpen->dwSimConnectVersionMajor, pOpen->dwSimConnectVersionMinor, pOpen->dwSimConnectBuildMajor, pOpen->dwSimConnectBuildMinor); 331 | 332 | const int argc = 2; 333 | 334 | Local argv[argc] = { 335 | String::NewFromOneByte(isolate, (const uint8_t *)pOpen->szApplicationName, v8::NewStringType::kNormal).ToLocalChecked(), 336 | String::NewFromUtf8(isolate, simconnVersion)}; 337 | 338 | systemEventCallbacks[openEventId]->Call(isolate->GetCurrentContext()->Global(), argc, argv); 339 | } 340 | 341 | void handleReceived_SystemState(Isolate *isolate, SIMCONNECT_RECV *pData, DWORD cbData) 342 | { 343 | SIMCONNECT_RECV_SYSTEM_STATE *pState = (SIMCONNECT_RECV_SYSTEM_STATE *)pData; 344 | 345 | Local obj = Object::New(isolate); 346 | obj->Set(String::NewFromUtf8(isolate, "integer"), Number::New(isolate, pState->dwInteger)); 347 | obj->Set(String::NewFromUtf8(isolate, "float"), Number::New(isolate, pState->fFloat)); 348 | obj->Set(String::NewFromUtf8(isolate, "string"), String::NewFromUtf8(isolate, "string")); 349 | 350 | Local argv[1] = {obj}; 351 | systemStateCallbacks[openEventId]->Call(isolate->GetCurrentContext()->Global(), 1, argv); 352 | } 353 | 354 | void handleReceived_Quit(Isolate *isolate) 355 | { 356 | ghSimConnect = NULL; 357 | systemEventCallbacks[quitEventId]->Call(isolate->GetCurrentContext()->Global(), 0, NULL); 358 | } 359 | 360 | void handleSimDisconnect(Isolate *isolate) 361 | { 362 | } 363 | 364 | // Wrapped SimConnect-functions ////////////////////////////////////////////////////// 365 | void Open(const v8::FunctionCallbackInfo &args) 366 | { 367 | uv_sem_init(&workerSem, 1); 368 | uv_sem_init(&defineIdSem, 1); 369 | uv_sem_init(&eventIdSem, 1); 370 | uv_sem_init(&reqIdSem, 1); 371 | 372 | defineIdCounter = 0; 373 | eventIdCounter = 0; 374 | requestIdCounter = 0; 375 | 376 | Isolate *isolate = args.GetIsolate(); 377 | v8::Local ctx = Nan::GetCurrentContext(); 378 | 379 | // Get arguments 380 | v8::String::Utf8Value appName(isolate, args[0]->ToString(ctx).ToLocalChecked()); 381 | 382 | openEventId = getUniqueEventId(); 383 | systemEventCallbacks[openEventId] = {new Nan::Callback(args[1].As())}; 384 | quitEventId = getUniqueEventId(); 385 | systemEventCallbacks[quitEventId] = {new Nan::Callback(args[2].As())}; 386 | exceptionEventId = getUniqueEventId(); 387 | systemEventCallbacks[exceptionEventId] = {new Nan::Callback(args[3].As())}; 388 | errorCallback = {new Nan::Callback(args[4].As())}; 389 | 390 | // Create dispatch looper thread 391 | loop = uv_default_loop(); 392 | 393 | Nan::AsyncQueueWorker(new DispatchWorker(NULL)); 394 | 395 | // Open connection 396 | HRESULT hr = SimConnect_Open(&ghSimConnect, *appName, NULL, 0, 0, 0); 397 | 398 | // Return true if success 399 | Local retval = v8::Boolean::New(isolate, SUCCEEDED(hr)); 400 | 401 | args.GetReturnValue().Set(retval); 402 | } 403 | 404 | void Close(const v8::FunctionCallbackInfo &args) 405 | { 406 | if (ghSimConnect) 407 | { 408 | Isolate *isolate = args.GetIsolate(); 409 | printf("Trying to close..\n"); 410 | HRESULT hr = SimConnect_Close(&ghSimConnect); 411 | if (NT_ERROR(hr)) 412 | { 413 | handle_Error(isolate, hr); 414 | return; 415 | } 416 | 417 | printf("Closed: %i\n", hr); 418 | ghSimConnect = NULL; 419 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 420 | } 421 | } 422 | 423 | void isConnected(const v8::FunctionCallbackInfo &args) 424 | { 425 | Isolate *isolate = args.GetIsolate(); 426 | args.GetReturnValue().Set(v8::Boolean::New(isolate, ghSimConnect)); 427 | } 428 | 429 | void RequestSystemState(const v8::FunctionCallbackInfo &args) 430 | { 431 | if (ghSimConnect) 432 | { 433 | Isolate *isolate = args.GetIsolate(); 434 | v8::Local ctx = Nan::GetCurrentContext(); 435 | 436 | v8::String::Utf8Value stateName(isolate, args[0]->ToString(ctx).ToLocalChecked()); 437 | 438 | SIMCONNECT_DATA_REQUEST_ID reqId = getUniqueRequestId(); 439 | systemStateCallbacks[reqId] = new Nan::Callback(args[1].As()); 440 | HRESULT hr = SimConnect_RequestSystemState(ghSimConnect, reqId, *stateName); 441 | if (NT_ERROR(hr)) 442 | { 443 | handle_Error(isolate, hr); 444 | return; 445 | } 446 | args.GetReturnValue().Set(v8::Number::New(isolate, reqId)); 447 | } 448 | } 449 | 450 | void FlightLoad(const v8::FunctionCallbackInfo &args) 451 | { 452 | if (ghSimConnect) 453 | { 454 | Isolate *isolate = args.GetIsolate(); 455 | v8::Local ctx = Nan::GetCurrentContext(); 456 | 457 | v8::String::Utf8Value szFileName(isolate, args[0]->ToString(ctx).ToLocalChecked()); 458 | HRESULT hr = SimConnect_FlightLoad(ghSimConnect, *szFileName); 459 | if (NT_ERROR(hr)) 460 | { 461 | handle_Error(isolate, hr); 462 | return; 463 | } 464 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 465 | } 466 | } 467 | 468 | void TransmitClientEvent(const v8::FunctionCallbackInfo &args) 469 | { 470 | if (ghSimConnect) 471 | { 472 | Isolate *isolate = args.GetIsolate(); 473 | v8::Local ctx = Nan::GetCurrentContext(); 474 | 475 | v8::String::Utf8Value eventName(isolate, args[0]->ToString(ctx).ToLocalChecked()); 476 | DWORD data = args.Length() > 1 ? args[1]->Int32Value(ctx).ToChecked() : 0; 477 | 478 | SIMCONNECT_CLIENT_EVENT_ID id = getUniqueEventId(); 479 | HRESULT hr = SimConnect_MapClientEventToSimEvent(ghSimConnect, id, *eventName); 480 | if (NT_ERROR(hr)) 481 | { 482 | handle_Error(isolate, hr); 483 | return; 484 | } 485 | 486 | hr = SimConnect_TransmitClientEvent(ghSimConnect, SIMCONNECT_OBJECT_ID_USER, id, data, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY); 487 | if (NT_ERROR(hr)) 488 | { 489 | handle_Error(isolate, hr); 490 | return; 491 | } 492 | 493 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 494 | } 495 | } 496 | 497 | void SubscribeToSystemEvent(const v8::FunctionCallbackInfo &args) 498 | { 499 | if (ghSimConnect) 500 | { 501 | v8::Isolate *isolate = args.GetIsolate(); 502 | v8::Local ctx = Nan::GetCurrentContext(); 503 | 504 | SIMCONNECT_CLIENT_EVENT_ID eventId = getUniqueEventId(); 505 | 506 | v8::String::Utf8Value systemEventName(isolate, args[0]->ToString(ctx).ToLocalChecked()); 507 | systemEventCallbacks[eventId] = {new Nan::Callback(args[1].As())}; 508 | 509 | HANDLE hSimConnect = ghSimConnect; 510 | HRESULT hr = SimConnect_SubscribeToSystemEvent(hSimConnect, eventId, *systemEventName); 511 | if (NT_ERROR(hr)) 512 | { 513 | handle_Error(isolate, hr); 514 | return; 515 | } 516 | 517 | args.GetReturnValue().Set(v8::Integer::New(isolate, eventId)); 518 | } 519 | } 520 | 521 | void RequestDataOnSimObject(const v8::FunctionCallbackInfo &args) 522 | { 523 | if (ghSimConnect) 524 | { 525 | v8::Isolate *isolate = args.GetIsolate(); 526 | v8::Local ctx = Nan::GetCurrentContext(); 527 | 528 | Local reqValues = v8::Local::Cast(args[0]); 529 | auto callback = new Nan::Callback(args[1].As()); 530 | 531 | int objectId = args.Length() > 2 ? args[2]->Int32Value(ctx).ToChecked() : SIMCONNECT_OBJECT_ID_USER; 532 | int periodId = args.Length() > 3 ? args[3]->Int32Value(ctx).ToChecked() : SIMCONNECT_PERIOD_SIM_FRAME; 533 | int flags = args.Length() > 4 ? args[4]->Int32Value(ctx).ToChecked() : 0; 534 | int origin = args.Length() > 5 ? args[5]->Int32Value(ctx).ToChecked() : 0; 535 | int interval = args.Length() > 6 ? args[6]->Int32Value(ctx).ToChecked() : 0; 536 | DWORD limit = args.Length() > 7 ? args[7]->NumberValue(ctx).ToChecked() : 0; 537 | 538 | SIMCONNECT_DATA_REQUEST_ID reqId = getUniqueRequestId(); 539 | 540 | DataDefinition definition = generateDataDefinition(isolate, ghSimConnect, reqValues); 541 | 542 | HRESULT hr = SimConnect_RequestDataOnSimObject(ghSimConnect, reqId, definition.id, objectId, SIMCONNECT_PERIOD(periodId), flags, origin, interval, limit); 543 | if (NT_ERROR(hr)) 544 | { 545 | handle_Error(isolate, hr); 546 | return; 547 | } 548 | 549 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 550 | 551 | dataDefinitions[definition.id] = definition; 552 | dataRequestCallbacks[reqId] = callback; 553 | } 554 | } 555 | 556 | void RequestDataOnSimObjectType(const v8::FunctionCallbackInfo &args) 557 | { 558 | if (ghSimConnect) 559 | { 560 | v8::Isolate *isolate = args.GetIsolate(); 561 | v8::Local ctx = Nan::GetCurrentContext(); 562 | 563 | DataDefinition definition; 564 | 565 | if (args[0]->IsArray()) 566 | { 567 | Local reqValues = v8::Local::Cast(args[0]); 568 | definition = generateDataDefinition(isolate, ghSimConnect, reqValues); 569 | } 570 | else if (args[0]->IsNumber()) 571 | { 572 | definition = dataDefinitions[args[0]->NumberValue(ctx).ToChecked()]; 573 | } 574 | 575 | auto callback = new Nan::Callback(args[1].As()); 576 | 577 | DWORD radius = args.Length() > 2 ? args[2]->Int32Value(ctx).ToChecked() : 0; 578 | int typeId = args.Length() > 3 ? args[3]->Int32Value(ctx).ToChecked() : SIMCONNECT_SIMOBJECT_TYPE_USER; 579 | 580 | SIMCONNECT_DATA_REQUEST_ID reqId = getUniqueRequestId(); 581 | HRESULT hr = SimConnect_RequestDataOnSimObjectType(ghSimConnect, reqId, definition.id, radius, SIMCONNECT_SIMOBJECT_TYPE(typeId)); 582 | if (NT_ERROR(hr)) 583 | { 584 | handle_Error(isolate, hr); 585 | return; 586 | } 587 | 588 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 589 | 590 | dataDefinitions[definition.id] = definition; 591 | dataRequestCallbacks[reqId] = callback; 592 | } 593 | } 594 | 595 | void CreateDataDefinition(const v8::FunctionCallbackInfo &args) 596 | { 597 | if (ghSimConnect) 598 | { 599 | v8::Isolate *isolate = args.GetIsolate(); 600 | Local reqValues = v8::Local::Cast(args[0]); 601 | DataDefinition definition = generateDataDefinition(isolate, ghSimConnect, reqValues); 602 | args.GetReturnValue().Set(v8::Number::New(isolate, definition.id)); 603 | dataDefinitions[definition.id] = definition; 604 | } 605 | } 606 | 607 | void SetDataOnSimObject(const v8::FunctionCallbackInfo &args) 608 | { 609 | if (ghSimConnect) 610 | { 611 | v8::Isolate *isolate = args.GetIsolate(); 612 | v8::Local ctx = Nan::GetCurrentContext(); 613 | 614 | v8::String::Utf8Value name(isolate, args[0]->ToString(ctx).ToLocalChecked()); 615 | v8::String::Utf8Value unit(isolate, args[1]->ToString(ctx).ToLocalChecked()); 616 | 617 | double value = args[2]->NumberValue(ctx).ToChecked(); 618 | 619 | int objectId = args.Length() > 3 ? args[3]->Int32Value(ctx).FromMaybe(SIMCONNECT_OBJECT_ID_USER) : SIMCONNECT_OBJECT_ID_USER; 620 | int flags = args.Length() > 4 ? args[4]->Int32Value(ctx).ToChecked() : 0; 621 | 622 | SIMCONNECT_DATA_DEFINITION_ID defId = getUniqueDefineId(); 623 | 624 | HRESULT hr = SimConnect_AddToDataDefinition(ghSimConnect, defId, *name, *unit); 625 | if (NT_ERROR(hr)) 626 | { 627 | handle_Error(isolate, hr); 628 | return; 629 | } 630 | 631 | hr = SimConnect_SetDataOnSimObject(ghSimConnect, defId, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(value), &value); 632 | if (NT_ERROR(hr)) 633 | { 634 | handle_Error(isolate, hr); 635 | return; 636 | } 637 | 638 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 639 | } 640 | } 641 | 642 | // Generates a SimConnect data definition for the collection of requests. 643 | DataDefinition generateDataDefinition(Isolate *isolate, HANDLE hSimConnect, Local requestedValues) 644 | { 645 | 646 | SIMCONNECT_DATA_DEFINITION_ID definitionId = getUniqueDefineId(); 647 | v8::Local ctx = Nan::GetCurrentContext(); 648 | 649 | HRESULT hr = -1; 650 | bool success = true; 651 | unsigned int numValues = requestedValues->Length(); 652 | 653 | std::vector datumNames; 654 | std::vector datumTypes; 655 | 656 | for (unsigned int i = 0; i < requestedValues->Length(); i++) 657 | { 658 | Local value = v8::Local::Cast(requestedValues->Get(i)); 659 | 660 | if (value->IsArray()) 661 | { 662 | int len = value->Length(); 663 | 664 | if (len > 1) 665 | { 666 | v8::String::Utf8Value datumName(isolate, value->Get(0)->ToString(ctx).ToLocalChecked()); 667 | const char *sDatumName = *datumName; 668 | const char *sUnitsName = NULL; 669 | 670 | if (!value->Get(1)->IsNull()) 671 | { // Should be NULL for string 672 | v8::String::Utf8Value unitsName(isolate, value->Get(1)->ToString(ctx).ToLocalChecked()); 673 | sUnitsName = *unitsName; 674 | } 675 | 676 | SIMCONNECT_DATATYPE datumType = SIMCONNECT_DATATYPE_FLOAT64; // Default type (double) 677 | double epsilon; 678 | float datumId; 679 | 680 | if (len > 1) 681 | { 682 | hr = SimConnect_AddToDataDefinition(hSimConnect, definitionId, sDatumName, sUnitsName); 683 | if (NT_ERROR(hr)) 684 | { 685 | handle_Error(isolate, hr); 686 | break; 687 | } 688 | } 689 | if (len > 2) 690 | { 691 | int t = value->Get(2)->Int32Value(ctx).ToChecked(); 692 | datumType = SIMCONNECT_DATATYPE(t); 693 | hr = SimConnect_AddToDataDefinition(hSimConnect, definitionId, sDatumName, sUnitsName, datumType); 694 | if (NT_ERROR(hr)) 695 | { 696 | handle_Error(isolate, hr); 697 | break; 698 | } 699 | } 700 | if (len > 3) 701 | { 702 | epsilon = value->Get(3)->Int32Value(ctx).ToChecked(); 703 | hr = SimConnect_AddToDataDefinition(hSimConnect, definitionId, sDatumName, sUnitsName, datumType, epsilon); 704 | if (NT_ERROR(hr)) 705 | { 706 | handle_Error(isolate, hr); 707 | break; 708 | } 709 | } 710 | if (len > 4) 711 | { 712 | datumId = value->Get(4)->Int32Value(ctx).ToChecked(); 713 | hr = SimConnect_AddToDataDefinition(hSimConnect, definitionId, sDatumName, sUnitsName, datumType, epsilon, datumId); 714 | if (NT_ERROR(hr)) 715 | { 716 | handle_Error(isolate, hr); 717 | break; 718 | } 719 | } 720 | 721 | std::string datumNameStr(sDatumName); 722 | datumNames.push_back(datumNameStr); 723 | datumTypes.push_back(datumType); 724 | } 725 | } 726 | } 727 | 728 | return {definitionId, numValues, datumNames, datumTypes}; 729 | } 730 | 731 | // Custom useful functions //////////////////////////////////////////////////////////////////// 732 | void SetAircraftInitialPosition(const v8::FunctionCallbackInfo &args) 733 | { 734 | if (ghSimConnect) 735 | { 736 | Isolate *isolate = args.GetIsolate(); 737 | v8::Local ctx = Nan::GetCurrentContext(); 738 | 739 | SIMCONNECT_DATA_INITPOSITION init; 740 | Local json = args[0]->ToObject(ctx).ToLocalChecked(); 741 | 742 | v8::Local altProp = Nan::New("altitude").ToLocalChecked(); 743 | v8::Local latProp = Nan::New("latitude").ToLocalChecked(); 744 | v8::Local lngProp = Nan::New("longitude").ToLocalChecked(); 745 | v8::Local pitchProp = Nan::New("pitch").ToLocalChecked(); 746 | v8::Local bankProp = Nan::New("bank").ToLocalChecked(); 747 | v8::Local hdgProp = Nan::New("heading").ToLocalChecked(); 748 | v8::Local gndProp = Nan::New("onGround").ToLocalChecked(); 749 | v8::Local iasProp = Nan::New("airspeed").ToLocalChecked(); 750 | 751 | init.Altitude = json->HasRealNamedProperty(ctx, altProp).ToChecked() ? json->Get(altProp)->NumberValue(ctx).ToChecked() : 0; 752 | init.Latitude = json->HasRealNamedProperty(ctx, latProp).ToChecked() ? json->Get(latProp)->NumberValue(ctx).ToChecked() : 0; 753 | init.Longitude = json->HasRealNamedProperty(ctx, lngProp).ToChecked() ? json->Get(lngProp)->NumberValue(ctx).ToChecked() : 0; 754 | init.Pitch = json->HasRealNamedProperty(ctx, pitchProp).ToChecked() ? json->Get(pitchProp)->NumberValue(ctx).ToChecked() : 0; 755 | init.Bank = json->HasRealNamedProperty(ctx, bankProp).ToChecked() ? json->Get(bankProp)->NumberValue(ctx).ToChecked() : 0; 756 | init.Heading = json->HasRealNamedProperty(ctx, hdgProp).ToChecked() ? json->Get(hdgProp)->NumberValue(ctx).ToChecked() : 0; 757 | init.OnGround = json->HasRealNamedProperty(ctx, gndProp).ToChecked() ? json->Get(gndProp)->IntegerValue(ctx).ToChecked() : 0; 758 | init.Airspeed = json->HasRealNamedProperty(ctx, iasProp).ToChecked() ? json->Get(iasProp)->IntegerValue(ctx).ToChecked() : 0; 759 | 760 | SIMCONNECT_DATA_DEFINITION_ID id = getUniqueDefineId(); 761 | HRESULT hr = SimConnect_AddToDataDefinition(ghSimConnect, id, "Initial Position", NULL, SIMCONNECT_DATATYPE_INITPOSITION); 762 | if (NT_ERROR(hr)) 763 | { 764 | handle_Error(isolate, hr); 765 | return; 766 | } 767 | 768 | hr = SimConnect_SetDataOnSimObject(ghSimConnect, id, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(init), &init); 769 | if (NT_ERROR(hr)) 770 | { 771 | handle_Error(isolate, hr); 772 | return; 773 | } 774 | 775 | args.GetReturnValue().Set(v8::Boolean::New(isolate, SUCCEEDED(hr))); 776 | } 777 | } 778 | 779 | void Initialize(v8::Local exports) 780 | { 781 | NODE_SET_METHOD(exports, "open", Open); 782 | NODE_SET_METHOD(exports, "close", Close); 783 | NODE_SET_METHOD(exports, "subscribeToSystemEvent", SubscribeToSystemEvent); 784 | NODE_SET_METHOD(exports, "requestDataOnSimObject", RequestDataOnSimObject); 785 | NODE_SET_METHOD(exports, "setDataOnSimObject", SetDataOnSimObject); 786 | NODE_SET_METHOD(exports, "requestDataOnSimObjectType", RequestDataOnSimObjectType); 787 | NODE_SET_METHOD(exports, "setAircraftInitialPosition", SetAircraftInitialPosition); 788 | NODE_SET_METHOD(exports, "transmitClientEvent", TransmitClientEvent); 789 | NODE_SET_METHOD(exports, "requestSystemState", RequestSystemState); 790 | NODE_SET_METHOD(exports, "createDataDefinition", CreateDataDefinition); 791 | NODE_SET_METHOD(exports, "flightLoad", FlightLoad); 792 | NODE_SET_METHOD(exports, "isConnected", isConnected); 793 | } 794 | 795 | NODE_MODULE(addon, Initialize); 796 | -------------------------------------------------------------------------------- /src/addon.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "SimConnect.h" 6 | 7 | using namespace v8; 8 | 9 | struct CallbackData { 10 | DWORD cbData; 11 | SIMCONNECT_RECV* pData; 12 | NTSTATUS ntstatus; 13 | }; 14 | 15 | struct SystemEventRequest { 16 | Nan::Callback* jsCallback; 17 | }; 18 | 19 | struct DataDefinition { 20 | SIMCONNECT_DATA_DEFINITION_ID id; 21 | unsigned int num_values; 22 | std::vector datum_names; 23 | std::vector datum_types; 24 | }; 25 | 26 | 27 | std::map exceptionNames = { 28 | { SIMCONNECT_EXCEPTION_NONE, "SIMCONNECT_EXCEPTION_NONE" }, 29 | { SIMCONNECT_EXCEPTION_ERROR, "SIMCONNECT_EXCEPTION_ERROR" }, 30 | { SIMCONNECT_EXCEPTION_SIZE_MISMATCH, "SIMCONNECT_EXCEPTION_SIZE_MISMATCH" }, 31 | { SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID, "SIMCONNECT_EXCEPTION_UNRECOGNIZED_ID" }, 32 | { SIMCONNECT_EXCEPTION_UNOPENED, "SIMCONNECT_EXCEPTION_UNOPENED" }, 33 | { SIMCONNECT_EXCEPTION_VERSION_MISMATCH, "SIMCONNECT_EXCEPTION_VERSION_MISMATCH" }, 34 | { SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS, "SIMCONNECT_EXCEPTION_TOO_MANY_GROUPS" }, 35 | { SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED, "SIMCONNECT_EXCEPTION_NAME_UNRECOGNIZED" }, 36 | { SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES, "SIMCONNECT_EXCEPTION_TOO_MANY_EVENT_NAMES" }, 37 | { SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE, "SIMCONNECT_EXCEPTION_EVENT_ID_DUPLICATE" }, 38 | { SIMCONNECT_EXCEPTION_TOO_MANY_MAPS, "SIMCONNECT_EXCEPTION_TOO_MANY_MAPS" }, 39 | { SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS, "SIMCONNECT_EXCEPTION_TOO_MANY_OBJECTS" }, 40 | { SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS, "SIMCONNECT_EXCEPTION_TOO_MANY_REQUESTS" }, 41 | { SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT, "SIMCONNECT_EXCEPTION_WEATHER_INVALID_PORT" }, 42 | { SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR, "SIMCONNECT_EXCEPTION_WEATHER_INVALID_METAR" }, 43 | { SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION, "SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_GET_OBSERVATION" }, 44 | { SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION, "SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_CREATE_STATION" }, 45 | { SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION, "SIMCONNECT_EXCEPTION_WEATHER_UNABLE_TO_REMOVE_STATION" }, 46 | { SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE, "SIMCONNECT_EXCEPTION_INVALID_DATA_TYPE" }, 47 | { SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE, "SIMCONNECT_EXCEPTION_INVALID_DATA_SIZE" }, 48 | { SIMCONNECT_EXCEPTION_DATA_ERROR, "SIMCONNECT_EXCEPTION_DATA_ERROR" }, 49 | { SIMCONNECT_EXCEPTION_INVALID_ARRAY, "SIMCONNECT_EXCEPTION_INVALID_ARRAY" }, 50 | { SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED, "SIMCONNECT_EXCEPTION_CREATE_OBJECT_FAILED" }, 51 | { SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE, "SIMCONNECT_EXCEPTION_OPERATION_INVALID_FOR_OBJECT_TYPE" }, 52 | { SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION, "SIMCONNECT_EXCEPTION_ILLEGAL_OPERATION" }, 53 | { SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED, "SIMCONNECT_EXCEPTION_ALREADY_SUBSCRIBED" }, 54 | { SIMCONNECT_EXCEPTION_INVALID_ENUM, "SIMCONNECT_EXCEPTION_INVALID_ENUM" }, 55 | { SIMCONNECT_EXCEPTION_DEFINITION_ERROR, "SIMCONNECT_EXCEPTION_DEFINITION_ERROR" }, 56 | { SIMCONNECT_EXCEPTION_DUPLICATE_ID, "SIMCONNECT_EXCEPTION_DUPLICATE_ID" }, 57 | { SIMCONNECT_EXCEPTION_DATUM_ID, "SIMCONNECT_EXCEPTION_DATUM_ID" }, 58 | { SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS, "SIMCONNECT_EXCEPTION_OUT_OF_BOUNDS" }, 59 | { SIMCONNECT_EXCEPTION_ALREADY_CREATED, "SIMCONNECT_EXCEPTION_ALREADY_CREATED" }, 60 | { SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE, "SIMCONNECT_EXCEPTION_OBJECT_OUTSIDE_REALITY_BUBBLE" }, 61 | { SIMCONNECT_EXCEPTION_OBJECT_CONTAINER, "SIMCONNECT_EXCEPTION_OBJECT_CONTAINER" }, 62 | { SIMCONNECT_EXCEPTION_OBJECT_AI, "SIMCONNECT_EXCEPTION_OBJECT_AI" }, 63 | { SIMCONNECT_EXCEPTION_OBJECT_ATC, "SIMCONNECT_EXCEPTION_OBJECT_ATC" }, 64 | { SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE, "SIMCONNECT_EXCEPTION_OBJECT_SCHEDULE" } 65 | }; 66 | 67 | std::map sizeMap = 68 | { 69 | { SIMCONNECT_DATATYPE_INT32, 4 }, 70 | { SIMCONNECT_DATATYPE_INT64, 8 }, 71 | { SIMCONNECT_DATATYPE_FLOAT32, 4 }, 72 | { SIMCONNECT_DATATYPE_FLOAT64, 8 }, 73 | { SIMCONNECT_DATATYPE_STRING8, 8 }, 74 | { SIMCONNECT_DATATYPE_STRING32, 32 }, 75 | { SIMCONNECT_DATATYPE_STRING64, 64 }, 76 | { SIMCONNECT_DATATYPE_STRING128, 128 }, 77 | { SIMCONNECT_DATATYPE_STRING256, 256 }, 78 | { SIMCONNECT_DATATYPE_STRING260, 260 } 79 | }; 80 | 81 | 82 | void handleReceived_Data(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 83 | void handleReceived_DataByType(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 84 | void handleReceived_Frame(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 85 | void handleReceived_Event(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 86 | void handleReceived_Exception(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 87 | void handleReceived_Filename(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 88 | void handleReceived_Open(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 89 | void handleReceived_SystemState(Isolate* isolate, SIMCONNECT_RECV* pData, DWORD cbData); 90 | void handleReceived_Quit(Isolate* isolate); 91 | void handle_Error(Isolate* isolate, NTSTATUS code); 92 | 93 | void messageReceiver(uv_async_t* handle); 94 | DataDefinition generateDataDefinition(Isolate* isolate, HANDLE hSimConnect, Local requestedValues); --------------------------------------------------------------------------------